Support attached meters for Phoenix EV-CC controllers (#416)

This commit is contained in:
andig 2020-11-11 12:28:14 +01:00
parent c509b1c44e
commit bbaf8ab092
2 changed files with 215 additions and 3 deletions

View file

@ -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
}

View file

@ -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()
}