diff --git a/cmd/setup.go b/cmd/setup.go index 6a77f1c24..4cc932170 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -538,9 +538,14 @@ func configureVehicles(static []config.Named, names ...string) error { } } - instance, err := vehicleInstance(cc) - if err != nil { - return fmt.Errorf("cannot create vehicle '%s': %w", cc.Name, err) + // disabled vehicles are not instantiated + var instance api.Vehicle + 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() diff --git a/core/loadpoint.go b/core/loadpoint.go index 154686ac0..13f2e6e0e 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -253,7 +253,8 @@ func NewLoadpointFromConfig(log *util.Logger, settings settings.Settings, collec } lp.defaultVehicle = dev.Instance() 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) } } diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go index 59f55c5c9..d427b0f1d 100644 --- a/core/loadpoint_test.go +++ b/core/loadpoint_test.go @@ -12,7 +12,9 @@ import ( "github.com/evcc-io/evcc/core/soc" "github.com/evcc-io/evcc/messenger" "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/config" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "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 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())) +} diff --git a/core/site_vehicles.go b/core/site_vehicles.go index aa457808c..cedccb264 100644 --- a/core/site_vehicles.go +++ b/core/site_vehicles.go @@ -104,14 +104,7 @@ type vehicles struct { } func (vv *vehicles) Instances() []api.Vehicle { - devs := config.Vehicles().Devices() - - res := make([]api.Vehicle, 0, len(devs)) - for _, dev := range devs { - res = append(res, dev.Instance()) - } - - return res + return config.Instances(config.Vehicles().Devices()) } func (vv *vehicles) Settings() []vehicle.API { @@ -119,6 +112,10 @@ func (vv *vehicles) Settings() []vehicle.API { res := make([]vehicle.API, 0, len(devs)) for _, dev := range devs { + // skip disabled vehicles + if dev.Instance() == nil { + continue + } res = append(res, vehicle.Adapter(vv.log, dev)) } diff --git a/tests/config-disable.spec.ts b/tests/config-disable.spec.ts index 744bb07e7..5f5b66e19 100644 --- a/tests/config-disable.spec.ts +++ b/tests/config-disable.spec.ts @@ -239,6 +239,10 @@ test.describe("disable / enable", async () => { await start(); await page.goto("/#/config"); + // loadpoint so the vehicle shows up on the dashboard + await createLoadpoint(page, "Carport"); + await page.reload(); + // add user-defined vehicle await page.getByTestId("add-vehicle").click(); const modal = page.getByTestId("vehicle-modal"); @@ -277,6 +281,23 @@ soc: await expectNoFatal(page); 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 await disabledBadge(vehicleCard).click(); await expect(disabledBadge(vehicleCard)).toHaveCount(0); @@ -286,6 +307,19 @@ soc: await page.reload(); await expectNoFatal(page); 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(); }); });