Config UI: make circuit/vehicle refs changeable (#21539)
This commit is contained in:
parent
4d2ba9ba0b
commit
e30102270c
8 changed files with 211 additions and 4 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
:defaultYaml="defaultYaml"
|
:defaultYaml="defaultYaml"
|
||||||
removeKey="circuits"
|
removeKey="circuits"
|
||||||
endpoint="/config/circuits"
|
endpoint="/config/circuits"
|
||||||
|
data-testid="circuits-modal"
|
||||||
@changed="$emit('changed')"
|
@changed="$emit('changed')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1007,7 +1007,7 @@ CONTINUE:
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isRoot && !instance.HasMeter() {
|
if !isRoot && !instance.HasMeter() {
|
||||||
return fmt.Errorf("circuit %s has no meter and no loadpoint assigned", dev.Config().Name)
|
log.INFO.Printf("circuit %s has no meter and no loadpoint assigned", dev.Config().Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,7 @@ loadpoints:
|
||||||
|
|
||||||
// circuit without device
|
// circuit without device
|
||||||
err = validateCircuits(lps)
|
err = validateCircuits(lps)
|
||||||
suite.Require().Error(err)
|
suite.Require().NoError(err)
|
||||||
suite.Require().Equal("circuit slave has no meter and no loadpoint assigned", err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *circuitsTestSuite) TestMissingRootCircuit() {
|
func (suite *circuitsTestSuite) TestMissingRootCircuit() {
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,7 @@
|
||||||
"chargerPowerCustomHelp": "Definiere einen eigenen Strombereich.",
|
"chargerPowerCustomHelp": "Definiere einen eigenen Strombereich.",
|
||||||
"chargerTypeLabel": "Ladetyp",
|
"chargerTypeLabel": "Ladetyp",
|
||||||
"chargingTitle": "Laden",
|
"chargingTitle": "Laden",
|
||||||
|
"circuitHelp": "Lastmanagement-Zuordnung, um die Leistungs- und Stromgrenzen nicht zu überschreiten.",
|
||||||
"circuitLabel": "Stromkreis",
|
"circuitLabel": "Stromkreis",
|
||||||
"circuitUnassigned": "nicht zugewiesen",
|
"circuitUnassigned": "nicht zugewiesen",
|
||||||
"defaultModeHelp": "Lademodus beim Anschließen des Fahrzeugs.",
|
"defaultModeHelp": "Lademodus beim Anschließen des Fahrzeugs.",
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,7 @@
|
||||||
"chargerPowerCustomHelp": "Define a custom current range.",
|
"chargerPowerCustomHelp": "Define a custom current range.",
|
||||||
"chargerTypeLabel": "Charger type",
|
"chargerTypeLabel": "Charger type",
|
||||||
"chargingTitle": "Charging",
|
"chargingTitle": "Charging",
|
||||||
|
"circuitHelp": "Load management assignment to ensure power and current limits are not exceeded.",
|
||||||
"circuitLabel": "Circuit",
|
"circuitLabel": "Circuit",
|
||||||
"circuitUnassigned": "unassigned",
|
"circuitUnassigned": "unassigned",
|
||||||
"defaultModeHelp": "Charging mode when connecting the vehicle.",
|
"defaultModeHelp": "Charging mode when connecting the vehicle.",
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ func updateLoadpointHandler() http.HandlerFunc {
|
||||||
|
|
||||||
// merge here to maintain dynamic part of the config
|
// merge here to maintain dynamic part of the config
|
||||||
other := configurable.Config().Other
|
other := configurable.Config().Other
|
||||||
if err := mergo.Merge(&other, static); err != nil {
|
if err := mergo.Merge(&other, static, mergo.WithOverride); err != nil {
|
||||||
jsonError(w, http.StatusBadRequest, err)
|
jsonError(w, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
tests/config-circuit.evcc.yaml
Executable file
30
tests/config-circuit.evcc.yaml
Executable file
|
|
@ -0,0 +1,30 @@
|
||||||
|
site:
|
||||||
|
meters:
|
||||||
|
grid: grid
|
||||||
|
|
||||||
|
meters:
|
||||||
|
- name: grid
|
||||||
|
type: template
|
||||||
|
template: demo-meter
|
||||||
|
power: 2070
|
||||||
|
currentL1: 3
|
||||||
|
currentL2: 3
|
||||||
|
currentL3: 3
|
||||||
|
|
||||||
|
loadpoints:
|
||||||
|
- title: Carport
|
||||||
|
charger: charger
|
||||||
|
circuit: main
|
||||||
|
|
||||||
|
circuits:
|
||||||
|
- name: main
|
||||||
|
meter: grid
|
||||||
|
maxcurrent: 16
|
||||||
|
|
||||||
|
chargers:
|
||||||
|
- name: charger
|
||||||
|
type: template
|
||||||
|
template: demo-charger
|
||||||
|
status: C
|
||||||
|
enabled: true
|
||||||
|
power: 1000
|
||||||
175
tests/config-circuit.spec.js
Normal file
175
tests/config-circuit.spec.js
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { start, stop, restart, baseUrl } from "./evcc";
|
||||||
|
import {
|
||||||
|
enableExperimental,
|
||||||
|
expectModalVisible,
|
||||||
|
expectModalHidden,
|
||||||
|
editorClear,
|
||||||
|
editorType,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
const CONFIG_YAML = "config-circuit.evcc.yaml";
|
||||||
|
const CONFIG_EMPTY = "config-empty.evcc.yaml";
|
||||||
|
|
||||||
|
test.use({ baseURL: baseUrl() });
|
||||||
|
|
||||||
|
test.afterEach(async () => {
|
||||||
|
await stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("circuit", async () => {
|
||||||
|
test("from yaml", async ({ page }) => {
|
||||||
|
await start(CONFIG_YAML);
|
||||||
|
|
||||||
|
await page.goto("/#/config");
|
||||||
|
await enableExperimental(page);
|
||||||
|
|
||||||
|
await expect(page.getByTestId("loadpoint")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("loadpoint")).toContainText(["Power", "1.0 kW"].join(""));
|
||||||
|
|
||||||
|
await expect(page.getByTestId("grid")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("grid")).toContainText(["Power", "2.1 kW"].join(""));
|
||||||
|
await expect(page.getByTestId("grid")).toContainText(
|
||||||
|
["Current L1, L2, L3", "3.0 · 3.0 · 3.0 A"].join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page.getByTestId("circuits")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(["Power", "2.1 kW"].join(""));
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["Current", "3.0 A / 16.0 A"].join("")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("via ui", async ({ page }) => {
|
||||||
|
await start(CONFIG_EMPTY);
|
||||||
|
|
||||||
|
await page.goto("/#/config");
|
||||||
|
await enableExperimental(page);
|
||||||
|
|
||||||
|
// add grid meter
|
||||||
|
await page.getByRole("button", { name: "Add grid meter" }).click();
|
||||||
|
const meterModal = page.getByTestId("meter-modal");
|
||||||
|
await expectModalVisible(meterModal);
|
||||||
|
await meterModal.getByLabel("Manufacturer").selectOption("Demo meter");
|
||||||
|
await meterModal.getByLabel("Power").fill("2070");
|
||||||
|
await page.getByRole("button", { name: "Show advanced settings" }).click();
|
||||||
|
await meterModal.getByLabel("L1 current").fill("3");
|
||||||
|
await meterModal.getByLabel("L2 current").fill("3");
|
||||||
|
await meterModal.getByLabel("L3 current").fill("3");
|
||||||
|
await meterModal.getByRole("button", { name: "Save" }).click();
|
||||||
|
await expectModalHidden(meterModal);
|
||||||
|
|
||||||
|
// add loadpoint and charger
|
||||||
|
const lpModal = page.getByTestId("loadpoint-modal");
|
||||||
|
await page.getByRole("button", { name: "Add charge point" }).click();
|
||||||
|
await expectModalVisible(lpModal);
|
||||||
|
await lpModal.getByLabel("Title").fill("Carport");
|
||||||
|
|
||||||
|
// add charger
|
||||||
|
await lpModal.getByRole("button", { name: "Add charger" }).click();
|
||||||
|
const chargerModal = page.getByTestId("charger-modal");
|
||||||
|
await expectModalVisible(chargerModal);
|
||||||
|
await chargerModal.getByLabel("Manufacturer").selectOption("Demo charger");
|
||||||
|
await chargerModal.getByLabel("Charge status").selectOption("C");
|
||||||
|
await chargerModal.getByLabel("Power").fill("1000");
|
||||||
|
await chargerModal.getByRole("button", { name: "Save" }).click();
|
||||||
|
await expectModalHidden(chargerModal);
|
||||||
|
await expectModalVisible(lpModal);
|
||||||
|
|
||||||
|
await lpModal.getByRole("button", { name: "Save" }).click();
|
||||||
|
await expectModalHidden(lpModal);
|
||||||
|
|
||||||
|
// add circuit via ui as yaml input
|
||||||
|
await page.getByTestId("circuits").getByRole("button", { name: "edit" }).click();
|
||||||
|
const circuitsModal = page.getByTestId("circuits-modal");
|
||||||
|
await expectModalVisible(circuitsModal);
|
||||||
|
|
||||||
|
const editor = circuitsModal.getByTestId("yaml-editor");
|
||||||
|
await editorClear(editor);
|
||||||
|
await editorType(editor, [
|
||||||
|
// prettier-ignore
|
||||||
|
"- name: main",
|
||||||
|
" meter: db:1",
|
||||||
|
"maxcurrent: 16",
|
||||||
|
"Shift+Tab",
|
||||||
|
"- name: house",
|
||||||
|
" title: House",
|
||||||
|
"maxcurrent: 10",
|
||||||
|
"parent: main",
|
||||||
|
"Shift+Tab",
|
||||||
|
"- name: garage",
|
||||||
|
" title: Garage",
|
||||||
|
"maxcurrent: 8",
|
||||||
|
"parent: main",
|
||||||
|
]);
|
||||||
|
|
||||||
|
await circuitsModal.getByRole("button", { name: "Save" }).click();
|
||||||
|
await expectModalHidden(circuitsModal);
|
||||||
|
|
||||||
|
// restart
|
||||||
|
const restartButton = await page
|
||||||
|
.getByTestId("bottom-banner")
|
||||||
|
.getByRole("button", { name: "Restart" });
|
||||||
|
await expect(restartButton).toBeVisible();
|
||||||
|
await restart(CONFIG_EMPTY);
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
// assign loadpoint to circuit
|
||||||
|
await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click();
|
||||||
|
await expectModalVisible(lpModal);
|
||||||
|
await lpModal.getByLabel("Circuit").selectOption("Garage [garage]");
|
||||||
|
await lpModal.getByLabel("Circuit").selectOption("House [house]");
|
||||||
|
await lpModal.getByRole("button", { name: "Save" }).click();
|
||||||
|
await expectModalHidden(lpModal);
|
||||||
|
|
||||||
|
// save, restart and check values
|
||||||
|
await expect(restartButton).toBeVisible();
|
||||||
|
await restart(CONFIG_EMPTY);
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
// verify the configuration matches the yaml test
|
||||||
|
await expect(page.getByTestId("loadpoint")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("loadpoint")).toContainText(["Power", "1.0 kW"].join(""));
|
||||||
|
|
||||||
|
await expect(page.getByTestId("grid")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("grid")).toContainText(["Power", "2.1 kW"].join(""));
|
||||||
|
await expect(page.getByTestId("grid")).toContainText(
|
||||||
|
["Current L1, L2, L3", "3.0 · 3.0 · 3.0 A"].join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page.getByTestId("circuits")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["(main)", "Power", "2.1 kW", "Current", "3.0 A / 16.0 A"].join("")
|
||||||
|
);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["House (house)", "Power", "1.0 kW", "Current", "6.0 A / 10.0 A"].join("")
|
||||||
|
);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["Garage (garage)", "Power", "0.0 kW", "Current", "0.0 A / 8.0 A"].join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
// assign to garage
|
||||||
|
await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click();
|
||||||
|
await expectModalVisible(lpModal);
|
||||||
|
await lpModal.getByLabel("Circuit").selectOption("Garage [garage]");
|
||||||
|
await lpModal.getByRole("button", { name: "Save" }).click();
|
||||||
|
await expectModalHidden(lpModal);
|
||||||
|
|
||||||
|
// save, restart and check values
|
||||||
|
await expect(restartButton).toBeVisible();
|
||||||
|
await restart(CONFIG_EMPTY);
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
// verify circuits
|
||||||
|
await expect(page.getByTestId("circuits")).toHaveCount(1);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["(main)", "Power", "2.1 kW", "Current", "3.0 A / 16.0 A"].join("")
|
||||||
|
);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["House (house)", "Power", "0.0 kW", "Current", "0.0 A / 10.0 A"].join("")
|
||||||
|
);
|
||||||
|
await expect(page.getByTestId("circuits")).toContainText(
|
||||||
|
["Garage (garage)", "Power", "1.0 kW", "Current", "6.0 A / 8.0 A"].join("")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue