Loadpoint: adapt UI for integrated and switch devices (#30909)
Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
parent
5f9047531d
commit
eed0649eaa
13 changed files with 101 additions and 12 deletions
|
|
@ -220,6 +220,9 @@ export default {
|
|||
return `${this.fmtNumber(distanceValue(value), 0)} ${distanceUnit()}`;
|
||||
case "chargeStatus":
|
||||
return value ? this.$t(`config.deviceValue.chargeStatus${value}`) : "-";
|
||||
case "switchDevice":
|
||||
// switch device means no current control
|
||||
return this.$t(`config.deviceValue.${value ? "no" : "yes"}`);
|
||||
case "price":
|
||||
case "gridPrice":
|
||||
case "feedinPrice":
|
||||
|
|
|
|||
|
|
@ -114,13 +114,7 @@
|
|||
v-model="values.defaultMode"
|
||||
type="Choice"
|
||||
class="w-100"
|
||||
:choice="[
|
||||
{ key: '', name: '---' },
|
||||
{ key: 'off', name: $t('main.mode.off') },
|
||||
{ key: 'pv', name: $t('main.mode.pv') },
|
||||
{ key: 'minpv', name: $t('main.mode.minpv') },
|
||||
{ key: 'now', name: $t('main.mode.now') },
|
||||
]"
|
||||
:choice="defaultModeOptions"
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
|
|
@ -306,7 +300,7 @@
|
|||
/>
|
||||
</FormRow>
|
||||
|
||||
<h6>
|
||||
<h6 v-if="!chargerIsSwitchDevice">
|
||||
{{ $t("config.loadpoint.electricalTitle") }}
|
||||
<small class="text-muted">{{
|
||||
$t("config.loadpoint.electricalSubtitle")
|
||||
|
|
@ -314,6 +308,7 @@
|
|||
</h6>
|
||||
|
||||
<FormRow
|
||||
v-if="!chargerIsSwitchDevice"
|
||||
id="chargerPower"
|
||||
:label="$t('config.loadpoint.chargerTypeLabel')"
|
||||
:help="
|
||||
|
|
@ -346,7 +341,10 @@
|
|||
/>
|
||||
</FormRow>
|
||||
|
||||
<div v-if="chargerPower === 'other'" class="row ms-3 mb-5">
|
||||
<div
|
||||
v-if="!chargerIsSwitchDevice && chargerPower === 'other'"
|
||||
class="row ms-3 mb-5"
|
||||
>
|
||||
<FormRow
|
||||
id="loadpointMinCurrent"
|
||||
:label="$t('config.loadpoint.minCurrentLabel')"
|
||||
|
|
@ -634,6 +632,7 @@ import InvalidReferenceAlert from "./InvalidReferenceAlert.vue";
|
|||
import { handleError, customChargerName, createDeviceUtils } from "./DeviceModal";
|
||||
import { getModal, openModal, replaceModal, closeModal } from "@/configModal";
|
||||
import {
|
||||
CHARGE_MODE,
|
||||
LOADPOINT_TYPE,
|
||||
type DeviceType,
|
||||
type LoadpointType,
|
||||
|
|
@ -646,6 +645,8 @@ import {
|
|||
|
||||
const nsPerMin = 60 * 1e9;
|
||||
|
||||
const { OFF, PV, MINPV, NOW } = CHARGE_MODE;
|
||||
|
||||
const defaultValues = {
|
||||
id: undefined,
|
||||
title: "",
|
||||
|
|
@ -763,6 +764,9 @@ export default {
|
|||
chargerIsIntegratedDevice() {
|
||||
return this.chargerStatus.integratedDevice?.value || false;
|
||||
},
|
||||
chargerIsSwitchDevice() {
|
||||
return this.chargerStatus.switchDevice?.value || false;
|
||||
},
|
||||
chargerIsHeating() {
|
||||
return this.chargerStatus.heating?.value === true;
|
||||
},
|
||||
|
|
@ -797,6 +801,12 @@ export default {
|
|||
{ value: 3, name: this.$t("config.loadpoint.phases3p") },
|
||||
];
|
||||
},
|
||||
defaultModeOptions(): { key: CHARGE_MODE; name: string }[] {
|
||||
// empty option is provided by PropertyField placeholder
|
||||
// switch devices have no current control, so minpv does not apply
|
||||
const modes = this.chargerIsSwitchDevice ? [OFF, PV, NOW] : [OFF, PV, MINPV, NOW];
|
||||
return modes.map((key) => ({ key, name: this.$t(`main.mode.${key}`) }));
|
||||
},
|
||||
showCircuit() {
|
||||
return this.circuits.length > 0 || !!this.values.circuit;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ export default defineComponent({
|
|||
chargerFeatureIntegratedDevice: Boolean,
|
||||
chargerFeatureHeating: Boolean,
|
||||
chargerFeatureContinuous: Boolean,
|
||||
chargerFeatureSwitchDevice: Boolean,
|
||||
chargerIcon: String as PropType<string | null>,
|
||||
|
||||
// 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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div v-if="!switchDevice" class="mb-3 row">
|
||||
<label :for="formId('maxcurrent')" class="col-sm-4 col-form-label pt-0 pt-sm-2">
|
||||
{{ $t("main.loadpointSettings.maxCurrent.label") }}
|
||||
</label>
|
||||
|
|
@ -194,6 +194,9 @@ export default defineComponent({
|
|||
minCurrent() {
|
||||
return this.loadpoint?.minCurrent;
|
||||
},
|
||||
switchDevice() {
|
||||
return this.loadpoint?.chargerFeatureSwitchDevice;
|
||||
},
|
||||
batteryBoostLimit() {
|
||||
return this.loadpoint?.batteryBoostLimit;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@
|
|||
"singlePhase": "Einphasig",
|
||||
"soc": "Ladestand",
|
||||
"solarForecast": "PV-Vorhersage",
|
||||
"switchDevice": "Stromregelung",
|
||||
"temp": "Temperatur",
|
||||
"topic": "Thema",
|
||||
"url": "URL",
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@
|
|||
"singlePhase": "Single phase",
|
||||
"soc": "Charge",
|
||||
"solarForecast": "Solar forecast",
|
||||
"switchDevice": "Current control",
|
||||
"temp": "Temperature",
|
||||
"topic": "Topic",
|
||||
"url": "URL",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -48,4 +48,4 @@ chargers:
|
|||
source: js
|
||||
script: |
|
||||
55
|
||||
features: [integrateddevice, heating]
|
||||
features: [integrateddevice, heating, switchdevice]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue