Support attached meters for Phoenix EM-CP controllers (#417)

This commit is contained in:
andig 2020-11-11 12:29:31 +01:00 • committed by GitHub
parent bbaf8ab092
commit 0ebae4544a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 209 additions and 4 deletions

View file

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

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