implement some features of Venus OS
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:
@@ -2,6 +2,7 @@ package mqttclient
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.coadcorp.com/nathan/invertergui/mk2driver"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -57,6 +58,7 @@ func Test_decodeWriteCommand(t *testing.T) {
|
||||
payload: `{"request_id":"abc","kind":"setting","id":15,"value":-5}`,
|
||||
check: func(t *testing.T, got writeCommand) {
|
||||
assert.Equal(t, writeCommand{
|
||||
Source: mk2driver.CommandSourceMQTT,
|
||||
RequestID: "abc",
|
||||
Kind: commandKindSetting,
|
||||
ID: 15,
|
||||
@@ -69,9 +71,10 @@ func Test_decodeWriteCommand(t *testing.T) {
|
||||
payload: `{"type":"ramvar","id":2,"value":7}`,
|
||||
check: func(t *testing.T, got writeCommand) {
|
||||
assert.Equal(t, writeCommand{
|
||||
Kind: commandKindRAMVar,
|
||||
ID: 2,
|
||||
Value: 7,
|
||||
Source: mk2driver.CommandSourceMQTT,
|
||||
Kind: commandKindRAMVar,
|
||||
ID: 2,
|
||||
Value: 7,
|
||||
}, got)
|
||||
},
|
||||
},
|
||||
@@ -305,12 +308,196 @@ func Test_panelStateCacheResolvePanelCommand(t *testing.T) {
|
||||
assert.Equal(t, "on", resolved.SwitchName)
|
||||
}
|
||||
|
||||
func float64Ptr(in float64) *float64 {
|
||||
return &in
|
||||
}
|
||||
|
||||
func Test_normalizeID(t *testing.T) {
|
||||
assert.Equal(t, "victron_main_01", normalizeID("Victron Main #01"))
|
||||
assert.Equal(t, "inverter-gui", normalizeID(" inverter-gui "))
|
||||
assert.Equal(t, "", normalizeID(" "))
|
||||
}
|
||||
|
||||
func Test_decodeVenusWriteCommand(t *testing.T) {
|
||||
cfg := Config{
|
||||
ClientID: "inverter-gui",
|
||||
Venus: VenusConfig{
|
||||
Enabled: true,
|
||||
PortalID: "site01",
|
||||
Service: "vebus/257",
|
||||
GuideCompat: true,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
topic string
|
||||
payload string
|
||||
assertion func(*testing.T, writeCommand)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "mode numeric",
|
||||
topic: "W/site01/vebus/257/Mode",
|
||||
payload: `{"value":3}`,
|
||||
assertion: func(t *testing.T, cmd writeCommand) {
|
||||
assert.Equal(t, commandKindPanel, cmd.Kind)
|
||||
assert.True(t, cmd.HasSwitch)
|
||||
assert.Equal(t, mk2driver.PanelSwitchOn, cmd.SwitchState)
|
||||
assert.Equal(t, "on", cmd.SwitchName)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "current limit",
|
||||
topic: "W/site01/vebus/257/Ac/ActiveIn/CurrentLimit",
|
||||
payload: `{"value":16.5}`,
|
||||
assertion: func(t *testing.T, cmd writeCommand) {
|
||||
assert.Equal(t, commandKindPanel, cmd.Kind)
|
||||
if assert.NotNil(t, cmd.CurrentLimitA) {
|
||||
assert.Equal(t, 16.5, *cmd.CurrentLimitA)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "standby",
|
||||
topic: "W/site01/vebus/257/Settings/Standby",
|
||||
payload: `{"value":true}`,
|
||||
assertion: func(t *testing.T, cmd writeCommand) {
|
||||
assert.Equal(t, commandKindStandby, cmd.Kind)
|
||||
if assert.NotNil(t, cmd.Standby) {
|
||||
assert.True(t, *cmd.Standby)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid topic",
|
||||
topic: "W/site01/vebus/257/Unknown",
|
||||
payload: `{"value":1}`,
|
||||
wantErr: "unsupported Venus write path",
|
||||
},
|
||||
{
|
||||
name: "guide ess setpoint",
|
||||
topic: "W/site01/settings/0/Settings/CGwacs/AcPowerSetPoint",
|
||||
payload: `{"value":-1200}`,
|
||||
assertion: func(t *testing.T, cmd writeCommand) {
|
||||
assert.Equal(t, commandKindESSSet, cmd.Kind)
|
||||
if assert.NotNil(t, cmd.FloatValue) {
|
||||
assert.InDelta(t, -1200, *cmd.FloatValue, 0.01)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "guide ess mode with prefix",
|
||||
topic: "victron/W/site01/settings/0/Settings/CGwacs/BatteryLife/State",
|
||||
payload: `{"value":10}`,
|
||||
assertion: func(t *testing.T, cmd writeCommand) {
|
||||
assert.Equal(t, commandKindESSMode, cmd.Kind)
|
||||
assert.Equal(t, int16(10), cmd.Value)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testCfg := cfg
|
||||
if tt.name == "guide ess mode with prefix" {
|
||||
testCfg.Venus.TopicPrefix = "victron"
|
||||
}
|
||||
cmd, err := decodeVenusWriteCommand(testCfg, tt.topic, []byte(tt.payload))
|
||||
if tt.wantErr != "" {
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
tt.assertion(t, cmd)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_panelStateCacheRememberTracksFields(t *testing.T) {
|
||||
cache := &panelStateCache{}
|
||||
limit := 12.0
|
||||
standby := true
|
||||
|
||||
cache.remember(writeCommand{
|
||||
Kind: commandKindPanel,
|
||||
HasSwitch: true,
|
||||
SwitchName: "on",
|
||||
SwitchState: mk2driver.PanelSwitchOn,
|
||||
CurrentLimitA: &limit,
|
||||
})
|
||||
cache.remember(writeCommand{
|
||||
Kind: commandKindStandby,
|
||||
Standby: &standby,
|
||||
})
|
||||
|
||||
s := cache.snapshot()
|
||||
assert.True(t, s.HasSwitch)
|
||||
assert.Equal(t, "on", s.Switch)
|
||||
assert.True(t, s.HasCurrent)
|
||||
assert.InDelta(t, 12.0, s.CurrentLimit, 0.001)
|
||||
assert.True(t, s.HasStandby)
|
||||
assert.True(t, s.Standby)
|
||||
}
|
||||
|
||||
func Test_historyTrackerSummary(t *testing.T) {
|
||||
h := newHistoryTracker(2)
|
||||
now := time.Now().UTC()
|
||||
|
||||
h.Add(telemetrySnapshot{
|
||||
Timestamp: now,
|
||||
InputPower: 100,
|
||||
OutputPower: 90,
|
||||
BatteryPower: -10,
|
||||
BatteryVoltage: 25.0,
|
||||
}, operatingStatePassthru, 0, nil)
|
||||
summary := h.Add(telemetrySnapshot{
|
||||
Timestamp: now.Add(1 * time.Second),
|
||||
InputPower: 200,
|
||||
OutputPower: 180,
|
||||
BatteryPower: -20,
|
||||
BatteryVoltage: 24.5,
|
||||
}, operatingStateInverter, 2, &now)
|
||||
|
||||
assert.Equal(t, 2, summary.Samples)
|
||||
assert.InDelta(t, 150, summary.AverageInputPower, 0.01)
|
||||
assert.InDelta(t, 135, summary.AverageOutputPower, 0.01)
|
||||
assert.InDelta(t, 180, summary.MaxOutputPower, 0.01)
|
||||
assert.InDelta(t, 24.5, summary.MinBatteryVoltage, 0.01)
|
||||
assert.Equal(t, uint64(2), summary.FaultCount)
|
||||
}
|
||||
|
||||
func Test_resolveESSWriteCommand(t *testing.T) {
|
||||
ess := newESSControlCache()
|
||||
telemetry := &telemetryCache{}
|
||||
telemetry.set(telemetrySnapshot{InputVoltage: 230})
|
||||
|
||||
setpoint := 920.0
|
||||
mapped, err := resolveESSWriteCommand(writeCommand{
|
||||
Kind: commandKindESSSet,
|
||||
FloatValue: &setpoint,
|
||||
}, ess, telemetry)
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, mapped) {
|
||||
assert.Equal(t, commandKindPanel, mapped.Kind)
|
||||
assert.Equal(t, "charger_only", mapped.SwitchName)
|
||||
if assert.NotNil(t, mapped.CurrentLimitA) {
|
||||
assert.InDelta(t, 4.0, *mapped.CurrentLimitA, 0.01)
|
||||
}
|
||||
}
|
||||
|
||||
maxDischarge := 1000.0
|
||||
_, err = resolveESSWriteCommand(writeCommand{
|
||||
Kind: commandKindESSMaxD,
|
||||
FloatValue: &maxDischarge,
|
||||
}, ess, telemetry)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dischargeSetpoint := -2000.0
|
||||
mapped, err = resolveESSWriteCommand(writeCommand{
|
||||
Kind: commandKindESSSet,
|
||||
FloatValue: &dischargeSetpoint,
|
||||
}, ess, telemetry)
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, mapped) {
|
||||
assert.Equal(t, commandKindPanel, mapped.Kind)
|
||||
assert.Equal(t, "inverter_only", mapped.SwitchName)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user