evcc-io/core/site_optimizer_test.go
2026-04-15 11:34:28 +00:00

112 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"testing"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
optimizer "github.com/evcc-io/optimizer/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
func TestLoadpointProfile(t *testing.T) {
ctrl := gomock.NewController(t)
lp := loadpoint.NewMockAPI(ctrl)
lp.EXPECT().GetMode().Return(api.ModeMinPV).AnyTimes()
lp.EXPECT().GetStatus().Return(api.StatusC).AnyTimes()
lp.EXPECT().GetChargePower().Return(10000.0).AnyTimes() // 10 kW
lp.EXPECT().EffectiveMinPower().Return(1000.0).AnyTimes() // 1 kW
lp.EXPECT().GetRemainingEnergy().Return(1.8).AnyTimes() // 1.8 kWh
// expected slots: 0.25 kWh...
require.Equal(t, []float64{250, 250, 250, 250, 250, 250, 250, 50}, loadpointProfile(lp, 8))
}
func TestAsTimestamps(t *testing.T) {
// now is 10 minutes into a 15-minute slot
now := time.Date(2025, 1, 1, 12, 10, 0, 0, time.UTC)
// dt[0]=300 means first event is 300s (5min) before end of current slot
// dt[1..] just mark subsequent slot boundaries
dt := []int{60 * 5, 60 * 15, 60 * 15}
got := asTimestamps(dt, now)
// current slot: 12:00–12:15
// first timestamp: 12:15 - 5min = 12:10
// subsequent: 12:15, 12:30
assert.Equal(t, []time.Time{
time.Date(2025, 1, 1, 12, 10, 0, 0, time.UTC),
time.Date(2025, 1, 1, 12, 15, 0, 0, time.UTC),
time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC),
}, got)
}
func TestBatteryForecastTotals(t *testing.T) {
site := new(Site)
req := []optimizer.BatteryConfig{
{SMax: 80},
{SMax: 80},
}
const zero = -1
for _, tc := range []struct {
name string
bat1, bat2 []float32
full, empty int
}{
{
"never full",
[]float32{0, 0},
[]float32{0, 0},
zero, 0,
},
{
"never empty",
[]float32{100, 100},
[]float32{100, 100},
0, zero,
},
{
"first full then empty",
[]float32{100, 0},
[]float32{100, 0},
0, 1,
},
{
"first full finally empty",
[]float32{100, 100, 0},
[]float32{100, 0, 0},
0, 2,
},
{
"first empty then full",
[]float32{0, 100},
[]float32{0, 100},
1, 0,
},
{
"first empty finally full",
[]float32{0, 100, 100},
[]float32{0, 0, 100},
2, 0,
},
} {
t.Run(tc.name, func(t *testing.T) {
resp := []optimizer.BatteryResult{
{StateOfCharge: tc.bat1},
{StateOfCharge: tc.bat2},
}
full, empty := site.batteryForecastFullAndEmptySlots(req, resp)
assert.Equal(t, tc.full, full, "full")
assert.Equal(t, tc.empty, empty, "empty")
})
}
}