Phoenix: fix EM/EV-ETH measurand scaling (#21739)

This commit is contained in:
premultiply 2025-06-12 09:09:44 +02:00 • committed by GitHub
parent 9b734fbf0e
commit 7f6eb33e7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 42 additions and 106 deletions

View file

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

View file

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