diff --git a/charger/phoenix-emcp.go b/charger/phoenix-emcp.go index dc0112805..2be9b5a06 100644 --- a/charger/phoenix-emcp.go +++ b/charger/phoenix-emcp.go @@ -1,6 +1,7 @@ package charger import ( + "encoding/binary" "fmt" "net" "time" @@ -15,8 +16,13 @@ const ( phEMCPRegChargeTime = 102 // Input phEMCPRegMaxCurrent = 300 // Holding phEMCPRegEnable = 400 // Coil + + phEMCPRegPower = 120 // power reading + phEMCPRegEnergy = 128 // energy reading ) +var phEMCPRegCurrents = []uint16{114, 116, 118} // current readings + // PhoenixEMCP is an api.ChargeController implementation for Phoenix EM-CP-PP-ETH wallboxes. // It uses Modbus TCP to communicate with the wallbox at modbus client id 180. type PhoenixEMCP struct { @@ -28,11 +34,16 @@ func init() { registry.Add("phoenix-emcp", NewPhoenixEMCPFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p charger -f decoratePhoenixEMCP -b api.Charger -o phoenix-emcp_decorators -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" + // NewPhoenixEMCPFromConfig creates a Phoenix charger from generic config func NewPhoenixEMCPFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { - URI string - ID uint8 + URI string + ID uint8 + Meter struct { + Power, Energy, Currents bool + } }{ URI: "192.168.0.8:502", // default ID: 180, // default @@ -46,11 +57,28 @@ func NewPhoenixEMCPFromConfig(other map[string]interface{}) (api.Charger, error) return nil, fmt.Errorf("missing or invalid phoenix uri: %s", cc.URI) } - return NewPhoenixEMCP(cc.URI, cc.ID) + wb, err := NewPhoenixEMCP(cc.URI, 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 decoratePhoenixEMCP(wb, currentPower, totalEnergy, currents), err } // NewPhoenixEMCP creates a Phoenix charger -func NewPhoenixEMCP(uri string, id uint8) (api.Charger, error) { +func NewPhoenixEMCP(uri string, id uint8) (*PhoenixEMCP, error) { log := util.NewLogger("emcp") conn, err := modbus.NewConnection(uri, "", "", 0, false, id) @@ -125,3 +153,43 @@ func (wb *PhoenixEMCP) ChargingTime() (time.Duration, error) { secs := uint64(b[3])<<16 | uint64(b[2])<<24 | uint64(b[1]) | uint64(b[0])<<8 return time.Duration(time.Duration(secs) * time.Second), nil } + +func (wb *PhoenixEMCP) decodeReading(b []byte) float64 { + v := binary.BigEndian.Uint32(b) + return float64(v) +} + +// CurrentPower implements the Meter.CurrentPower interface +func (wb *PhoenixEMCP) currentPower() (float64, error) { + b, err := wb.conn.ReadInputRegisters(phEMCPRegPower, 2) + if err != nil { + return 0, err + } + + return wb.decodeReading(b), err +} + +// totalEnergy implements the Meter.TotalEnergy interface +func (wb *PhoenixEMCP) totalEnergy() (float64, error) { + b, err := wb.conn.ReadInputRegisters(phEMCPRegEnergy, 2) + if err != nil { + return 0, err + } + + return wb.decodeReading(b), err +} + +// currents implements the Meter.Currents interface +func (wb *PhoenixEMCP) currents() (float64, float64, float64, error) { + var currents []float64 + for _, regCurrent := range phEMCPRegCurrents { + b, err := wb.conn.ReadInputRegisters(regCurrent, 2) + 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-emcp_decorators.go b/charger/phoenix-emcp_decorators.go new file mode 100644 index 000000000..9cf49d231 --- /dev/null +++ b/charger/phoenix-emcp_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 decoratePhoenixEMCP(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: &decoratePhoenixEMCPMeterImpl{ + meter: meter, + }, + } + + case meter == nil && meterCurrent == nil && meterEnergy != nil: + return &struct { + api.Charger + api.MeterEnergy + }{ + Charger: base, + MeterEnergy: &decoratePhoenixEMCPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meter != nil && meterCurrent == nil && meterEnergy != nil: + return &struct { + api.Charger + api.Meter + api.MeterEnergy + }{ + Charger: base, + Meter: &decoratePhoenixEMCPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decoratePhoenixEMCPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meter == nil && meterCurrent != nil && meterEnergy == nil: + return &struct { + api.Charger + api.MeterCurrent + }{ + Charger: base, + MeterCurrent: &decoratePhoenixEMCPMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case meter != nil && meterCurrent != nil && meterEnergy == nil: + return &struct { + api.Charger + api.Meter + api.MeterCurrent + }{ + Charger: base, + Meter: &decoratePhoenixEMCPMeterImpl{ + meter: meter, + }, + MeterCurrent: &decoratePhoenixEMCPMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case meter == nil && meterCurrent != nil && meterEnergy != nil: + return &struct { + api.Charger + api.MeterCurrent + api.MeterEnergy + }{ + Charger: base, + MeterCurrent: &decoratePhoenixEMCPMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decoratePhoenixEMCPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meter != nil && meterCurrent != nil && meterEnergy != nil: + return &struct { + api.Charger + api.Meter + api.MeterCurrent + api.MeterEnergy + }{ + Charger: base, + Meter: &decoratePhoenixEMCPMeterImpl{ + meter: meter, + }, + MeterCurrent: &decoratePhoenixEMCPMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decoratePhoenixEMCPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decoratePhoenixEMCPMeterImpl struct { + meter func() (float64, error) +} + +func (impl *decoratePhoenixEMCPMeterImpl) CurrentPower() (float64, error) { + return impl.meter() +} + +type decoratePhoenixEMCPMeterCurrentImpl struct { + meterCurrent func() (float64, float64, float64, error) +} + +func (impl *decoratePhoenixEMCPMeterCurrentImpl) Currents() (float64, float64, float64, error) { + return impl.meterCurrent() +} + +type decoratePhoenixEMCPMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decoratePhoenixEMCPMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +}