HEMS: redesign dim and curtail api as tristate (#30116)
This commit is contained in:
parent
3d59da473b
commit
8dc0edd8d9
8 changed files with 67 additions and 54 deletions
|
|
@ -280,11 +280,11 @@ type Circuit interface {
|
|||
|
||||
// EnWG §14a - reduce demand/consumption
|
||||
Dim(bool)
|
||||
Dimmed() bool
|
||||
Dimmed() *bool
|
||||
|
||||
// EEG §9 - reduce feed-in to the grid
|
||||
Curtail(bool)
|
||||
Curtailed() bool
|
||||
Curtailed() *bool
|
||||
}
|
||||
|
||||
// Redactor is an interface to redact sensitive data
|
||||
|
|
|
|||
|
|
@ -869,10 +869,10 @@ func (mr *MockCircuitMockRecorder) Curtail(arg0 any) *gomock.Call {
|
|||
}
|
||||
|
||||
// Curtailed mocks base method.
|
||||
func (m *MockCircuit) Curtailed() bool {
|
||||
func (m *MockCircuit) Curtailed() *bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Curtailed")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret0, _ := ret[0].(*bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
|
|
@ -895,10 +895,10 @@ func (mr *MockCircuitMockRecorder) Dim(arg0 any) *gomock.Call {
|
|||
}
|
||||
|
||||
// Dimmed mocks base method.
|
||||
func (m *MockCircuit) Dimmed() bool {
|
||||
func (m *MockCircuit) Dimmed() *bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Dimmed")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret0, _ := ret[0].(*bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ type Circuit struct {
|
|||
|
||||
current float64
|
||||
power float64
|
||||
dimmed bool
|
||||
curtailed bool
|
||||
dimmed *bool
|
||||
curtailed *bool
|
||||
|
||||
currentUpdated time.Time
|
||||
powerUpdated time.Time
|
||||
|
|
@ -394,19 +394,19 @@ func (c *Circuit) ValidateCurrent(old, new float64) float64 {
|
|||
func (c *Circuit) Dim(dim bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.dimmed = dim
|
||||
c.dimmed = &dim
|
||||
}
|
||||
|
||||
func (c *Circuit) Dimmed() bool {
|
||||
func (c *Circuit) Dimmed() *bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.dimmed {
|
||||
return true
|
||||
if c.dimmed != nil {
|
||||
return c.dimmed
|
||||
}
|
||||
|
||||
if c.parent == nil {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.parent.Dimmed()
|
||||
|
|
@ -415,19 +415,19 @@ func (c *Circuit) Dimmed() bool {
|
|||
func (c *Circuit) Curtail(curtail bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.curtailed = curtail
|
||||
c.curtailed = &curtail
|
||||
}
|
||||
|
||||
func (c *Circuit) Curtailed() bool {
|
||||
func (c *Circuit) Curtailed() *bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.curtailed {
|
||||
return true
|
||||
if c.curtailed != nil {
|
||||
return c.curtailed
|
||||
}
|
||||
|
||||
if c.parent == nil {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.parent.Curtailed()
|
||||
|
|
|
|||
|
|
@ -82,18 +82,18 @@ func circuitMaxPower(circuit api.Circuit) float64 {
|
|||
}
|
||||
|
||||
// circuitDimmed returns a circuits dim status
|
||||
func circuitDimmed(circuit api.Circuit) bool {
|
||||
func circuitDimmed(circuit api.Circuit) *bool {
|
||||
if circuit == nil {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
return circuit.Dimmed()
|
||||
}
|
||||
|
||||
// circuitCurtailed returns a circuit's curtail status
|
||||
func circuitCurtailed(circuit api.Circuit) bool {
|
||||
func circuitCurtailed(circuit api.Circuit) *bool {
|
||||
if circuit == nil {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
return circuit.Curtailed()
|
||||
|
|
|
|||
|
|
@ -1963,20 +1963,20 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, f
|
|||
return
|
||||
}
|
||||
|
||||
dim := lp.circuit != nil && lp.circuit.Dimmed()
|
||||
if dim := circuitDimmed(lp.circuit); dim != nil {
|
||||
if *dim != dimmed {
|
||||
if err := dimmer.Dim(*dim); err != nil {
|
||||
lp.log.ERROR.Printf("dim: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if dim != dimmed {
|
||||
if err := dimmer.Dim(dim); err != nil {
|
||||
lp.log.ERROR.Printf("dim: %v", err)
|
||||
return
|
||||
lp.publish(keys.Dimmed, *dim)
|
||||
lp.log.INFO.Printf("§14a dim: %t", *dim)
|
||||
}
|
||||
|
||||
lp.publish(keys.Dimmed, dim)
|
||||
lp.log.INFO.Printf("§14a dim: %t", dim)
|
||||
}
|
||||
|
||||
if dim {
|
||||
return
|
||||
if *dim {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func (site *Site) updateBatteryMode(batteryGridChargeActive bool, rate api.Rate)
|
|||
|
||||
// put battery into hold mode when charging is active and circuit dimmed
|
||||
fromToCharge := batteryMode == api.BatteryCharge || batteryMode == api.BatteryUnknown && site.batteryMode == api.BatteryCharge
|
||||
if fromToCharge && circuitDimmed(site.circuit) {
|
||||
if dimmed := circuitDimmed(site.circuit); fromToCharge && dimmed != nil && *dimmed {
|
||||
site.log.DEBUG.Println("battery mode: circuit dimmed")
|
||||
batteryMode = api.BatteryHold
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,18 +29,25 @@ func TestDimming(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
has, want bool
|
||||
has bool
|
||||
want *bool
|
||||
}{
|
||||
{has: false, want: false},
|
||||
{has: false, want: true},
|
||||
{has: true, want: false},
|
||||
{has: true, want: true},
|
||||
// nil: circuit has no opinion - the device must not be touched, so a
|
||||
// limit configured outside evcc is preserved (issue #30068)
|
||||
{has: false, want: nil},
|
||||
{has: true, want: nil},
|
||||
{has: false, want: new(false)},
|
||||
{has: false, want: new(true)},
|
||||
{has: true, want: new(false)},
|
||||
{has: true, want: new(true)},
|
||||
} {
|
||||
t.Logf("%+v", tc)
|
||||
|
||||
dimmer.EXPECT().Dimmed().Return(tc.has, nil)
|
||||
if tc.has != tc.want {
|
||||
dimmer.EXPECT().Dim(tc.want).Return(nil)
|
||||
if tc.want != nil {
|
||||
dimmer.EXPECT().Dimmed().Return(tc.has, nil)
|
||||
if tc.has != *tc.want {
|
||||
dimmer.EXPECT().Dim(*tc.want).Return(nil)
|
||||
}
|
||||
}
|
||||
|
||||
require.NoError(t, s.dimMeters(tc.want))
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ type circuitStruct struct {
|
|||
Current *float64 `json:"current,omitempty"`
|
||||
MaxPower float64 `json:"maxPower,omitempty"`
|
||||
MaxCurrent float64 `json:"maxCurrent,omitempty"`
|
||||
Dimmed bool `json:"dimmed"`
|
||||
Curtailed bool `json:"curtailed"`
|
||||
Dimmed *bool `json:"dimmed,omitempty"`
|
||||
Curtailed *bool `json:"curtailed,omitempty"`
|
||||
}
|
||||
|
||||
// publishCircuits returns a list of circuit titles
|
||||
|
|
@ -58,9 +58,12 @@ func (site *Site) publishCircuits() {
|
|||
site.publish(keys.Circuits, res)
|
||||
}
|
||||
|
||||
func (site *Site) dimMeters(dim bool) error {
|
||||
var errs error
|
||||
func (site *Site) dimMeters(dim *bool) error {
|
||||
if dim == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs error
|
||||
for _, dev := range slices.Concat(site.auxMeters, site.extMeters) {
|
||||
m, ok := api.Cap[api.Dimmer](dev.Instance())
|
||||
if !ok {
|
||||
|
|
@ -68,7 +71,7 @@ func (site *Site) dimMeters(dim bool) error {
|
|||
}
|
||||
|
||||
if dimmed, err := m.Dimmed(); err == nil {
|
||||
if dim == dimmed {
|
||||
if *dim == dimmed {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
|
|
@ -78,8 +81,8 @@ func (site *Site) dimMeters(dim bool) error {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := m.Dim(dim); err == nil {
|
||||
site.log.DEBUG.Printf("%s dim: %t", dev.Config().Name, dim)
|
||||
if err := m.Dim(*dim); err == nil {
|
||||
site.log.DEBUG.Printf("%s dim: %t", dev.Config().Name, *dim)
|
||||
} else if !errors.Is(err, api.ErrNotAvailable) {
|
||||
errs = errors.Join(errs, fmt.Errorf("%s dim: %w", dev.Config().Name, err))
|
||||
}
|
||||
|
|
@ -88,9 +91,12 @@ func (site *Site) dimMeters(dim bool) error {
|
|||
return errs
|
||||
}
|
||||
|
||||
func (site *Site) curtailPV(curtail bool) error {
|
||||
var errs error
|
||||
func (site *Site) curtailPV(curtail *bool) error {
|
||||
if curtail == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs error
|
||||
for _, dev := range site.pvMeters {
|
||||
m, ok := api.Cap[api.Curtailer](dev.Instance())
|
||||
if !ok {
|
||||
|
|
@ -98,7 +104,7 @@ func (site *Site) curtailPV(curtail bool) error {
|
|||
}
|
||||
|
||||
if curtailed, err := m.Curtailed(); err == nil {
|
||||
if curtail == curtailed {
|
||||
if *curtail == curtailed {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
|
|
@ -108,8 +114,8 @@ func (site *Site) curtailPV(curtail bool) error {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := m.Curtail(curtail); err == nil {
|
||||
site.log.DEBUG.Printf("%s curtail: %t", dev.Config().Name, curtail)
|
||||
if err := m.Curtail(*curtail); err == nil {
|
||||
site.log.DEBUG.Printf("%s curtail: %t", dev.Config().Name, *curtail)
|
||||
} else if !errors.Is(err, api.ErrNotAvailable) {
|
||||
errs = errors.Join(errs, fmt.Errorf("%s curtail: %w", dev.Config().Name, err))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue