From bbaf8ab0926d857bfd4047671b4cb112cec1e320 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 11 Nov 2020 12:28:14 +0100 Subject: [PATCH] Support attached meters for Phoenix EV-CC controllers (#416) --- charger/phoenix-evcc.go | 81 ++++++++++++++++- charger/phoenix-evcc_decorators.go | 137 +++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 charger/phoenix-evcc_decorators.go diff --git a/charger/phoenix-evcc.go b/charger/phoenix-evcc.go index 6b2cc2244..c66c255d9 100644 --- a/charger/phoenix-evcc.go +++ b/charger/phoenix-evcc.go @@ -1,6 +1,7 @@ package charger import ( + "encoding/binary" "fmt" "github.com/andig/evcc/api" @@ -12,8 +13,13 @@ const ( phEVCCRegStatus = 24000 // Input phEVCCRegMaxCurrent = 22000 // Holding phEVCCRegEnable = 20000 // Coil + + phRegPower = 337 // power reading + phRegEnergy = 341 // energy reading ) +var phRegCurrents = []uint16{334, 335, 336} // current readings + // PhoenixEVCC is an api.ChargeController implementation for Phoenix EV-CC-AC1-M wallboxes. // It uses Modbus TCP to communicate with the wallbox at modbus client id 255. type PhoenixEVCC struct { @@ -25,18 +31,47 @@ func init() { registry.Add("phoenix-evcc", NewPhoenixEVCCFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p charger -f decoratePhoenixEVCC -b api.Charger -o phoenix-evcc_decorators -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" + // NewPhoenixEVCCFromConfig creates a Phoenix charger from generic config func NewPhoenixEVCCFromConfig(other map[string]interface{}) (api.Charger, error) { - cc := modbus.Settings{ID: 255} + cc := struct { + modbus.Settings `mapstructure:",squash"` + Meter struct { + Power, Energy, Currents bool + } + }{ + Settings: modbus.Settings{ + ID: 255, + }, + } + if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - return NewPhoenixEVCC(cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.ID) + wb, err := NewPhoenixEVCC(cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.ID) + + var currentPower func() (float64, error) + if cc.Meter.Power { + currentPower = wb.currentPower + } + + var totalEnergy func() (float64, error) + if cc.Meter.Energy { + totalEnergy = wb.totalEnergy + } + + var currents func() (float64, float64, float64, error) + if cc.Meter.Currents { + currents = wb.currents + } + + return decoratePhoenixEVCC(wb, currentPower, totalEnergy, currents), err } // NewPhoenixEVCC creates a Phoenix charger -func NewPhoenixEVCC(uri, device, comset string, baudrate int, id uint8) (api.Charger, error) { +func NewPhoenixEVCC(uri, device, comset string, baudrate int, id uint8) (*PhoenixEVCC, error) { log := util.NewLogger("evcc") conn, err := modbus.NewConnection(uri, device, comset, baudrate, true, id) @@ -98,3 +133,43 @@ func (wb *PhoenixEVCC) MaxCurrent(current int64) error { return err } + +func (wb *PhoenixEVCC) decodeReading(b []byte) float64 { + v := binary.BigEndian.Uint16(b) + return float64(v) +} + +// CurrentPower implements the Meter.CurrentPower interface +func (wb *PhoenixEVCC) currentPower() (float64, error) { + b, err := wb.conn.ReadHoldingRegisters(phRegPower, 1) + if err != nil { + return 0, err + } + + return wb.decodeReading(b), err +} + +// totalEnergy implements the Meter.TotalEnergy interface +func (wb *PhoenixEVCC) totalEnergy() (float64, error) { + b, err := wb.conn.ReadHoldingRegisters(phRegEnergy, 1) + if err != nil { + return 0, err + } + + return wb.decodeReading(b), err +} + +// currents implements the Meter.Currents interface +func (wb *PhoenixEVCC) currents() (float64, float64, float64, error) { + var currents []float64 + for _, regCurrent := range phRegCurrents { + b, err := wb.conn.ReadHoldingRegisters(regCurrent, 1) + if err != nil { + return 0, 0, 0, err + } + + currents = append(currents, wb.decodeReading(b)) + } + + return currents[0], currents[1], currents[2], nil +} diff --git a/charger/phoenix-evcc_decorators.go b/charger/phoenix-evcc_decorators.go new file mode 100644 index 000000000..6dc75c297 --- /dev/null +++ b/charger/phoenix-evcc_decorators.go @@ -0,0 +1,137 @@ +package charger + +// Code generated by github.com/andig/cmd/tools/decorate.go. DO NOT EDIT. + +import ( + "github.com/andig/evcc/api" +) + +func decoratePhoenixEVCC(base api.Charger, meter func() (float64, error), meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Charger { + switch { + case meter == nil && meterCurrent == nil && meterEnergy == nil: + return base + + case meter != nil && meterCurrent == nil && meterEnergy == nil: + return &struct { + api.Charger + api.Meter + }{ + Charger: base, + Meter: &decoratePhoenixEVCCMeterImpl{ + meter: meter, + }, + } + + case meter == nil && meterCurrent == nil && meterEnergy != nil: + return &struct { + api.Charger + api.MeterEnergy + }{ + Charger: base, + MeterEnergy: &decoratePhoenixEVCCMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meter != nil && meterCurrent == nil && meterEnergy != nil: + return &struct { + api.Charger + api.Meter + api.MeterEnergy + }{ + Charger: base, + Meter: &decoratePhoenixEVCCMeterImpl{ + meter: meter, + }, + MeterEnergy: &decoratePhoenixEVCCMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meter == nil && meterCurrent != nil && meterEnergy == nil: + return &struct { + api.Charger + api.MeterCurrent + }{ + Charger: base, + MeterCurrent: &decoratePhoenixEVCCMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case meter != nil && meterCurrent != nil && meterEnergy == nil: + return &struct { + api.Charger + api.Meter + api.MeterCurrent + }{ + Charger: base, + Meter: &decoratePhoenixEVCCMeterImpl{ + meter: meter, + }, + MeterCurrent: &decoratePhoenixEVCCMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case meter == nil && meterCurrent != nil && meterEnergy != nil: + return &struct { + api.Charger + api.MeterCurrent + api.MeterEnergy + }{ + Charger: base, + MeterCurrent: &decoratePhoenixEVCCMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decoratePhoenixEVCCMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meter != nil && meterCurrent != nil && meterEnergy != nil: + return &struct { + api.Charger + api.Meter + api.MeterCurrent + api.MeterEnergy + }{ + Charger: base, + Meter: &decoratePhoenixEVCCMeterImpl{ + meter: meter, + }, + MeterCurrent: &decoratePhoenixEVCCMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decoratePhoenixEVCCMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decoratePhoenixEVCCMeterImpl struct { + meter func() (float64, error) +} + +func (impl *decoratePhoenixEVCCMeterImpl) CurrentPower() (float64, error) { + return impl.meter() +} + +type decoratePhoenixEVCCMeterCurrentImpl struct { + meterCurrent func() (float64, float64, float64, error) +} + +func (impl *decoratePhoenixEVCCMeterCurrentImpl) Currents() (float64, float64, float64, error) { + return impl.meterCurrent() +} + +type decoratePhoenixEVCCMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decoratePhoenixEVCCMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +}