From 7f6eb33e7b838698994b401a2ca118f1810bcf7c Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:09:44 +0200 Subject: [PATCH] Phoenix: fix EM/EV-ETH measurand scaling (#21739) --- charger/phoenix-em-eth.go | 84 +++++++++++++-------------------------- charger/phoenix-ev-eth.go | 64 +++++++---------------------- 2 files changed, 42 insertions(+), 106 deletions(-) diff --git a/charger/phoenix-em-eth.go b/charger/phoenix-em-eth.go index 8f5964da8..901d75ec6 100644 --- a/charger/phoenix-em-eth.go +++ b/charger/phoenix-em-eth.go @@ -9,22 +9,19 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/modbus" "github.com/volkszaehler/mbmd/encoding" - "github.com/volkszaehler/mbmd/meters/rs485" ) const ( - phxEMEthRegStatus = 100 // Input - phxEMEthRegChargeTime = 102 // Input - phxEMEthRegVoltages = 108 // Input - phxEMEthRegCurrents = 114 // Input - phxEMEthRegPower = 120 // Input - phxEMEthRegEnergy = 128 // Input - phxEMEthRegMaxCurrent = 300 // Holding - phxEMEthRegVoltagesScale = 352 // Holding - phxEMEthRegCurrentsScale = 358 // Holding - phxEMEthRegPowerScale = 364 // Holding - phxEMEthRegEnergyScale = 372 // Holding - phxEMEthRegEnable = 400 // Coil + phxEMEthRegStatus = 100 // Input + phxEMEthRegChargeTime = 102 // Input [s] + phxEMEthRegVoltages = 108 // Input [V] + phxEMEthRegCurrents = 114 // Input [A] + phxEMEthRegPower = 120 // Input [kW]! + phxEMEthRegEnergy = 128 // Input [kWh] + phxEMEthRegMaxCurrent = 300 // Holding [A] + phxEMEthRegEnable = 400 // Coil + + phxEMEthSF float64 = 0.01 // scale factor from register values to real values (2 decimal places) ) // PhoenixEMEth is an api.Charger implementation for Phoenix EM-CP-PP-ETH wallboxes. @@ -40,7 +37,7 @@ func init() { //go:generate go tool decorate -f decoratePhoenixEMEth -b *PhoenixEMEth -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.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" // NewPhoenixEMEthFromConfig creates a Phoenix charger from generic config -func NewPhoenixEMEthFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) { +func NewPhoenixEMEthFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) { cc := modbus.TcpSettings{ ID: 180, } @@ -141,79 +138,52 @@ func (wb *PhoenixEMEth) ChargeDuration() (time.Duration, error) { return 0, err } - return time.Duration(encoding.Int32LswFirst(b)) * time.Second, nil + return time.Duration(encoding.Uint32LswFirst(b)) * time.Second, nil } // CurrentPower implements the api.Meter interface func (wb *PhoenixEMEth) currentPower() (float64, error) { - value, err := wb.readScaledValue(phxEMEthRegPower, phxEMEthRegPowerScale) + b, err := wb.conn.ReadInputRegisters(phxEMEthRegPower, 2) if err != nil { return 0, err } - return value, nil + return float64(encoding.Int32LswFirst(b)*1e3) * phxEMEthSF, nil } +// totalEnergy implements the api.MeterEnergy interface func (wb *PhoenixEMEth) totalEnergy() (float64, error) { - value, err := wb.readScaledValue(phxEMEthRegEnergy, phxEMEthRegEnergyScale) + b, err := wb.conn.ReadInputRegisters(phxEMEthRegEnergy, 2) if err != nil { return 0, err } - return value, nil + return float64(encoding.Uint32LswFirst(b)) * phxEMEthSF, nil } // currents implements the api.PhaseCurrents interface func (wb *PhoenixEMEth) currents() (float64, float64, float64, error) { - return wb.getPhaseValues(phxEMEthRegCurrents, phxEMEthRegCurrentsScale) + return wb.getPhaseValues(phxEMEthRegCurrents) } // voltages implements the api.PhaseVoltages interface func (wb *PhoenixEMEth) voltages() (float64, float64, float64, error) { - return wb.getPhaseValues(phxEMEthRegVoltages, phxEMEthRegVoltagesScale) + return wb.getPhaseValues(phxEMEthRegVoltages) } -func (wb *PhoenixEMEth) readScaledValue(regValue, regScale uint16) (float64, error) { - bValue, err := wb.conn.ReadInputRegisters(regValue, 2) - if err != nil { - return 0, err - } - bScale, err := wb.conn.ReadHoldingRegisters(regScale, 2) - if err != nil { - return 0, err - } - scale := rs485.RTUIeee754ToFloat64Swapped(bScale) / 1000.0 - - return float64(encoding.Int32LswFirst(bValue)) * scale, nil -} - -func (wb *PhoenixEMEth) readScaledValues(regValue, regScale uint16) ([]float64, error) { +// getPhaseValues returns 3 sequential phase values +func (wb *PhoenixEMEth) getPhaseValues(reg uint16) (float64, float64, float64, error) { const count = 3 - - bValue, err := wb.conn.ReadInputRegisters(regValue, uint16(2*count)) - if err != nil { - return nil, err - } - bScale, err := wb.conn.ReadHoldingRegisters(regScale, uint16(2*count)) - if err != nil { - return nil, err - } - - res := make([]float64, count) - for i := 0; i < count; i++ { - scale := rs485.RTUIeee754ToFloat64Swapped(bScale[4*i:]) / 1000.0 - res[i] = float64(encoding.Int32LswFirst(bValue[4*i:])) * scale - } - - return res, nil -} - -func (wb *PhoenixEMEth) getPhaseValues(regValue, regScale uint16) (float64, float64, float64, error) { - res, err := wb.readScaledValues(regValue, regScale) + b, err := wb.conn.ReadInputRegisters(reg, 2*count) if err != nil { return 0, 0, 0, err } + var res [count]float64 + for i := range res { + res[i] = float64(encoding.Int32LswFirst(b[4*i:])) * phxEMEthSF + } + return res[0], res[1], res[2], nil } diff --git a/charger/phoenix-ev-eth.go b/charger/phoenix-ev-eth.go index c3d195774..4839fcfe5 100644 --- a/charger/phoenix-ev-eth.go +++ b/charger/phoenix-ev-eth.go @@ -2,7 +2,7 @@ package charger // LICENSE -// Copyright (c) 2023-2025 premultiply +// Copyright (c) 2023 premultiply // This module is NOT covered by the MIT license. All rights reserved. @@ -32,7 +32,6 @@ import ( "github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/sponsor" "github.com/volkszaehler/mbmd/encoding" - "github.com/volkszaehler/mbmd/meters/rs485" ) type PhoenixEVEth struct { @@ -50,10 +49,6 @@ const ( phxRegEnergy = 128 // Input phxRegChargedEnergy = 132 // Input phxRegFirmwareWallbe = 149 // Input - phxRegVoltagesScale = 352 // Holding - phxRegCurrentsScale = 358 // Holding - phxRegPowerScale = 364 // Holding - phxRegEnergyScale = 372 // Holding phxRegEnable = 400 // Coil phxRegCardEnabled = 419 // Coil phxRegMaxCurrent = 528 // Holding @@ -189,12 +184,12 @@ func (wb *PhoenixEVEth) maxCurrentMillis(current float64) error { // currentPower implements the api.Meter interface func (wb *PhoenixEVEth) currentPower() (float64, error) { - value, err := wb.readScaledValue(phxRegPower, phxRegPowerScale) + b, err := wb.conn.ReadInputRegisters(phxRegPower, 2) if err != nil { return 0, err } - return value, nil + return float64(encoding.Int32LswFirst(b)), nil } // totalEnergy implements the api.MeterEnergy interface @@ -208,66 +203,37 @@ func (wb *PhoenixEVEth) totalEnergy() (float64, error) { return float64(encoding.Uint64LswFirst(b)) / 1e3, nil } - value, err := wb.readScaledValue(phxRegEnergy, phxRegEnergyScale) + b, err := wb.conn.ReadHoldingRegisters(phxRegEnergyWh, 2) if err != nil { return 0, err } - return value, nil + return float64(encoding.Uint32LswFirst(b)) / 1e3, nil } // currents implements the api.PhaseCurrents interface func (wb *PhoenixEVEth) currents() (float64, float64, float64, error) { - return wb.getPhaseValues(phxRegCurrents, phxRegCurrentsScale) + return wb.getPhaseValues(phxRegCurrents) } // voltages implements the api.PhaseVoltages interface func (wb *PhoenixEVEth) voltages() (float64, float64, float64, error) { - return wb.getPhaseValues(phxRegVoltages, phxRegVoltagesScale) -} - -func (wb *PhoenixEVEth) readScaledValue(regValue, regScale uint16) (float64, error) { - bValue, err := wb.conn.ReadInputRegisters(regValue, 2) - if err != nil { - return 0, err - } - bScale, err := wb.conn.ReadHoldingRegisters(regScale, 2) - if err != nil { - return 0, err - } - scale := rs485.RTUIeee754ToFloat64Swapped(bScale) / 1000.0 - - return float64(encoding.Int32LswFirst(bValue)) * scale, nil -} - -func (wb *PhoenixEVEth) readScaledValues(regValue, regScale uint16) ([]float64, error) { - const count = 3 - - bValue, err := wb.conn.ReadInputRegisters(regValue, uint16(2*count)) - if err != nil { - return nil, err - } - bScale, err := wb.conn.ReadHoldingRegisters(regScale, uint16(2*count)) - if err != nil { - return nil, err - } - - res := make([]float64, count) - for i := 0; i < count; i++ { - scale := rs485.RTUIeee754ToFloat64Swapped(bScale[4*i:]) / 1000.0 - res[i] = float64(encoding.Int32LswFirst(bValue[4*i:])) * scale - } - - return res, nil + return wb.getPhaseValues(phxRegVoltages) } // getPhaseValues returns 3 sequential phase values -func (wb *PhoenixEVEth) getPhaseValues(regValue, regScale uint16) (float64, float64, float64, error) { - res, err := wb.readScaledValues(regValue, regScale) +func (wb *PhoenixEVEth) getPhaseValues(reg uint16) (float64, float64, float64, error) { + const count = 3 + b, err := wb.conn.ReadInputRegisters(reg, 2*count) if err != nil { return 0, 0, 0, err } + var res [count]float64 + for i := range res { + res[i] = float64(encoding.Int32LswFirst(b[4*i:])) + } + return res[0], res[1], res[2], nil }