From e702924fe39effc91a544a506e764b782a04df05 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 29 Dec 2025 17:29:27 +0100 Subject: [PATCH] =?UTF-8?q?Add=20EEG=20=C2=A79=20curtailment=20api=20(#258?= =?UTF-8?q?87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api.go | 14 ++++++++++++-- api/mock.go | 26 ++++++++++++++++++++++++++ core/circuit/circuit.go | 28 +++++++++++++++++++++++++--- core/site_circuits.go | 2 ++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/api/api.go b/api/api.go index 34c212717..cb3680ac1 100644 --- a/api/api.go +++ b/api/api.go @@ -192,12 +192,18 @@ type SocLimiter interface { GetLimitSoc() (int64, error) } -// Dimmer provides §14a dimming +// Dimmer provides EnWG §14a dimming type Dimmer interface { Dimmed() (bool, error) Dim(bool) error } +// Curtailer provides EEG §9 curtailment +type Curtailer interface { + Curtailed() (bool, error) + Curtail(bool) error +} + // ChargeController allows to start/stop the charging session on the vehicle side type ChargeController interface { ChargeEnable(bool) error @@ -272,9 +278,13 @@ type Circuit interface { ValidateCurrent(old, new float64) float64 ValidatePower(old, new float64) float64 - // §14a + // EnWG §14a - reduce demand/consumption Dim(bool) Dimmed() bool + + // EEG §9 - reduce feed-in to the grid + Curtail(bool) + Curtailed() bool } // Redactor is an interface to redact sensitive data diff --git a/api/mock.go b/api/mock.go index d009b80a7..4591c1e38 100644 --- a/api/mock.go +++ b/api/mock.go @@ -856,6 +856,32 @@ func (m *MockCircuit) EXPECT() *MockCircuitMockRecorder { return m.recorder } +// Curtail mocks base method. +func (m *MockCircuit) Curtail(arg0 bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Curtail", arg0) +} + +// Curtail indicates an expected call of Curtail. +func (mr *MockCircuitMockRecorder) Curtail(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Curtail", reflect.TypeOf((*MockCircuit)(nil).Curtail), arg0) +} + +// Curtailed mocks base method. +func (m *MockCircuit) Curtailed() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Curtailed") + ret0, _ := ret[0].(bool) + return ret0 +} + +// Curtailed indicates an expected call of Curtailed. +func (mr *MockCircuitMockRecorder) Curtailed() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Curtailed", reflect.TypeOf((*MockCircuit)(nil).Curtailed)) +} + // Dim mocks base method. func (m *MockCircuit) Dim(arg0 bool) { m.ctrl.T.Helper() diff --git a/core/circuit/circuit.go b/core/circuit/circuit.go index b40e7875d..97729daf0 100644 --- a/core/circuit/circuit.go +++ b/core/circuit/circuit.go @@ -34,9 +34,10 @@ type Circuit struct { getMaxCurrent func() (float64, error) // dynamic max allowed current getMaxPower func() (float64, error) // dynamic max allowed power - current float64 - power float64 - dimmed bool + current float64 + power float64 + dimmed bool + curtailed bool currentUpdated time.Time powerUpdated time.Time @@ -402,3 +403,24 @@ func (c *Circuit) Dimmed() bool { return c.parent.Dimmed() } + +func (c *Circuit) Curtail(curtail bool) { + c.mu.Lock() + defer c.mu.Unlock() + c.curtailed = curtail +} + +func (c *Circuit) Curtailed() bool { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.curtailed { + return true + } + + if c.parent == nil { + return false + } + + return c.parent.Curtailed() +} diff --git a/core/site_circuits.go b/core/site_circuits.go index ee0e2b6f5..319348e13 100644 --- a/core/site_circuits.go +++ b/core/site_circuits.go @@ -19,6 +19,7 @@ type circuitStruct struct { MaxPower float64 `json:"maxPower,omitempty"` MaxCurrent float64 `json:"maxCurrent,omitempty"` Dimmed bool `json:"dimmed"` + Curtailed bool `json:"curtailed"` } // publishCircuits returns a list of circuit titles @@ -37,6 +38,7 @@ func (site *Site) publishCircuits() { MaxPower: instance.GetMaxPower(), MaxCurrent: instance.GetMaxCurrent(), Dimmed: instance.Dimmed(), + Curtailed: instance.Curtailed(), } if instance.GetMaxCurrent() > 0 {