+
({ key, name: this.$t(`main.mode.${key}`) }));
+ },
showCircuit() {
return this.circuits.length > 0 || !!this.values.circuit;
},
diff --git a/assets/js/components/Config/defaultYaml/switchsocketCharger.yaml b/assets/js/components/Config/defaultYaml/switchsocketCharger.yaml
index 7b24dfa22..29669429a 100644
--- a/assets/js/components/Config/defaultYaml/switchsocketCharger.yaml
+++ b/assets/js/components/Config/defaultYaml/switchsocketCharger.yaml
@@ -12,6 +12,7 @@ power: # charge power reading in W
standbypower: 20 # in W, below values will be treaded as inactive
features:
+ - switchdevice # on/off only, no current control
- integrateddevice # no charging sessions, no connected vehicles
diff --git a/assets/js/components/Config/defaultYaml/switchsocketHeater.yaml b/assets/js/components/Config/defaultYaml/switchsocketHeater.yaml
index 5bb60110f..e682d5524 100644
--- a/assets/js/components/Config/defaultYaml/switchsocketHeater.yaml
+++ b/assets/js/components/Config/defaultYaml/switchsocketHeater.yaml
@@ -12,6 +12,7 @@ power: # charge power reading in W
standbypower: 20 # in W, below values will be treaded as inactive
features:
+ - switchdevice # on/off only, no current control
- heating # treat as a heating device
- integrateddevice # no charging sessions, no connected vehicles
diff --git a/assets/js/components/Loadpoints/Loadpoint.vue b/assets/js/components/Loadpoints/Loadpoint.vue
index a5a24b0e6..663f5f6ce 100644
--- a/assets/js/components/Loadpoints/Loadpoint.vue
+++ b/assets/js/components/Loadpoints/Loadpoint.vue
@@ -167,6 +167,7 @@ export default defineComponent({
chargerFeatureIntegratedDevice: Boolean,
chargerFeatureHeating: Boolean,
chargerFeatureContinuous: Boolean,
+ chargerFeatureSwitchDevice: Boolean,
chargerIcon: String as PropType,
// heating display range (ui-only)
@@ -281,6 +282,9 @@ export default defineComponent({
continuous() {
return this.chargerFeatureContinuous;
},
+ switchDevice() {
+ return this.chargerFeatureSwitchDevice;
+ },
phasesProps() {
return this.collectProps(Phases);
},
diff --git a/assets/js/components/Loadpoints/Mode.vue b/assets/js/components/Loadpoints/Mode.vue
index fd9884966..4921f45a8 100644
--- a/assets/js/components/Loadpoints/Mode.vue
+++ b/assets/js/components/Loadpoints/Mode.vue
@@ -26,13 +26,14 @@ export default defineComponent({
mode: String,
pvPossible: Boolean,
smartCostAvailable: Boolean,
+ switchDevice: Boolean,
},
emits: ["updated"],
computed: {
modes(): CHARGE_MODE[] {
if (this.pvPossible) {
- return [OFF, PV, MINPV, NOW];
+ return this.switchDevice ? [OFF, PV, NOW] : [OFF, PV, MINPV, NOW];
}
if (this.smartCostAvailable) {
return [OFF, PV, NOW];
diff --git a/assets/js/components/Loadpoints/SettingsModal.vue b/assets/js/components/Loadpoints/SettingsModal.vue
index 82e2e3640..cc441bd50 100644
--- a/assets/js/components/Loadpoints/SettingsModal.vue
+++ b/assets/js/components/Loadpoints/SettingsModal.vue
@@ -81,7 +81,7 @@
-
+
@@ -194,6 +194,9 @@ export default defineComponent({
minCurrent() {
return this.loadpoint?.minCurrent;
},
+ switchDevice() {
+ return this.loadpoint?.chargerFeatureSwitchDevice;
+ },
batteryBoostLimit() {
return this.loadpoint?.batteryBoostLimit;
},
diff --git a/i18n/de.json b/i18n/de.json
index 9a54df23b..3f45afffb 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -193,6 +193,7 @@
"singlePhase": "Einphasig",
"soc": "Ladestand",
"solarForecast": "PV-Vorhersage",
+ "switchDevice": "Stromregelung",
"temp": "Temperatur",
"topic": "Thema",
"url": "URL",
diff --git a/i18n/en.json b/i18n/en.json
index 806c9deeb..2bd7b8b15 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -193,6 +193,7 @@
"singlePhase": "Single phase",
"soc": "Charge",
"solarForecast": "Solar forecast",
+ "switchDevice": "Current control",
"temp": "Temperature",
"topic": "Topic",
"url": "URL",
diff --git a/server/http_config_helper.go b/server/http_config_helper.go
index df62e1791..3ace73443 100644
--- a/server/http_config_helper.go
+++ b/server/http_config_helper.go
@@ -349,6 +349,10 @@ func testInstance(instance any) map[string]testResult {
makeResult("integratedDevice", true, nil)
}
+ if hasFeature(instance, api.SwitchDevice) {
+ makeResult("switchDevice", true, nil)
+ }
+
if dev, ok := api.Cap[api.IconDescriber](instance); ok && dev.Icon() != "" {
makeResult("icon", dev.Icon(), nil)
}
diff --git a/tests/config-loadpoint.spec.ts b/tests/config-loadpoint.spec.ts
index 548575445..3bf55a0af 100644
--- a/tests/config-loadpoint.spec.ts
+++ b/tests/config-loadpoint.spec.ts
@@ -508,6 +508,45 @@ power:
await expect(chargerEditor).toContainText("value: 'C'");
await expect(chargerEditor).toContainText("value: 11000");
});
+
+ test("user-defined switch socket (pool pump)", async ({ page }) => {
+ await start();
+ await page.goto("/#/config");
+
+ // add loadpoint
+ await newLoadpoint(page, "Pool pump");
+ const lpModal = page.getByTestId("loadpoint-modal");
+ await lpModal.getByRole("button", { name: "Add charger" }).click();
+
+ const chargerModal = page.getByTestId("charger-modal");
+ await expectModalVisible(chargerModal);
+ await chargerModal.getByLabel("Manufacturer").selectOption("User-defined switch socket");
+ await page.waitForLoadState("networkidle");
+ await expect(chargerModal.getByTestId("yaml-editor")).toContainText("switchdevice");
+
+ const result = chargerModal.getByTestId("test-result");
+ await result.getByRole("link", { name: "validate" }).click();
+ await expect(result).toContainText("Status: successful");
+ await expect(result).toContainText(["Power", "11.0 kW"].join(""));
+ await expect(result).toContainText(["Current control", "no"].join(""));
+
+ await chargerModal.getByRole("button", { name: "Save" }).click();
+ await expectModalHidden(chargerModal);
+ await expectModalVisible(lpModal);
+
+ const modeSelect = lpModal.getByLabel("Default mode");
+ await expect(modeSelect.getByRole("option", { name: "Solar", exact: true })).toHaveCount(1);
+ await expect(modeSelect.getByRole("option", { name: "Min+Solar" })).toHaveCount(0);
+
+ await expect(lpModal.getByText("Electrics")).toHaveCount(0);
+ await expect(lpModal.getByText("Charger type", { exact: true })).toHaveCount(0);
+
+ // create
+ await lpModal.getByRole("button", { name: "Save" }).click();
+ await expectModalHidden(lpModal);
+ await expect(page.getByTestId("loadpoint")).toHaveCount(1);
+ await expect(page.getByTestId("loadpoint")).toContainText("Pool pump");
+ });
});
test.describe("heating loadpoint", async () => {
diff --git a/tests/heating.evcc.yaml b/tests/heating.evcc.yaml
index 65cce86ad..83b6ef4c0 100644
--- a/tests/heating.evcc.yaml
+++ b/tests/heating.evcc.yaml
@@ -48,4 +48,4 @@ chargers:
source: js
script: |
55
- features: [integrateddevice, heating]
+ features: [integrateddevice, heating, switchdevice]
diff --git a/tests/heating.spec.ts b/tests/heating.spec.ts
index 468b23d2e..da6e513d6 100644
--- a/tests/heating.spec.ts
+++ b/tests/heating.spec.ts
@@ -1,5 +1,6 @@
import { test, expect } from "@playwright/test";
import { start, stop, baseUrl } from "./evcc";
+import { expectModalVisible } from "./utils";
test.use({ baseURL: baseUrl() });
@@ -27,3 +28,23 @@ test.describe("loadpoint", async () => {
await expect(page.getByTestId("limit-soc")).toContainText("69.0°C");
});
});
+
+test.describe("integrated device", async () => {
+ test("no Min+Solar mode for switch device", async ({ page }) => {
+ const lp = page.getByTestId("loadpoint").first();
+ const mode = lp.getByTestId("mode");
+ await expect(mode.getByRole("button", { name: "Solar", exact: true })).toBeVisible();
+ await expect(mode.getByRole("button", { name: "Min+Solar" })).toHaveCount(0);
+ });
+
+ test("min current and phases but no max current for switch device", async ({ page }) => {
+ const lp = page.getByTestId("loadpoint").first();
+ await lp.getByTestId("loadpoint-settings-button").last().click();
+ const modal = page.getByTestId("loadpoint-settings-modal").first();
+ await expectModalVisible(modal);
+ // min current and phases feed the solar switch-on threshold and stay
+ await expect(modal.getByText("Min. Current")).toBeVisible();
+ await expect(modal.getByText("Phases")).toBeVisible();
+ await expect(modal.getByText("Max. Current")).toHaveCount(0);
+ });
+});