Config UI: fix preserving modbus fields (#25029)

This commit is contained in:
Michael Geers 2025-11-05 00:12:00 -08:00 • committed by GitHub
parent 6591514456
commit 2a0429aa76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 119 additions and 19 deletions

View file

@ -110,12 +110,21 @@ func templateForConfig(class templates.Class, conf map[string]any) (templates.Te
func filterValidTemplateParams(tmpl *templates.Template, conf map[string]any) map[string]any {
res := make(map[string]any)
// check if template has modbus capability
hasModbus := len(tmpl.ModbusChoices()) > 0
for k, v := range conf {
if k == "template" {
res[k] = v
continue
}
// preserve modbus fields if template supports modbus
if hasModbus && slices.Contains(templates.ModbusParams, k) {
res[k] = v
continue
}
if i, _ := tmpl.ParamByName(k); i >= 0 {
res[k] = v
}

View file

@ -0,0 +1,57 @@
import { test, expect, type Page, type Locator } from "@playwright/test";
import { start, stop, baseUrl } from "./evcc";
import { expectModalVisible, enableExperimental } from "./utils";
const CONFIG_MODBUS_FIELDS = "config-modbus-fields.sql";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start(undefined, CONFIG_MODBUS_FIELDS);
});
test.afterAll(async () => {
await stop();
});
async function openMeterModal(page: Page, title: string): Promise<Locator> {
await page.goto("/#/config");
await enableExperimental(page, true);
await page
.getByTestId("pv")
.filter({ hasText: title })
.getByRole("button", { name: "edit" })
.click();
const modal = page.getByTestId("meter-modal");
await expectModalVisible(modal);
return modal;
}
test.describe("modbus fields", async () => {
test("tcpip", async ({ page }) => {
const modal = await openMeterModal(page, "TCP Test");
await expect(page.getByLabel("Network")).toBeChecked();
await expect(page.getByLabel("TCP")).toBeChecked();
await expect(modal.getByLabel("IP address or hostname")).toHaveValue("192.168.1.10");
await expect(modal.getByLabel("Port", { exact: true })).toHaveValue("5020");
await expect(modal.getByLabel("Modbus ID")).toHaveValue("10");
});
test("rs485tcpip", async ({ page }) => {
const modal = await openMeterModal(page, "RTU/IP Test");
await expect(page.getByLabel("Network")).toBeChecked();
await expect(page.getByLabel("RTU")).toBeChecked();
await expect(modal.getByLabel("IP address or hostname")).toHaveValue("192.168.1.20");
await expect(modal.getByLabel("Port", { exact: true })).toHaveValue("8899");
await expect(modal.getByLabel("Modbus ID")).toHaveValue("20");
});
test("rs485serial", async ({ page }) => {
const modal = await openMeterModal(page, "Serial Test");
await expect(page.getByLabel("Serial / USB")).toBeChecked();
await expect(modal.getByLabel("Device name")).toHaveValue("/dev/ttyUSB5");
await expect(modal.getByLabel("Baud rate")).toHaveValue("19200");
await expect(modal.getByLabel("ComSet")).toHaveValue("8E1");
await expect(modal.getByLabel("Modbus ID")).toHaveValue("30");
});
});

View file

@ -0,0 +1,24 @@
BEGIN;
CREATE TABLE `configs` (
`id` integer PRIMARY KEY AUTOINCREMENT
, `class` integer
, `type` text
, `title` text
, `icon` text
, `product` text
, `value` text
);
CREATE TABLE `settings` (
`key` text
, `value` text
, PRIMARY KEY(`key`)
);
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(1, 2, 'template', 'TCP Test', '', 'SunSpec Inverter', '{"host":"192.168.1.10","id":10,"modbus":"tcpip","port":5020,"template":"sunspec-inverter","usage":"pv"}');
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(2, 2, 'template', 'RTU/IP Test', '', 'SunSpec Inverter', '{"host":"192.168.1.20","id":20,"modbus":"rs485tcpip","port":8899,"template":"sunspec-inverter","usage":"pv"}');
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(3, 2, 'template', 'Serial Test', '', 'SunSpec Inverter', '{"baudrate":19200,"comset":"8E1","device":"/dev/ttyUSB5","id":30,"modbus":"rs485serial","template":"sunspec-inverter","usage":"pv"}');
INSERT INTO settings("key", value) VALUES('pvMeters', 'db:1,db:2,db:3');
COMMIT;

View file

@ -74,19 +74,19 @@ func (t *Template) ModbusValues(renderMode int, values map[string]any) {
var defaultValue string
switch p.Name {
case ModbusParamNameId:
case ModbusParamId:
if modbusParam.ID != 0 {
defaultValue = strconv.Itoa(modbusParam.ID)
}
case ModbusParamNamePort:
case ModbusParamPort:
if modbusParam.Port != 0 {
defaultValue = strconv.Itoa(modbusParam.Port)
}
case ModbusParamNameBaudrate:
case ModbusParamBaudrate:
if modbusParam.Baudrate != 0 {
defaultValue = strconv.Itoa(modbusParam.Baudrate)
}
case ModbusParamNameComset:
case ModbusParamComset:
if modbusParam.Comset != "" {
defaultValue = modbusParam.Comset
}

View file

@ -25,14 +25,14 @@ const (
ModbusKeyTCPIP = "tcpip"
ModbusKeyUDP = "udp"
ModbusParamNameId = "id"
ModbusParamNameDevice = "device"
ModbusParamNameBaudrate = "baudrate"
ModbusParamNameComset = "comset"
ModbusParamNameURI = "uri"
ModbusParamNameHost = "host"
ModbusParamNamePort = "port"
ModbusParamNameRTU = "rtu"
ModbusParamId = "id"
ModbusParamDevice = "device"
ModbusParamBaudrate = "baudrate"
ModbusParamComset = "comset"
ModbusParamURI = "uri"
ModbusParamHost = "host"
ModbusParamPort = "port"
ModbusParamRTU = "rtu"
)
const (
@ -41,7 +41,19 @@ const (
RenderModeInstance
)
var ValidModbusChoices = []string{ModbusChoiceRS485, ModbusChoiceTCPIP, ModbusChoiceUDP}
var (
ValidModbusChoices = []string{ModbusChoiceRS485, ModbusChoiceTCPIP, ModbusChoiceUDP}
// ModbusParams contains all field names used by modbus templates
ModbusParams = []string{
ModbusParamId, ModbusParamDevice, ModbusParamBaudrate, ModbusParamComset,
ModbusParamURI, ModbusParamHost, ModbusParamPort, ModbusParamRTU,
}
ModbusConnectionTypes = []string{
ModbusKeyTCPIP, ModbusKeyUDP, ModbusKeyRS485Serial, ModbusKeyRS485TCPIP,
}
)
const (
CapabilityISO151182 = "iso151182" // ISO 15118-2 support
@ -63,12 +75,10 @@ const (
var ValidRequirements = []string{RequirementEEBUS, RequirementMQTT, RequirementSponsorship, RequirementSkipTest}
var predefinedTemplateProperties = []string{
"type", "template", "name",
ModbusParamNameId, ModbusParamNameDevice, ModbusParamNameBaudrate, ModbusParamNameComset,
ModbusParamNameURI, ModbusParamNameHost, ModbusParamNamePort, ModbusParamNameRTU,
ModbusKeyTCPIP, ModbusKeyUDP, ModbusKeyRS485Serial, ModbusKeyRS485TCPIP,
}
var predefinedTemplateProperties = append(
[]string{"type", "template", "name"},
append(ModbusParams, ModbusConnectionTypes...)...,
)
// TextLanguage contains language-specific texts
type TextLanguage struct {