add capability to restrict remote panel modes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-02-19 15:55:20 +11:00
parent e8153e2953
commit e700239764
9 changed files with 197 additions and 2 deletions

View File

@@ -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)
}

View File

@@ -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)
}