Cfos: add attached meter (#15192)

This commit is contained in:
andig 2024-08-05 17:47:17 +02:00 • committed by GitHub
parent 77a3cf2861
commit a19782ef11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 310 additions and 10 deletions

View file

@ -17,7 +17,13 @@ const (
cfosRegMaxCurrent = 8093
cfosRegEnable = 8094
cfosRegLastRfid = 8096
cfosRegMeter = 8112
cfosRegSolarEnabled = 8113
cfosRegMeterFlags = 8057
cfosRegEnergy = 8058 // 4 rw Aktiver Import [Wh]
cfosRegPower = 8062 // 2 r Aktive Leistung [W]
cfosRegCurrents = 8064 // 2 r Momentaner Strom L1 [0.1 A]
)
// CfosPowerBrain is an charger implementation for cFos PowerBrain wallboxes.
@ -31,7 +37,7 @@ func init() {
registry.Add("cfos", NewCfosPowerBrainFromConfig)
}
//go:generate go run ../cmd/tools/decorate.go -f decorateCfos -b *CfosPowerBrain -r api.Charger -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
//go:generate go run ../cmd/tools/decorate.go -f decorateCfos -b *CfosPowerBrain -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
// NewCfosPowerBrainFromConfig creates a cFos charger from generic config
func NewCfosPowerBrainFromConfig(other map[string]interface{}) (api.Charger, error) {
@ -70,15 +76,28 @@ func NewCfosPowerBrain(uri string, id uint8) (api.Charger, error) {
conn: conn,
}
// decorate meter
var (
power func() (float64, error)
energy func() (float64, error)
currents func() (float64, float64, float64, error)
)
if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeter, 1); err == nil && binary.BigEndian.Uint16(b) != 0 {
power = wb.currentPower
energy = wb.totalEnergy
if b, err := wb.conn.ReadHoldingRegisters(cfosRegMeterFlags, 1); err == nil && binary.BigEndian.Uint16(b) != 0 {
currents = wb.currents
}
}
// decorate phases
var phases1p3p func(int) error
b, err := wb.conn.ReadHoldingRegisters(cfosRegSolarEnabled, 1)
if err == nil && binary.BigEndian.Uint16(b)&(1<<8) != 0 {
if b, err := wb.conn.ReadHoldingRegisters(cfosRegSolarEnabled, 1); err == nil && binary.BigEndian.Uint16(b)&(1<<8) != 0 {
phases1p3p = wb.phases1p3p
}
return decorateCfos(wb, phases1p3p), nil
return decorateCfos(wb, power, energy, currents, phases1p3p), nil
}
// Status implements the api.Charger interface
@ -141,6 +160,41 @@ func (wb *CfosPowerBrain) MaxCurrentMillis(current float64) error {
return err
}
// currentPower implements the api.Meter interface
func (wb *CfosPowerBrain) currentPower() (float64, error) {
b, err := wb.conn.ReadHoldingRegisters(cfosRegPower, 2)
if err != nil {
return 0, err
}
return float64(binary.BigEndian.Uint32(b)), nil
}
// totalEnergy implements the api.MeterEnergy interface
func (wb *CfosPowerBrain) totalEnergy() (float64, error) {
b, err := wb.conn.ReadHoldingRegisters(cfosRegEnergy, 4)
if err != nil {
return 0, err
}
return float64(binary.BigEndian.Uint64(b)) / 1e3, nil
}
// currents implements the api.PhaseCurrents interface
func (wb *CfosPowerBrain) currents() (float64, float64, float64, error) {
b, err := wb.conn.ReadHoldingRegisters(cfosRegCurrents, 6)
if err != nil {
return 0, 0, 0, err
}
var res [3]float64
for i := range res {
res[i] = float64(binary.BigEndian.Uint32(b[4*i:])) / 10
}
return res[0], res[1], res[2], nil
}
// phases1p3p implements the api.PhaseSwitcher interface
func (wb *CfosPowerBrain) phases1p3p(phases int) error {
if phases == 3 {

View file

@ -6,12 +6,109 @@ import (
"github.com/evcc-io/evcc/api"
)
func decorateCfos(base *CfosPowerBrain, phaseSwitcher func(int) error) api.Charger {
func decorateCfos(base *CfosPowerBrain, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseSwitcher func(int) error) api.Charger {
switch {
case phaseSwitcher == nil:
case meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return base
case phaseSwitcher != nil:
case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.Meter
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.MeterEnergy
}{
CfosPowerBrain: base,
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.Meter
api.MeterEnergy
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.PhaseCurrents
}{
CfosPowerBrain: base,
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.Meter
api.PhaseCurrents
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.MeterEnergy
api.PhaseCurrents
}{
CfosPowerBrain: base,
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*CfosPowerBrain
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.PhaseSwitcher
@ -21,11 +118,160 @@ func decorateCfos(base *CfosPowerBrain, phaseSwitcher func(int) error) api.Charg
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.Meter
api.PhaseSwitcher
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.MeterEnergy
api.PhaseSwitcher
}{
CfosPowerBrain: base,
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.Meter
api.MeterEnergy
api.PhaseSwitcher
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.PhaseCurrents
api.PhaseSwitcher
}{
CfosPowerBrain: base,
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.Meter
api.PhaseCurrents
api.PhaseSwitcher
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
CfosPowerBrain: base,
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*CfosPowerBrain
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
CfosPowerBrain: base,
Meter: &decorateCfosMeterImpl{
meter: meter,
},
MeterEnergy: &decorateCfosMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateCfosPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateCfosPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
}
return nil
}
type decorateCfosMeterImpl struct {
meter func() (float64, error)
}
func (impl *decorateCfosMeterImpl) CurrentPower() (float64, error) {
return impl.meter()
}
type decorateCfosMeterEnergyImpl struct {
meterEnergy func() (float64, error)
}
func (impl *decorateCfosMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}
type decorateCfosPhaseCurrentsImpl struct {
phaseCurrents func() (float64, float64, float64, error)
}
func (impl *decorateCfosPhaseCurrentsImpl) Currents() (float64, float64, float64, error) {
return impl.phaseCurrents()
}
type decorateCfosPhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}

View file

@ -7,10 +7,10 @@ capabilities: ["mA", "rfid", "1p3p"]
requirements:
description:
de: |
Der Zähler- falls vorhanden- muss separat als Ladezähler konfiguriert werden.
Ein evtl. vorhandener S0 Zähler muss separat als Ladezähler konfiguriert werden.
Phasenumschaltung bietet nur die Solar-Variante und muss vom Anwender freigeschaltet werden.
en: |
The meter- if present- must be configured separately as charge meter.
S0 meters must be configured separately as charge meter.
Phase switching is only available with the Solar variant and must be enabled by the user.
evcc: ["sponsorship"]
params: