add capability to restrict remote panel modes
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -3,6 +3,7 @@ package mk2driver
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -20,6 +21,7 @@ type WriterPolicy struct {
|
||||
MaxCurrentLimitA *float64
|
||||
ModeChangeMinInterval time.Duration
|
||||
LockoutWindow time.Duration
|
||||
AllowedPanelStates map[PanelSwitchState]struct{}
|
||||
}
|
||||
|
||||
type CommandEvent struct {
|
||||
@@ -93,6 +95,15 @@ func (m *ManagedWriter) SetPanelStateWithSource(source CommandSource, switchStat
|
||||
if err := m.ensureProfileAllows("set_panel_state"); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(m.policy.AllowedPanelStates) > 0 {
|
||||
if _, ok := m.policy.AllowedPanelStates[switchState]; !ok {
|
||||
return fmt.Errorf(
|
||||
"panel switch mode %s denied by allowlist policy; allowed modes: %s",
|
||||
panelStateLabel(switchState),
|
||||
stringsForAllowedPanelStates(m.policy.AllowedPanelStates),
|
||||
)
|
||||
}
|
||||
}
|
||||
if m.policy.MaxCurrentLimitA != nil && currentLimitA != nil && *currentLimitA > *m.policy.MaxCurrentLimitA {
|
||||
return fmt.Errorf("current limit %.2fA exceeds configured policy max %.2fA", *currentLimitA, *m.policy.MaxCurrentLimitA)
|
||||
}
|
||||
@@ -192,3 +203,30 @@ func (m *ManagedWriter) recordLocked(source CommandSource, kind string, allowed
|
||||
func (m *ManagedWriter) baseWriter() SettingsWriter {
|
||||
return m.writer
|
||||
}
|
||||
|
||||
func panelStateLabel(state PanelSwitchState) string {
|
||||
switch state {
|
||||
case PanelSwitchOn:
|
||||
return "on"
|
||||
case PanelSwitchOff:
|
||||
return "off"
|
||||
case PanelSwitchChargerOnly:
|
||||
return "charger_only"
|
||||
case PanelSwitchInverterOnly:
|
||||
return "inverter_only"
|
||||
default:
|
||||
return fmt.Sprintf("unknown(0x%02x)", byte(state))
|
||||
}
|
||||
}
|
||||
|
||||
func stringsForAllowedPanelStates(states map[PanelSwitchState]struct{}) string {
|
||||
if len(states) == 0 {
|
||||
return "<none>"
|
||||
}
|
||||
labels := make([]string, 0, len(states))
|
||||
for state := range states {
|
||||
labels = append(labels, panelStateLabel(state))
|
||||
}
|
||||
sort.Strings(labels)
|
||||
return fmt.Sprintf("%v", labels)
|
||||
}
|
||||
|
||||
@@ -77,3 +77,21 @@ func TestManagedWriterModeRateLimit(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, base.panelWrites)
|
||||
}
|
||||
|
||||
func TestManagedWriterPanelModeAllowlist(t *testing.T) {
|
||||
base := &writerStub{}
|
||||
managed := NewManagedWriter(base, WriterPolicy{
|
||||
Profile: WriterProfileNormal,
|
||||
AllowedPanelStates: map[PanelSwitchState]struct{}{
|
||||
PanelSwitchOff: {},
|
||||
},
|
||||
})
|
||||
|
||||
err := managed.SetPanelStateWithSource(CommandSourceMQTT, PanelSwitchOn, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 0, base.panelWrites)
|
||||
|
||||
err = managed.SetPanelStateWithSource(CommandSourceMQTT, PanelSwitchOff, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, base.panelWrites)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user