From 45cab34ce94de22198ede72c48975575bf54c221 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 10 Jun 2026 22:16:58 +0200 Subject: [PATCH] HEMS: make Dimmed/Curtailed tri-state (once more) (#30712) --- api/api.go | 4 ++-- api/mock.go | 8 ++++---- assets/js/types/evcc.ts | 4 ++-- assets/js/views/Config.vue | 4 ++-- cmd/root.go | 4 ++-- core/helper.go | 4 ++-- hems/eebus/eebus.go | 8 ++++---- hems/eebus/eebus_test.go | 4 ++-- hems/fnn/fnn.go | 12 ++++++------ hems/relay/relay.go | 11 ++++++----- 10 files changed, 32 insertions(+), 31 deletions(-) diff --git a/api/api.go b/api/api.go index 4bfeae8e4..7bb11c8b2 100644 --- a/api/api.go +++ b/api/api.go @@ -287,8 +287,8 @@ type Circuit interface { // HEMS exposes the runtime state of the home energy management system. type HEMS interface { SetUpdated(func()) - Dimmed() bool - Curtailed() bool + Dimmed() *bool // nil = no statement + Curtailed() *bool // nil = no statement MaxConsumptionPower() float64 // 0 = no limit MaxProductionPower() *float64 // nil = no limit } diff --git a/api/mock.go b/api/mock.go index 7042443d3..549bffcca 100644 --- a/api/mock.go +++ b/api/mock.go @@ -1173,10 +1173,10 @@ func (m *MockHEMS) EXPECT() *MockHEMSMockRecorder { } // Curtailed mocks base method. -func (m *MockHEMS) Curtailed() bool { +func (m *MockHEMS) Curtailed() *bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Curtailed") - ret0, _ := ret[0].(bool) + ret0, _ := ret[0].(*bool) return ret0 } @@ -1187,10 +1187,10 @@ func (mr *MockHEMSMockRecorder) Curtailed() *gomock.Call { } // Dimmed mocks base method. -func (m *MockHEMS) Dimmed() bool { +func (m *MockHEMS) Dimmed() *bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Dimmed") - ret0, _ := ret[0].(bool) + ret0, _ := ret[0].(*bool) return ret0 } diff --git a/assets/js/types/evcc.ts b/assets/js/types/evcc.ts index 9b5de09ea..f2c07c9c0 100644 --- a/assets/js/types/evcc.ts +++ b/assets/js/types/evcc.ts @@ -42,8 +42,8 @@ export interface HemsConfig { } export interface HemsStatus { - dimmed: boolean; - curtailed: boolean; + dimmed?: boolean; + curtailed?: boolean; maxConsumptionPower?: number; maxProductionPower?: number; } diff --git a/assets/js/views/Config.vue b/assets/js/views/Config.vue index d886e2c5f..be2c1cec2 100644 --- a/assets/js/views/Config.vue +++ b/assets/js/views/Config.vue @@ -805,7 +805,7 @@ export default defineComponent({ value: status.maxConsumptionPower, warning: true, }; - } else { + } else if (status.dimmed !== undefined) { result["dimmed"] = { value: status.dimmed }; } if (status.curtailed && status.maxProductionPower !== undefined) { @@ -813,7 +813,7 @@ export default defineComponent({ value: status.maxProductionPower, warning: true, }; - } else { + } else if (status.curtailed !== undefined) { result["curtailed"] = { value: status.curtailed }; } diff --git a/cmd/root.go b/cmd/root.go index 0f89355df..c3270ea00 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -350,8 +350,8 @@ func runRoot(cmd *cobra.Command, args []string) { Config: conf.HEMS.Redacted(), YamlSource: yamlSource.hems, Status: struct { - Dimmed bool `json:"dimmed"` - Curtailed bool `json:"curtailed"` + Dimmed *bool `json:"dimmed,omitempty"` + Curtailed *bool `json:"curtailed,omitempty"` MaxConsumptionPower float64 `json:"maxConsumptionPower,omitempty"` MaxProductionPower *float64 `json:"maxProductionPower,omitempty"` }{ diff --git a/core/helper.go b/core/helper.go index 5fa7e5002..82c9085fe 100644 --- a/core/helper.go +++ b/core/helper.go @@ -83,7 +83,7 @@ func hemsDimmed(hems api.HEMS) *bool { return nil } - return new(hems.Dimmed()) + return hems.Dimmed() } // hemsCurtailed returns the HEMS curtail status, nil-safe @@ -92,5 +92,5 @@ func hemsCurtailed(hems api.HEMS) *bool { return nil } - return new(hems.Curtailed()) + return hems.Curtailed() } diff --git a/hems/eebus/eebus.go b/hems/eebus/eebus.go index cb05e4c60..259c95e95 100644 --- a/hems/eebus/eebus.go +++ b/hems/eebus/eebus.go @@ -294,17 +294,17 @@ func (c *EEBus) setProductionLimit(limit float64, active bool) { var _ api.HEMS = (*EEBus)(nil) // Dimmed implements api.HEMS, derived from consumptionLimitActivated. -func (c *EEBus) Dimmed() bool { +func (c *EEBus) Dimmed() *bool { c.mux.RLock() defer c.mux.RUnlock() - return !c.consumptionLimitActivated.IsZero() + return new(!c.consumptionLimitActivated.IsZero()) } // Curtailed implements api.HEMS, derived from productionLimitActivated. -func (c *EEBus) Curtailed() bool { +func (c *EEBus) Curtailed() *bool { c.mux.RLock() defer c.mux.RUnlock() - return !c.productionLimitActivated.IsZero() + return new(!c.productionLimitActivated.IsZero()) } // MaxConsumptionPower implements api.HEMS, returning the consumption cap diff --git a/hems/eebus/eebus_test.go b/hems/eebus/eebus_test.go index 36abf0f1d..7374a9f32 100644 --- a/hems/eebus/eebus_test.go +++ b/hems/eebus/eebus_test.go @@ -49,14 +49,14 @@ func newTestEEBus(t *testing.T) *EEBus { // assertConsumptionLimit checks the HEMS consumption state through the api.HEMS surface. func assertConsumptionLimit(t *testing.T, c *EEBus, limit float64) { t.Helper() - assert.Equal(t, limit > 0, c.Dimmed()) + assert.Equal(t, new(limit > 0), c.Dimmed()) assert.Equal(t, limit, c.MaxConsumptionPower()) } // assertProductionLimit checks the HEMS production state through the api.HEMS surface. func assertProductionLimit(t *testing.T, c *EEBus, active bool) { t.Helper() - assert.Equal(t, active, c.Curtailed()) + assert.Equal(t, new(active), c.Curtailed()) } // TestRun_HeartbeatLost_EntersFailsafe verifies the LPC-911/LPP-911 transition: diff --git a/hems/fnn/fnn.go b/hems/fnn/fnn.go index 868a8b34a..35c15f75c 100644 --- a/hems/fnn/fnn.go +++ b/hems/fnn/fnn.go @@ -223,25 +223,25 @@ func (c *Fnn) setConsumptionLimit(limit float64) error { var _ api.HEMS = (*Fnn)(nil) // Dimmed implements api.HEMS. -func (c *Fnn) Dimmed() bool { +func (c *Fnn) Dimmed() *bool { if c.w4 == nil { - return false + return nil } c.mu.Lock() defer c.mu.Unlock() - return c.consumptionLimit > 0 + return new(c.consumptionLimit > 0) } // Curtailed implements api.HEMS. -func (c *Fnn) Curtailed() bool { +func (c *Fnn) Curtailed() *bool { if c.w3 == nil { - return false + return nil } c.mu.Lock() defer c.mu.Unlock() - return c.productionLimit != nil + return new(c.productionLimit != nil) } // MaxConsumptionPower implements api.HEMS. diff --git a/hems/relay/relay.go b/hems/relay/relay.go index c4b2db07d..9db2dfc93 100644 --- a/hems/relay/relay.go +++ b/hems/relay/relay.go @@ -137,15 +137,16 @@ func (c *Relay) setConsumptionLimit(limit float64) error { var _ api.HEMS = (*Relay)(nil) // Dimmed implements api.HEMS, derived from the active consumption limit. -func (c *Relay) Dimmed() bool { +func (c *Relay) Dimmed() *bool { c.mu.Lock() defer c.mu.Unlock() - return c.limit != nil + return new(c.limit != nil) } -// Curtailed implements api.HEMS. Relay does not curtail production. -func (c *Relay) Curtailed() bool { - return false +// Curtailed implements api.HEMS. Relay does not curtail production and +// hence makes no statement. +func (c *Relay) Curtailed() *bool { + return nil } // MaxConsumptionPower implements api.HEMS, returning the active wattage cap.