diff --git a/charger/cfos.go b/charger/cfos.go index dd7a9dc39..a8e19b284 100644 --- a/charger/cfos.go +++ b/charger/cfos.go @@ -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 { diff --git a/charger/cfos_decorators.go b/charger/cfos_decorators.go index d527089ec..54ab8acdd 100644 --- a/charger/cfos_decorators.go +++ b/charger/cfos_decorators.go @@ -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 } diff --git a/templates/definition/charger/cfos.yaml b/templates/definition/charger/cfos.yaml index 6fa06090b..e66ff82f3 100644 --- a/templates/definition/charger/cfos.yaml +++ b/templates/definition/charger/cfos.yaml @@ -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: