EEBus: named scenario constants + fix MGCP scenario indices (#29701)

This commit is contained in:
andig 2026-05-06 14:52:54 +02:00 • committed by GitHub
parent 1b26d77b6c
commit 4b276a4d99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 116 additions and 15 deletions

View file

@ -316,7 +316,7 @@ func (c *EEBus) Enable(enable bool) error {
// send current charging power limits to the EV
func (c *EEBus) writeCurrentLimitData(evEntity spineapi.EntityRemoteInterface, current float64) error {
// check if the EVSE supports overload protection limits
if !c.cem.OpEV.IsScenarioAvailableAtEntity(evEntity, 1) {
if !c.cem.OpEV.IsScenarioAvailableAtEntity(evEntity, eebus.OPEVScenarioObligationLimit) {
return api.ErrNotAvailable
}
@ -362,7 +362,7 @@ func (c *EEBus) writeCurrentLimitData(evEntity spineapi.EntityRemoteInterface, c
// An active recommendation triggers the EV to charge with surplus energy.
// An inactive recommendation is equivalent to no recommendation existing.
func (c *EEBus) writeOscevLimits(evEntity spineapi.EntityRemoteInterface, current float64) {
if !c.cem.OscEV.IsScenarioAvailableAtEntity(evEntity, 1) {
if !c.cem.OscEV.IsScenarioAvailableAtEntity(evEntity, eebus.OSCEVScenarioRecommendationLimit) {
return
}
@ -430,7 +430,7 @@ func (c *EEBus) currentPower() (float64, error) {
// does the EVSE provide power data?
var powers []float64
if c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, 2) {
if c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, eebus.EVCEMScenarioPowerTotal) {
// is power data available for real? Elli Gen1 says it supports it, but doesn't provide any data
if powerData, err := c.cem.EvCem.PowerPerPhase(evEntity); err == nil {
powers = powerData
@ -438,7 +438,7 @@ func (c *EEBus) currentPower() (float64, error) {
}
// if no power data is available, and currents are reported to be supported, use currents
if len(powers) == 0 && c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, 1) {
if len(powers) == 0 && c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, eebus.EVCEMScenarioPowerPerPhase) {
// no power provided, calculate from current
if currents, err := c.cem.EvCem.CurrentPerPhase(evEntity); err == nil {
for _, current := range currents {
@ -462,7 +462,7 @@ func (c *EEBus) chargedEnergy() (float64, error) {
return 0, nil
}
if !c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, 3) {
if !c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, eebus.EVCEMScenarioEnergy) {
return 0, api.ErrNotAvailable
}
@ -482,7 +482,7 @@ func (c *EEBus) currents() (float64, float64, float64, error) {
}
// check if the EVSE supports currents
if !c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, 1) {
if !c.cem.EvCem.IsScenarioAvailableAtEntity(evEntity, eebus.EVCEMScenarioPowerPerPhase) {
return 0, 0, 0, api.ErrNotAvailable
}
@ -538,7 +538,7 @@ func (c *EEBus) Soc() (float64, error) {
return 0, api.ErrNotAvailable
}
if !c.cem.EvSoc.IsScenarioAvailableAtEntity(evEntity, 1) {
if !c.cem.EvSoc.IsScenarioAvailableAtEntity(evEntity, eebus.EVSOCScenarioStateOfCharge) {
return 0, api.ErrNotAvailable
}

View file

@ -29,6 +29,7 @@ type EEBus struct {
ma *eebus.MonitoringAppliance
eg *eebus.EnergyGuard
mm measurements
scenarios maScenarios
mu sync.Mutex
maEntity spineapi.EntityRemoteInterface
@ -36,6 +37,31 @@ type EEBus struct {
egLppEntity spineapi.EntityRemoteInterface
}
// maScenarios holds the spec scenario numbers for the active monitoring use case.
// MGCP and MPC use different scenario numbers for the same physical quantity, so
// IsScenarioAvailableAtEntity must be called with the per-UC value.
type maScenarios struct {
power uint
energy uint
currents uint
voltages uint
}
var (
mpcScenarios = maScenarios{
power: eebus.MPCScenarioPower,
energy: eebus.MPCScenarioEnergyConsumed,
currents: eebus.MPCScenarioCurrentPerPhase,
voltages: eebus.MPCScenarioVoltagePerPhase,
}
mgcpScenarios = maScenarios{
power: eebus.MGCPScenarioPower,
energy: eebus.MGCPScenarioEnergyConsumed,
currents: eebus.MGCPScenarioCurrentPerPhase,
voltages: eebus.MGCPScenarioVoltagePerPhase,
}
)
type measurements interface {
eebusapi.UseCaseBaseInterface
Power(entity spineapi.EntityRemoteInterface) (float64, error)
@ -76,10 +102,12 @@ func NewEEBus(ctx context.Context, ski, ip string, usage *templates.Usage) (api.
// Use MGCP only for explicit grid usage, MPC for everything else (default)
useCase := "mpc"
mm := measurements(ma.MaMPCInterface)
scenarios := mpcScenarios
if usage != nil && *usage == templates.UsageGrid {
useCase = "mgcp"
mm = ma.MaMGCPInterface
scenarios = mgcpScenarios
}
c := &EEBus{
@ -87,6 +115,7 @@ func NewEEBus(ctx context.Context, ski, ip string, usage *templates.Usage) (api.
ma: ma,
eg: eebus.Instance.EnergyGuard(),
mm: mm,
scenarios: scenarios,
connector: eebus.NewConnector(),
}
@ -144,13 +173,13 @@ func (c *EEBus) readValue(scenario uint, update func(entity spineapi.EntityRemot
var _ api.Meter = (*EEBus)(nil)
func (c *EEBus) CurrentPower() (float64, error) {
return c.readValue(1, c.mm.Power)
return c.readValue(c.scenarios.power, c.mm.Power)
}
var _ api.MeterEnergy = (*EEBus)(nil)
func (c *EEBus) TotalEnergy() (float64, error) {
return c.readValue(2, c.mm.EnergyConsumed)
return c.readValue(c.scenarios.energy, c.mm.EnergyConsumed)
}
func (c *EEBus) readPhases(scenario uint, update func(entity spineapi.EntityRemoteInterface) ([]float64, error)) (float64, float64, float64, error) {
@ -184,13 +213,13 @@ func (c *EEBus) readPhases(scenario uint, update func(entity spineapi.EntityRemo
var _ api.PhaseCurrents = (*EEBus)(nil)
func (c *EEBus) Currents() (float64, float64, float64, error) {
return c.readPhases(3, c.mm.CurrentPerPhase)
return c.readPhases(c.scenarios.currents, c.mm.CurrentPerPhase)
}
var _ api.PhaseVoltages = (*EEBus)(nil)
func (c *EEBus) Voltages() (float64, float64, float64, error) {
return c.readPhases(4, c.mm.VoltagePerPhase)
return c.readPhases(c.scenarios.voltages, c.mm.VoltagePerPhase)
}
var _ api.Dimmer = (*EEBus)(nil)
@ -200,7 +229,7 @@ func (c *EEBus) Dimmed() (bool, error) {
c.mu.Lock()
defer c.mu.Unlock()
limit, err := eebusReadValue(c.eg.EgLPCInterface, c.egLpcEntity, 1, c.eg.EgLPCInterface.ConsumptionLimit)
limit, err := eebusReadValue(c.eg.EgLPCInterface, c.egLpcEntity, eebus.LPCScenarioLimit, c.eg.EgLPCInterface.ConsumptionLimit)
if err != nil {
return false, err
}
@ -225,7 +254,7 @@ func (c *EEBus) Dim(dim bool) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.egLpcEntity == nil || !c.eg.EgLPCInterface.IsScenarioAvailableAtEntity(c.egLpcEntity, 1) {
if c.egLpcEntity == nil || !c.eg.EgLPCInterface.IsScenarioAvailableAtEntity(c.egLpcEntity, eebus.LPCScenarioLimit) {
return api.ErrNotAvailable
}
@ -244,7 +273,7 @@ func (c *EEBus) Curtailed() (bool, error) {
c.mu.Lock()
defer c.mu.Unlock()
limit, err := eebusReadValue(c.eg.EgLPPInterface, c.egLppEntity, 1, c.eg.EgLPPInterface.ProductionLimit)
limit, err := eebusReadValue(c.eg.EgLPPInterface, c.egLppEntity, eebus.LPPScenarioLimit, c.eg.EgLPPInterface.ProductionLimit)
if err != nil {
return false, err
}
@ -269,7 +298,7 @@ func (c *EEBus) Curtail(curtail bool) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.egLppEntity == nil || !c.eg.EgLPPInterface.IsScenarioAvailableAtEntity(c.egLppEntity, 1) {
if c.egLppEntity == nil || !c.eg.EgLPPInterface.IsScenarioAvailableAtEntity(c.egLppEntity, eebus.LPPScenarioLimit) {
return api.ErrNotAvailable
}

72
server/eebus/scenarios.go Normal file
View file

@ -0,0 +1,72 @@
package eebus
// EEBUS use case scenario numbers per the respective Use Case Technical Specifications.
//
// Spec scenario numbers diverge between use cases (e.g. MPC scenario 1 = active power,
// MGCP scenario 1 = power factor; MPC scenario 2 = energy, MGCP scenario 2 = active power).
// Passing the wrong number to IsScenarioAvailableAtEntity gates reads on the wrong feature.
//
// Each block mirrors the scenarios registered in the corresponding eebus-go usecase, which
// in turn matches the EEBus UC TS document.
// MGCP — Monitoring of Grid Connection Point (UC TS v1.0.0)
const (
MGCPScenarioPowerFactor uint = 1 // S1 power factor (cos phi)
MGCPScenarioPower uint = 2 // S2 active power per phase + total
MGCPScenarioEnergyFeedIn uint = 3 // S3 total feed-in energy
MGCPScenarioEnergyConsumed uint = 4 // S4 total consumed energy
MGCPScenarioCurrentPerPhase uint = 5 // S5 phase-specific currents
MGCPScenarioVoltagePerPhase uint = 6 // S6 phase-specific voltages
MGCPScenarioFrequency uint = 7 // S7 frequency
)
// MPC — Monitoring of Power Consumption (UC TS v1.0.0)
const (
MPCScenarioPower uint = 1 // S1 active power per phase + total
MPCScenarioEnergyConsumed uint = 2 // S2 total consumed energy
MPCScenarioCurrentPerPhase uint = 3 // S3 phase-specific currents
MPCScenarioVoltagePerPhase uint = 4 // S4 phase-specific voltages
MPCScenarioFrequency uint = 5 // S5 frequency
)
// LPC — Limitation of Power Consumption (UC TS v1.0.0). Same scenario layout for CS and EG roles.
const (
LPCScenarioLimit uint = 1 // S1 LoadControl: consumption limit
LPCScenarioFailsafe uint = 2 // S2 DeviceConfiguration: failsafe values
LPCScenarioHeartbeat uint = 3 // S3 DeviceDiagnosis: heartbeat
LPCScenarioElectricalConnection uint = 4 // S4 ElectricalConnection (optional)
)
// LPP — Limitation of Power Production (UC TS v1.0.0). Same scenario layout for CS and EG roles.
const (
LPPScenarioLimit uint = 1 // S1 LoadControl: production limit
LPPScenarioFailsafe uint = 2 // S2 DeviceConfiguration: failsafe values
LPPScenarioHeartbeat uint = 3 // S3 DeviceDiagnosis: heartbeat
LPPScenarioElectricalConnection uint = 4 // S4 ElectricalConnection (optional)
)
// OPEV — Overload Protection by EV Charging Current Curtailment (UC TS v1.0.1)
const (
OPEVScenarioObligationLimit uint = 1 // S1 LoadControl + ElectricalConnection
OPEVScenarioChargingState uint = 2 // S2 charging state
OPEVScenarioChargingPlan uint = 3 // S3 charging plan
)
// OSCEV — Optimization of Self-Consumption during EV Charging (UC TS v1.0.1)
const (
OSCEVScenarioRecommendationLimit uint = 1 // S1 LoadControl + ElectricalConnection
OSCEVScenarioChargingState uint = 2 // S2 charging state
OSCEVScenarioChargingPlan uint = 3 // S3 charging plan
)
// EVCEM — Measurement of Electricity during EV Charging (UC TS v1.0.1)
const (
EVCEMScenarioPowerPerPhase uint = 1 // S1 phase-specific active power + ElectricalConnection (currents)
EVCEMScenarioPowerTotal uint = 2 // S2 total active power only
EVCEMScenarioEnergy uint = 3 // S3 charging energy summary
)
// EVSOC — EV State of Charge (UC TS v1.0.0 RC1)
const (
EVSOCScenarioStateOfCharge uint = 1 // S1 state of charge
)