Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbb7d704f6 | ||
|
|
d284a2f9af | ||
|
|
96fb302147 | ||
|
|
eb836e9ec8 | ||
|
|
52c0cfaa5d | ||
|
|
d1b1ded0e6 |
14 changed files with 116 additions and 64 deletions
9
.github/workflows/command-backport.yml
vendored
9
.github/workflows/command-backport.yml
vendored
|
|
@ -157,18 +157,15 @@ jobs:
|
||||||
TITLE: ${{ steps.pr.outputs.title }}
|
TITLE: ${{ steps.pr.outputs.title }}
|
||||||
ACTOR: ${{ github.event.comment.user.login }}
|
ACTOR: ${{ github.event.comment.user.login }}
|
||||||
run: |
|
run: |
|
||||||
# the release line the branch was cut from, 0.313 for release/0.313.1
|
|
||||||
line=$(echo "${TARGET#release/}" | cut -d. -f1,2)
|
|
||||||
|
|
||||||
# the pull request is authored by the deploy token, so assign the
|
# the pull request is authored by the deploy token, so assign the
|
||||||
# requester. the body stays empty, squashing it would carry the text
|
# requester. the body stays empty, squashing it would carry the text
|
||||||
# into the release branch as the commit description.
|
# into the release branch as the commit description.
|
||||||
# the marker trails the title so the changelog filters and groups still
|
# the title stays untouched so the changelog filters and groups still
|
||||||
# see the original prefix, goreleaser strips it from the release notes
|
# see the original prefix, the backport label identifies the pull request
|
||||||
url=$(gh pr create \
|
url=$(gh pr create \
|
||||||
--base "$TARGET" \
|
--base "$TARGET" \
|
||||||
--head "$BRANCH" \
|
--head "$BRANCH" \
|
||||||
--title "$TITLE [$line backport]" \
|
--title "$TITLE" \
|
||||||
--label backport \
|
--label backport \
|
||||||
--assignee "$ACTOR" \
|
--assignee "$ACTOR" \
|
||||||
--body "")
|
--body "")
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,6 @@ snapshot:
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
# drop the marker the backport command appends to the pull request title
|
|
||||||
format: '{{ .SHA }} {{ reReplaceAll ` \[\d+\.\d+ backport\]` .Message "" }}'
|
|
||||||
filters:
|
filters:
|
||||||
exclude:
|
exclude:
|
||||||
- "^chore"
|
- "^chore"
|
||||||
|
|
|
||||||
11
cmd/setup.go
11
cmd/setup.go
|
|
@ -538,9 +538,14 @@ func configureVehicles(static []config.Named, names ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instance, err := vehicleInstance(cc)
|
// disabled vehicles are not instantiated
|
||||||
if err != nil {
|
var instance api.Vehicle
|
||||||
return fmt.Errorf("cannot create vehicle '%s': %w", cc.Name, err)
|
if !conf.Disable {
|
||||||
|
var err error
|
||||||
|
instance, err = vehicleInstance(cc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create vehicle '%s': %w", cc.Name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,8 @@ func NewLoadpointFromConfig(log *util.Logger, settings settings.Settings, collec
|
||||||
}
|
}
|
||||||
lp.defaultVehicle = dev.Instance()
|
lp.defaultVehicle = dev.Instance()
|
||||||
if lp.defaultVehicle == nil {
|
if lp.defaultVehicle == nil {
|
||||||
return lp, errors.New("missing default vehicle instance")
|
// disabled vehicle
|
||||||
|
lp.log.DEBUG.Printf("default vehicle '%s' is disabled", lp.VehicleRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1061,20 +1062,15 @@ func (lp *Loadpoint) charging() bool {
|
||||||
func (lp *Loadpoint) PvChargeStarting() bool {
|
func (lp *Loadpoint) PvChargeStarting() bool {
|
||||||
lp.RLock()
|
lp.RLock()
|
||||||
enabled := lp.enabled
|
enabled := lp.enabled
|
||||||
pvTimer := lp.pvTimer
|
pvTimerRunning := !lp.pvTimer.IsZero()
|
||||||
lp.RUnlock()
|
lp.RUnlock()
|
||||||
|
|
||||||
if lp.GetMode() != api.ModePV || !lp.connected() || lp.chargeGoalReached(enabled) {
|
if lp.GetMode() != api.ModePV || !lp.connected() || lp.chargeGoalReached(enabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if enabled || pvTimer.IsZero() {
|
// enable timer running (not yet enabled)
|
||||||
return false
|
return !enabled && pvTimerRunning
|
||||||
}
|
|
||||||
|
|
||||||
// a timer restarting on every surplus dip never starts the loadpoint, hence
|
|
||||||
// only claim surplus once it has survived half of the enable delay (#32778)
|
|
||||||
return lp.clock.Since(pvTimer) >= lp.GetEnableDelay()/2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// chargeGoalReached reports whether the loadpoint will not draw more: enabled
|
// chargeGoalReached reports whether the loadpoint will not draw more: enabled
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ import (
|
||||||
"github.com/evcc-io/evcc/core/soc"
|
"github.com/evcc-io/evcc/core/soc"
|
||||||
"github.com/evcc-io/evcc/messenger"
|
"github.com/evcc-io/evcc/messenger"
|
||||||
"github.com/evcc-io/evcc/util"
|
"github.com/evcc-io/evcc/util"
|
||||||
|
"github.com/evcc-io/evcc/util/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -882,3 +884,26 @@ func TestBatteryBoostHold(t *testing.T) {
|
||||||
// which is what keeps the sitePower priority adjustment applied to the loadpoint
|
// which is what keeps the sitePower priority adjustment applied to the loadpoint
|
||||||
assert.NotEqual(t, boostDisabled, lp.GetBatteryBoost(), "hold is active")
|
assert.NotEqual(t, boostDisabled, lp.GetBatteryBoost(), "hold is active")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default vehicle referencing a disabled vehicle must not fail loadpoint creation
|
||||||
|
func TestNewLoadpointFromConfigDisabledVehicle(t *testing.T) {
|
||||||
|
config.Reset()
|
||||||
|
t.Cleanup(config.Reset)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
// disabled vehicle: device registered without instance
|
||||||
|
var vehicle api.Vehicle
|
||||||
|
require.NoError(t, config.Vehicles().Add(config.NewStaticDevice(config.Named{Name: "vehicle"}, vehicle)))
|
||||||
|
require.NoError(t, config.Chargers().Add(config.NewStaticDevice(config.Named{Name: "charger"}, api.Charger(api.NewMockCharger(ctrl)))))
|
||||||
|
|
||||||
|
lp, err := NewLoadpointFromConfig(util.NewLogger("foo"), nil, nil, map[string]any{
|
||||||
|
"charger": "charger",
|
||||||
|
"vehicle": "vehicle",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, lp.defaultVehicle)
|
||||||
|
|
||||||
|
// disabled vehicle is filtered from instances
|
||||||
|
require.Empty(t, config.Instances(config.Vehicles().Devices()))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1127,9 +1127,7 @@ func (site *Site) reservedPVPower(lp updater) float64 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if other.EffectivePriority() > prio && other.PvChargeStarting() {
|
if other.EffectivePriority() > prio && other.PvChargeStarting() {
|
||||||
// min power is what the loadpoint needs to start, reserving its max
|
reserved += other.EffectiveMaxPower()
|
||||||
// power would starve this loadpoint beyond that (#32778)
|
|
||||||
reserved += other.EffectiveMinPower()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,8 @@ import (
|
||||||
"github.com/evcc-io/evcc/util"
|
"github.com/evcc-io/evcc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testEnableDelay = time.Minute
|
|
||||||
|
|
||||||
func newPVLoadpoint(prio int, mode api.ChargeMode, status api.ChargeStatus, enabled bool, timer time.Time) *Loadpoint {
|
func newPVLoadpoint(prio int, mode api.ChargeMode, status api.ChargeStatus, enabled bool, timer time.Time) *Loadpoint {
|
||||||
lp := &Loadpoint{
|
return &Loadpoint{
|
||||||
log: util.NewLogger("lp"),
|
log: util.NewLogger("lp"),
|
||||||
clock: clock.NewMock(),
|
clock: clock.NewMock(),
|
||||||
minCurrent: minA,
|
minCurrent: minA,
|
||||||
|
|
@ -25,21 +23,13 @@ func newPVLoadpoint(prio int, mode api.ChargeMode, status api.ChargeStatus, enab
|
||||||
pvTimer: timer,
|
pvTimer: timer,
|
||||||
priority: prio,
|
priority: prio,
|
||||||
}
|
}
|
||||||
lp.Enable.Delay = testEnableDelay
|
|
||||||
return lp
|
|
||||||
}
|
|
||||||
|
|
||||||
// pvTimerStarted returns a timer start that has been running for the given duration
|
|
||||||
func pvTimerStarted(running time.Duration) time.Time {
|
|
||||||
return clock.NewMock().Now().Add(-running)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPvChargeStarting(t *testing.T) {
|
func TestPvChargeStarting(t *testing.T) {
|
||||||
now := clock.NewMock().Now()
|
now := clock.NewMock().Now()
|
||||||
settled := pvTimerStarted(testEnableDelay / 2)
|
|
||||||
|
|
||||||
// enable timer running but car already full (soc at default 100% limit): not starting up
|
// enable timer running but car already full (soc at default 100% limit): not starting up
|
||||||
enablePendingFull := newPVLoadpoint(0, api.ModePV, api.StatusB, false, settled)
|
enablePendingFull := newPVLoadpoint(0, api.ModePV, api.StatusB, false, now)
|
||||||
enablePendingFull.vehicleSoc = 100
|
enablePendingFull.vehicleSoc = 100
|
||||||
|
|
||||||
tc := []struct {
|
tc := []struct {
|
||||||
|
|
@ -47,13 +37,12 @@ func TestPvChargeStarting(t *testing.T) {
|
||||||
lp *Loadpoint
|
lp *Loadpoint
|
||||||
starting bool
|
starting bool
|
||||||
}{
|
}{
|
||||||
{"enable timer running", newPVLoadpoint(0, api.ModePV, api.StatusB, false, settled), true},
|
{"enable timer running", newPVLoadpoint(0, api.ModePV, api.StatusB, false, now), true},
|
||||||
{"enable timer just restarted", newPVLoadpoint(0, api.ModePV, api.StatusB, false, now), false},
|
|
||||||
{"enabled not charging", newPVLoadpoint(0, api.ModePV, api.StatusB, true, time.Time{}), false},
|
{"enabled not charging", newPVLoadpoint(0, api.ModePV, api.StatusB, true, time.Time{}), false},
|
||||||
{"enabled and charging", newPVLoadpoint(0, api.ModePV, api.StatusC, true, time.Time{}), false},
|
{"enabled and charging", newPVLoadpoint(0, api.ModePV, api.StatusC, true, time.Time{}), false},
|
||||||
{"disabled idle", newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{}), false},
|
{"disabled idle", newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{}), false},
|
||||||
{"disconnected", newPVLoadpoint(0, api.ModePV, api.StatusA, false, settled), false},
|
{"disconnected", newPVLoadpoint(0, api.ModePV, api.StatusA, false, now), false},
|
||||||
{"not pv mode", newPVLoadpoint(0, api.ModeNow, api.StatusB, false, settled), false},
|
{"not pv mode", newPVLoadpoint(0, api.ModeNow, api.StatusB, false, now), false},
|
||||||
{"enable pending but car full", enablePendingFull, false},
|
{"enable pending but car full", enablePendingFull, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +57,7 @@ func TestReservedPVPower(t *testing.T) {
|
||||||
Voltage = 230
|
Voltage = 230
|
||||||
|
|
||||||
// higher-priority loadpoint (prio 1) starting up
|
// higher-priority loadpoint (prio 1) starting up
|
||||||
high := newPVLoadpoint(1, api.ModePV, api.StatusB, false, pvTimerStarted(testEnableDelay/2))
|
high := newPVLoadpoint(1, api.ModePV, api.StatusB, false, clock.NewMock().Now())
|
||||||
// lower-priority loadpoint (prio 0) in PV mode
|
// lower-priority loadpoint (prio 0) in PV mode
|
||||||
low := newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{})
|
low := newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{})
|
||||||
|
|
||||||
|
|
@ -77,20 +66,10 @@ func TestReservedPVPower(t *testing.T) {
|
||||||
loadpoints: []*Loadpoint{high, low},
|
loadpoints: []*Loadpoint{high, low},
|
||||||
}
|
}
|
||||||
|
|
||||||
// low reserves the power high needs to start, not its max power (#32778)
|
// low reserves high's anticipated max power while high is starting up
|
||||||
if got, want := site.reservedPVPower(low), high.EffectiveMinPower(); got != want {
|
if got, want := site.reservedPVPower(low), high.EffectiveMaxPower(); got != want {
|
||||||
t.Errorf("low: want %.0f, got %.0f", want, got)
|
t.Errorf("low: want %.0f, got %.0f", want, got)
|
||||||
}
|
}
|
||||||
if high.EffectiveMinPower() == high.EffectiveMaxPower() {
|
|
||||||
t.Fatal("test requires min and max power to differ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// a timer restarting on every surplus dip must not reserve at all (#32778)
|
|
||||||
high.pvTimer = clock.NewMock().Now()
|
|
||||||
if got := site.reservedPVPower(low); got != 0 {
|
|
||||||
t.Errorf("low while high timer restarts: want 0, got %.0f", got)
|
|
||||||
}
|
|
||||||
high.pvTimer = pvTimerStarted(testEnableDelay / 2)
|
|
||||||
|
|
||||||
// high (top priority) reserves nothing
|
// high (top priority) reserves nothing
|
||||||
if got := site.reservedPVPower(high); got != 0 {
|
if got := site.reservedPVPower(high); got != 0 {
|
||||||
|
|
|
||||||
|
|
@ -104,14 +104,7 @@ type vehicles struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vv *vehicles) Instances() []api.Vehicle {
|
func (vv *vehicles) Instances() []api.Vehicle {
|
||||||
devs := config.Vehicles().Devices()
|
return config.Instances(config.Vehicles().Devices())
|
||||||
|
|
||||||
res := make([]api.Vehicle, 0, len(devs))
|
|
||||||
for _, dev := range devs {
|
|
||||||
res = append(res, dev.Instance())
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vv *vehicles) Settings() []vehicle.API {
|
func (vv *vehicles) Settings() []vehicle.API {
|
||||||
|
|
@ -119,6 +112,10 @@ func (vv *vehicles) Settings() []vehicle.API {
|
||||||
|
|
||||||
res := make([]vehicle.API, 0, len(devs))
|
res := make([]vehicle.API, 0, len(devs))
|
||||||
for _, dev := range devs {
|
for _, dev := range devs {
|
||||||
|
// skip disabled vehicles
|
||||||
|
if dev.Instance() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
res = append(res, vehicle.Adapter(vv.log, dev))
|
res = append(res, vehicle.Adapter(vv.log, dev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -269,3 +269,5 @@ replace github.com/lorenzodonini/ocpp-go => github.com/evcc-io/ocpp-go v0.0.0-20
|
||||||
replace github.com/enbility/spine-go => github.com/andig/spine-go v0.7.1-0.20260804073026-f9a8d8e8fd18
|
replace github.com/enbility/spine-go => github.com/andig/spine-go v0.7.1-0.20260804073026-f9a8d8e8fd18
|
||||||
|
|
||||||
replace github.com/enbility/eebus-go => github.com/andig/eebus-go v0.0.0-20260725155950-e735091ff165
|
replace github.com/enbility/eebus-go => github.com/andig/eebus-go v0.0.0-20260725155950-e735091ff165
|
||||||
|
|
||||||
|
replace github.com/enbility/ship-go => github.com/andig/ship-go v0.6.1-0.20260818072438-bade5ec9fbd7
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -30,6 +30,8 @@ github.com/andig/gosunspec v0.0.0-20260705113727-6d585e133512 h1:1y8dS4GaBB9WUu/
|
||||||
github.com/andig/gosunspec v0.0.0-20260705113727-6d585e133512/go.mod h1:c6P6szcR+ROkqZruOR4f6qbDKFjZX6OitPpj+yJ/r8k=
|
github.com/andig/gosunspec v0.0.0-20260705113727-6d585e133512/go.mod h1:c6P6szcR+ROkqZruOR4f6qbDKFjZX6OitPpj+yJ/r8k=
|
||||||
github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e h1:m/NTP3JWpR7M0ljLxiQU4fzR25jjhe1LDtxLMNcoNJQ=
|
github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e h1:m/NTP3JWpR7M0ljLxiQU4fzR25jjhe1LDtxLMNcoNJQ=
|
||||||
github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e/go.mod h1:4VtYzTm//oUipwvO3yh0g/udTE7pYJM+U/kyAuFDsgM=
|
github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e/go.mod h1:4VtYzTm//oUipwvO3yh0g/udTE7pYJM+U/kyAuFDsgM=
|
||||||
|
github.com/andig/ship-go v0.6.1-0.20260818072438-bade5ec9fbd7 h1:YXXxRvtVSTLATXSvMYBtf8PBNeVRBu7WzxfL4PCqN+8=
|
||||||
|
github.com/andig/ship-go v0.6.1-0.20260818072438-bade5ec9fbd7/go.mod h1:EvRFx83phCwPQrOuWw2CX5t0c6mcFVzIk6gQEz4Lijg=
|
||||||
github.com/andig/spine-go v0.7.1-0.20260804073026-f9a8d8e8fd18 h1:FVKqjwJJLo8vV47jXt1lNN4EVJXCyV3LSTNFt9eeimU=
|
github.com/andig/spine-go v0.7.1-0.20260804073026-f9a8d8e8fd18 h1:FVKqjwJJLo8vV47jXt1lNN4EVJXCyV3LSTNFt9eeimU=
|
||||||
github.com/andig/spine-go v0.7.1-0.20260804073026-f9a8d8e8fd18/go.mod h1:ddWZU5BQGyzRGF20Z7rUKFFzbCp+cfu9mZgYtOMW/fM=
|
github.com/andig/spine-go v0.7.1-0.20260804073026-f9a8d8e8fd18/go.mod h1:ddWZU5BQGyzRGF20Z7rUKFFzbCp+cfu9mZgYtOMW/fM=
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
|
|
@ -140,8 +142,6 @@ github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2I
|
||||||
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
||||||
github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a h1:foChWb8lhzqa6lWDRs6COYMdp649YlUirFP8GqoT0JQ=
|
github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a h1:foChWb8lhzqa6lWDRs6COYMdp649YlUirFP8GqoT0JQ=
|
||||||
github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a/go.mod h1:H64mhYcAQUGUUnVqMdZQf93kPecH4M79xwH95Lddt3U=
|
github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a/go.mod h1:H64mhYcAQUGUUnVqMdZQf93kPecH4M79xwH95Lddt3U=
|
||||||
github.com/enbility/ship-go v0.6.1-0.20260720110450-0aa90f64ac76 h1:gDrV6vBG3luAgsyX4sXYShITT41CDKXrtyluLeRmglE=
|
|
||||||
github.com/enbility/ship-go v0.6.1-0.20260720110450-0aa90f64ac76/go.mod h1:EvRFx83phCwPQrOuWw2CX5t0c6mcFVzIk6gQEz4Lijg=
|
|
||||||
github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6 h1:XOYvxKtT1oxT37w/5oEiRLuPbm9FuJPt3fiYhX0h8Po=
|
github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6 h1:XOYvxKtT1oxT37w/5oEiRLuPbm9FuJPt3fiYhX0h8Po=
|
||||||
github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6/go.mod h1:BszP9qFV14mPXgyIREbgIdQtWxbAj3OKqvK02HihMoM=
|
github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6/go.mod h1:BszP9qFV14mPXgyIREbgIdQtWxbAj3OKqvK02HihMoM=
|
||||||
github.com/evcc-io/modbus v0.0.0-20250501165638-8b6f1fbdb7ea h1:F6eyC8V8wvc3ranlsR4coAls+OPBkVvxyX0a2VqdlPc=
|
github.com/evcc-io/modbus v0.0.0-20250501165638-8b6f1fbdb7ea h1:F6eyC8V8wvc3ranlsR4coAls+OPBkVvxyX0a2VqdlPc=
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/evcc-io/evcc/util"
|
"github.com/evcc-io/evcc/util"
|
||||||
"github.com/evcc-io/evcc/util/config"
|
"github.com/evcc-io/evcc/util/config"
|
||||||
"github.com/evcc-io/evcc/util/templates"
|
"github.com/evcc-io/evcc/util/templates"
|
||||||
|
"github.com/go-viper/mapstructure/v2"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -119,8 +120,9 @@ func loadpointConfig(dev config.Device[loadpoint.API]) (loadpointFullConfig, err
|
||||||
return loadpointFullConfig{}, err
|
return loadpointFullConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// non-strict: runtime settings (mode, ...) are persisted alongside the config
|
||||||
var static loadpoint.StaticConfig
|
var static loadpoint.StaticConfig
|
||||||
if err := util.DecodeOther(staticMap, &static); err != nil {
|
if err := mapstructure.Decode(staticMap, &static); err != nil {
|
||||||
return loadpointFullConfig{}, err
|
return loadpointFullConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ func TestLoadpointConfigDisabledNilInstance(t *testing.T) {
|
||||||
"charger": "wallbox",
|
"charger": "wallbox",
|
||||||
"meter": "lp-meter",
|
"meter": "lp-meter",
|
||||||
"title": "Garage",
|
"title": "Garage",
|
||||||
|
"mode": "pv", // runtime setting persisted by the settings adapter
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ requirements:
|
||||||
The [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) guide explains the process and generates a free Access and Refresh Token.
|
The [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) guide explains the process and generates a free Access and Refresh Token.
|
||||||
With this token pair and your Client ID created in the Tesla Developer Account, evcc can directly communicate with the Tesla Fleet API.
|
With this token pair and your Client ID created in the Tesla Developer Account, evcc can directly communicate with the Tesla Fleet API.
|
||||||
params:
|
params:
|
||||||
|
- name: usage
|
||||||
|
choice: ["battery"]
|
||||||
- name: host
|
- name: host
|
||||||
- name: password
|
- name: password
|
||||||
required: true
|
required: true
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,21 @@ test.describe("disable / enable", async () => {
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await createLoadpoint(page, "Garage");
|
await createLoadpoint(page, "Garage");
|
||||||
|
|
||||||
|
// restart to bring both loadpoints live
|
||||||
|
await restart();
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
// switch charge mode on dashboard; runtime setting is persisted next to the loadpoint config
|
||||||
|
await page.goto("/");
|
||||||
|
const modeButton = page
|
||||||
|
.getByTestId("loadpoint")
|
||||||
|
.nth(0)
|
||||||
|
.getByTestId("mode")
|
||||||
|
.getByRole("button", { name: "Fast" });
|
||||||
|
await modeButton.click();
|
||||||
|
await expect(modeButton).toHaveClass(/active/);
|
||||||
|
await page.goto("/#/config");
|
||||||
|
|
||||||
const target = page.getByTestId("loadpoint").nth(0);
|
const target = page.getByTestId("loadpoint").nth(0);
|
||||||
|
|
||||||
await toggleLoadpointDisable(page, 0, "Disable");
|
await toggleLoadpointDisable(page, 0, "Disable");
|
||||||
|
|
@ -66,10 +81,11 @@ test.describe("disable / enable", async () => {
|
||||||
// card shows disabled state
|
// card shows disabled state
|
||||||
await expect(disabledBadge(target)).toBeVisible();
|
await expect(disabledBadge(target)).toBeVisible();
|
||||||
|
|
||||||
// restart, no fatal
|
// restart, no fatal, both loadpoints still listed
|
||||||
await restart();
|
await restart();
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await expectNoFatal(page);
|
await expectNoFatal(page);
|
||||||
|
await expect(page.getByTestId("loadpoint")).toHaveCount(2);
|
||||||
await expect(disabledBadge(target)).toBeVisible();
|
await expect(disabledBadge(target)).toBeVisible();
|
||||||
|
|
||||||
// re-enable by clicking the disabled card
|
// re-enable by clicking the disabled card
|
||||||
|
|
@ -223,6 +239,10 @@ test.describe("disable / enable", async () => {
|
||||||
await start();
|
await start();
|
||||||
await page.goto("/#/config");
|
await page.goto("/#/config");
|
||||||
|
|
||||||
|
// loadpoint so the vehicle shows up on the dashboard
|
||||||
|
await createLoadpoint(page, "Carport");
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
// add user-defined vehicle
|
// add user-defined vehicle
|
||||||
await page.getByTestId("add-vehicle").click();
|
await page.getByTestId("add-vehicle").click();
|
||||||
const modal = page.getByTestId("vehicle-modal");
|
const modal = page.getByTestId("vehicle-modal");
|
||||||
|
|
@ -261,6 +281,23 @@ soc:
|
||||||
await expectNoFatal(page);
|
await expectNoFatal(page);
|
||||||
await expect(disabledBadge(vehicleCard)).toBeVisible();
|
await expect(disabledBadge(vehicleCard)).toBeVisible();
|
||||||
|
|
||||||
|
// disabled vehicle is hidden from main ui
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.getByTestId("loadpoint")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId("change-vehicle").getByRole("option", { name: "blue Honda" })
|
||||||
|
).toHaveCount(0);
|
||||||
|
|
||||||
|
// no vehicles entry in more menu
|
||||||
|
const moreTab = page.getByTestId("tab-more");
|
||||||
|
await moreTab.click();
|
||||||
|
await expect(moreTab.getByRole("button", { name: "User Interface" })).toBeVisible();
|
||||||
|
await expect(moreTab.getByRole("button", { name: "Vehicles" })).not.toBeVisible();
|
||||||
|
// close more menu
|
||||||
|
await moreTab.click();
|
||||||
|
|
||||||
|
await page.goto("/#/config");
|
||||||
|
|
||||||
// re-enable by clicking the disabled card
|
// re-enable by clicking the disabled card
|
||||||
await disabledBadge(vehicleCard).click();
|
await disabledBadge(vehicleCard).click();
|
||||||
await expect(disabledBadge(vehicleCard)).toHaveCount(0);
|
await expect(disabledBadge(vehicleCard)).toHaveCount(0);
|
||||||
|
|
@ -270,6 +307,19 @@ soc:
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await expectNoFatal(page);
|
await expectNoFatal(page);
|
||||||
await expect(vehicleCard).toContainText("blue Honda");
|
await expect(vehicleCard).toContainText("blue Honda");
|
||||||
|
|
||||||
|
// vehicle is selectable on the dashboard again
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(
|
||||||
|
page.getByTestId("change-vehicle").getByRole("option", { name: "blue Honda" })
|
||||||
|
).toHaveCount(1);
|
||||||
|
|
||||||
|
// vehicle is back in the more menu vehicles modal
|
||||||
|
await moreTab.click();
|
||||||
|
await moreTab.getByRole("button", { name: "Vehicles" }).click();
|
||||||
|
const settingsModal = page.getByTestId("vehicle-settings-modal");
|
||||||
|
await expectModalVisible(settingsModal);
|
||||||
|
await expect(settingsModal.getByRole("group", { name: "blue Honda" })).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue