From 82ecb9f3120e134cb3d3bcefedc565bc1f9e288f Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 1 Dec 2023 09:49:59 +0500 Subject: [PATCH] Victron: add EVCS (#10959) Co-authored-by: Philipp Trenz --- charger/victron.go | 83 ++++++++++++++----- .../definition/charger/victron-evcs.yaml | 17 ++++ 2 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 templates/definition/charger/victron-evcs.yaml diff --git a/charger/victron.go b/charger/victron.go index 2462cd73c..5e9a58467 100644 --- a/charger/victron.go +++ b/charger/victron.go @@ -26,41 +26,77 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" + "github.com/spf13/cast" ) // Victron charger implementation type Victron struct { conn *modbus.Connection + regs victronRegs } -const ( - victronRegMode = 3815 - victronRegEnergy = 3816 - victronRegPower = 3821 - victronRegStatus = 3824 - victronRegSetCurrent = 3825 - victronRegEnabled = 3826 +type victronRegs struct { + Mode uint16 + Energy uint16 + Power uint16 + Status uint16 + SetCurrent uint16 + Enabled uint16 + isGX bool +} + +var ( + victronGX = victronRegs{ + Mode: 3815, + Energy: 3816, + Power: 3821, + Status: 3824, + SetCurrent: 3825, + Enabled: 3826, + isGX: true, + } + + victronEVCS = victronRegs{ + Mode: 5009, + Energy: 5021, + Power: 5014, + Status: 5015, + SetCurrent: 5016, + Enabled: 5010, + isGX: false, + } ) func init() { - registry.Add("victron", NewVictronFromConfig) + registry.Add("victron", NewVictronGXFromConfig) + registry.Add("victron-evcs", NewVictronEVCSFromConfig) +} + +// NewVictronGXFromConfig creates a ABB charger from generic config +func NewVictronGXFromConfig(other map[string]interface{}) (api.Charger, error) { + return NewVictronFromConfig(other, victronGX) +} + +// NewVictronEVCSFromConfig creates a ABB charger from generic config +func NewVictronEVCSFromConfig(other map[string]interface{}) (api.Charger, error) { + return NewVictronFromConfig(other, victronEVCS) } // NewVictronFromConfig creates a ABB charger from generic config -func NewVictronFromConfig(other map[string]interface{}) (api.Charger, error) { +func NewVictronFromConfig(other map[string]interface{}, regs victronRegs) (api.Charger, error) { cc := modbus.TcpSettings{ - ID: 100, + ID: cast.ToUint8(regs.isGX) * 100, } if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - return NewVictron(cc.URI, cc.ID) + return NewVictron(cc.URI, cc.ID, victronEVCS) } // NewVictron creates Victron charger -func NewVictron(uri string, slaveID uint8) (api.Charger, error) { +func NewVictron(uri string, slaveID uint8, regs victronRegs) (api.Charger, error) { conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID) if err != nil { return nil, err @@ -75,9 +111,10 @@ func NewVictron(uri string, slaveID uint8) (api.Charger, error) { wb := &Victron{ conn: conn, + regs: regs, } - b, err := wb.conn.ReadHoldingRegisters(victronRegMode, 1) + b, err := wb.conn.ReadHoldingRegisters(wb.regs.Mode, 1) if err != nil { return nil, err } @@ -91,7 +128,7 @@ func NewVictron(uri string, slaveID uint8) (api.Charger, error) { // Status implements the api.Charger interface func (wb *Victron) Status() (api.ChargeStatus, error) { - b, err := wb.conn.ReadHoldingRegisters(victronRegStatus, 1) + b, err := wb.conn.ReadHoldingRegisters(wb.regs.Status, 1) if err != nil { return api.StatusNone, err } @@ -100,7 +137,7 @@ func (wb *Victron) Status() (api.ChargeStatus, error) { switch u { case 0, 1, 2, 3: return api.ChargeStatusString(string('A' + rune(binary.BigEndian.Uint16(b)))) - case 5, 6: + case 5, 6, 21: return api.StatusB, nil default: return api.StatusNone, fmt.Errorf("invalid status: %d", u) @@ -109,7 +146,7 @@ func (wb *Victron) Status() (api.ChargeStatus, error) { // Enabled implements the api.Charger interface func (wb *Victron) Enabled() (bool, error) { - b, err := wb.conn.ReadHoldingRegisters(victronRegEnabled, 1) + b, err := wb.conn.ReadHoldingRegisters(wb.regs.Enabled, 1) if err != nil { return false, err } @@ -124,13 +161,13 @@ func (wb *Victron) Enable(enable bool) error { u = 1 } - _, err := wb.conn.WriteSingleRegister(victronRegEnabled, u) + _, err := wb.conn.WriteSingleRegister(wb.regs.Enabled, u) return err } // MaxCurrent implements the api.Charger interface func (wb *Victron) MaxCurrent(current int64) error { - _, err := wb.conn.WriteSingleRegister(victronRegSetCurrent, uint16(current)) + _, err := wb.conn.WriteSingleRegister(wb.regs.SetCurrent, uint16(current)) return err } @@ -138,7 +175,7 @@ var _ api.Meter = (*Victron)(nil) // CurrentPower implements the api.Meter interface func (wb *Victron) CurrentPower() (float64, error) { - b, err := wb.conn.ReadHoldingRegisters(victronRegPower, 1) + b, err := wb.conn.ReadHoldingRegisters(wb.regs.Power, 1) if err != nil { return 0, err } @@ -150,10 +187,14 @@ var _ api.ChargeRater = (*Victron)(nil) // ChargedEnergy implements the api.MeterEnergy interface func (wb *Victron) ChargedEnergy() (float64, error) { - b, err := wb.conn.ReadHoldingRegisters(victronRegEnergy, 2) + b, err := wb.conn.ReadHoldingRegisters(wb.regs.Energy, cast.ToUint16(wb.regs.isGX)) if err != nil { return 0, err } - return float64(binary.BigEndian.Uint32(b)) / 100, nil + if wb.regs.isGX { + return float64(binary.BigEndian.Uint32(b)) / 100, nil + } + + return float64(binary.BigEndian.Uint16(b)) / 100, nil } diff --git a/templates/definition/charger/victron-evcs.yaml b/templates/definition/charger/victron-evcs.yaml new file mode 100644 index 000000000..56c6f69d4 --- /dev/null +++ b/templates/definition/charger/victron-evcs.yaml @@ -0,0 +1,17 @@ +template: victron-evcs +products: + - brand: Victron + description: + generic: EV charging station +requirements: + evcc: ["sponsorship"] + description: + en: Enter the host of the charger (not the GX device) and ensure that the charger is in manual mode. + de: Trage den Host der Wallbox (nicht des GX-Geräts) ein und stelle sicher, dass die Wallbox sich im Modus "Manual" befindet. +params: + - name: modbus + choice: ["tcpip"] + id: 1 +render: | + type: victron-evcs + {{- include "modbus" . }}