From 64b7eb3d9c0ccf7e55d2a4261deb87e168914d39 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 1 Oct 2023 14:18:54 +0200 Subject: [PATCH] Add Victron charger (#10133) --- charger/victron.go | 150 ++++++++++++++++++++++ templates/definition/charger/victron.yaml | 16 +++ templates/evcc.io/brands.json | 1 + 3 files changed, 167 insertions(+) create mode 100644 charger/victron.go create mode 100644 templates/definition/charger/victron.yaml diff --git a/charger/victron.go b/charger/victron.go new file mode 100644 index 0000000000..989a2268e7 --- /dev/null +++ b/charger/victron.go @@ -0,0 +1,150 @@ +package charger + +// LICENSE + +// Copyright (c) 2023 andig + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ( + "encoding/binary" + "errors" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/modbus" + "github.com/evcc-io/evcc/util/sponsor" +) + +// Victron charger implementation +type Victron struct { + conn *modbus.Connection +} + +const ( + victronRegMode = 3815 + victronRegEnergy = 3816 + victronRegPower = 3821 + victronRegStatus = 3824 + victronRegSetCurrent = 3825 + victronRegEnabled = 3826 +) + +func init() { + registry.Add("victron", NewVictronFromConfig) +} + +// NewVictronFromConfig creates a ABB charger from generic config +func NewVictronFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := modbus.TcpSettings{ + ID: 100, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + return NewVictron(cc.URI, cc.ID) +} + +// NewVictron creates Victron charger +func NewVictron(uri string, slaveID uint8) (api.Charger, error) { + conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID) + if err != nil { + return nil, err + } + + if !sponsor.IsAuthorized() { + return nil, api.ErrSponsorRequired + } + + log := util.NewLogger("victron") + conn.Logger(log.TRACE) + + wb := &Victron{ + conn: conn, + } + + b, err := wb.conn.ReadHoldingRegisters(victronRegMode, 1) + if err != nil { + return nil, err + } + + if binary.BigEndian.Uint16(b) != 0 { + return nil, errors.New("charger must be in manual mode") + } + + return wb, err +} + +// Status implements the api.Charger interface +func (wb *Victron) Status() (api.ChargeStatus, error) { + b, err := wb.conn.ReadHoldingRegisters(victronRegStatus, 1) + if err != nil { + return api.StatusNone, err + } + + return api.ChargeStatusString(string('A' + rune(binary.BigEndian.Uint16(b)))) +} + +// Enabled implements the api.Charger interface +func (wb *Victron) Enabled() (bool, error) { + b, err := wb.conn.ReadHoldingRegisters(victronRegEnabled, 1) + if err != nil { + return false, err + } + + return binary.BigEndian.Uint16(b) != 0, nil +} + +// Enable implements the api.Charger interface +func (wb *Victron) Enable(enable bool) error { + var u uint16 + if enable { + u = 1 + } + + _, err := wb.conn.WriteSingleRegister(victronRegEnabled, u) + return err +} + +// MaxCurrent implements the api.Charger interface +func (wb *Victron) MaxCurrent(current int64) error { + _, err := wb.conn.WriteSingleRegister(victronRegSetCurrent, uint16(current)) + return err +} + +var _ api.Meter = (*Victron)(nil) + +// CurrentPower implements the api.Meter interface +func (wb *Victron) CurrentPower() (float64, error) { + b, err := wb.conn.ReadHoldingRegisters(victronRegPower, 1) + if err != nil { + return 0, err + } + + return float64(binary.BigEndian.Uint16(b)), nil +} + +var _ api.ChargeRater = (*Victron)(nil) + +// ChargedEnergy implements the api.MeterEnergy interface +func (wb *Victron) ChargedEnergy() (float64, error) { + b, err := wb.conn.ReadHoldingRegisters(victronRegEnergy, 2) + if err != nil { + return 0, err + } + + return float64(binary.BigEndian.Uint32(b)) / 100, nil +} diff --git a/templates/definition/charger/victron.yaml b/templates/definition/charger/victron.yaml new file mode 100644 index 0000000000..529563d161 --- /dev/null +++ b/templates/definition/charger/victron.yaml @@ -0,0 +1,16 @@ +template: victron +products: + - brand: Victron + description: + generic: EV charging station +requirements: + description: + en: Charger has to be in manual mode and Modbus has to be configured for ID 100. + de: Wallbox muss sich im Modus "Manual" befinden und Modbus ID 100 konfiguriert sein. +params: + - name: modbus + choice: ["tcpip"] + id: 100 +render: | + type: victron + {{- include "modbus" . }} diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json index b9ff7a249a..6365188ae1 100644 --- a/templates/evcc.io/brands.json +++ b/templates/evcc.io/brands.json @@ -61,6 +61,7 @@ "TinkerForge", "Ubitricity", "Vestel", + "Victron", "Volkswagen", "Wallbe", "wallbox",