From de0a49789e87a1f8cb1065cb33eef3948f99ac17 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Fri, 6 Jun 2025 09:27:44 +0200 Subject: [PATCH 1/8] Docs: introduce product identifier --- go.mod | 2 ++ go.sum | 4 +++ .../definition/charger/ocpp-abb-tac.yaml | 2 +- templates/definition/charger/ocpp-alfen.yaml | 2 +- templates/definition/charger/ocpp-goe.yaml | 8 ++--- templates/definition/charger/ocpp-zaptec.yaml | 2 +- util/templates/documentation.go | 1 + util/templates/documentation.tpl | 30 +++++++++++++++++++ util/templates/generate/main.go | 9 ++++-- util/templates/types.go | 8 +++++ 10 files changed, 59 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 7bf9f24dcb5..8672a6fb1db 100644 --- a/go.mod +++ b/go.mod @@ -151,6 +151,8 @@ require ( github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/gosimple/slug v1.15.0 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 9dc8f2a1c9e..c26aaaf1ee4 100644 --- a/go.sum +++ b/go.sum @@ -308,6 +308,10 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= +github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/gregdel/pushover v1.3.1 h1:4bMLITOZ15+Zpi6qqoGqOPuVHCwSUvMCgVnN5Xhilfo= github.com/gregdel/pushover v1.3.1/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= diff --git a/templates/definition/charger/ocpp-abb-tac.yaml b/templates/definition/charger/ocpp-abb-tac.yaml index ec55c1da4f5..b23135099e0 100644 --- a/templates/definition/charger/ocpp-abb-tac.yaml +++ b/templates/definition/charger/ocpp-abb-tac.yaml @@ -3,7 +3,7 @@ covers: ["ocpp-abb"] products: - brand: ABB description: - generic: Terra AC + generic: Terra AC (OCPP) capabilities: ["mA", "rfid"] requirements: evcc: ["sponsorship", "skiptest"] diff --git a/templates/definition/charger/ocpp-alfen.yaml b/templates/definition/charger/ocpp-alfen.yaml index aa5bac0ee51..cc1c7865e4f 100644 --- a/templates/definition/charger/ocpp-alfen.yaml +++ b/templates/definition/charger/ocpp-alfen.yaml @@ -2,7 +2,7 @@ template: ocpp-alfen products: - brand: Alfen description: - generic: Eve + generic: Eve (OCPP) capabilities: ["mA", "rfid", "1p3p"] requirements: evcc: ["sponsorship", "skiptest"] diff --git a/templates/definition/charger/ocpp-goe.yaml b/templates/definition/charger/ocpp-goe.yaml index e865fc5fe10..aeafbede37c 100644 --- a/templates/definition/charger/ocpp-goe.yaml +++ b/templates/definition/charger/ocpp-goe.yaml @@ -3,16 +3,16 @@ covers: ["ocpp-fronius-wattpilot"] products: - brand: go-e description: - generic: Charger V3 + generic: Charger V3 (OCPP) - brand: go-e description: - generic: Charger Gemini + generic: Charger Gemini (OCPP) - brand: go-e description: - generic: Charger PRO + generic: Charger PRO (OCPP) - brand: Fronius description: - generic: Wattpilot + generic: Wattpilot (OCPP) capabilities: ["rfid", "1p3p"] requirements: evcc: ["sponsorship", "skiptest"] diff --git a/templates/definition/charger/ocpp-zaptec.yaml b/templates/definition/charger/ocpp-zaptec.yaml index 34e9f31dcd0..741841b3cd4 100644 --- a/templates/definition/charger/ocpp-zaptec.yaml +++ b/templates/definition/charger/ocpp-zaptec.yaml @@ -2,7 +2,7 @@ template: ocpp-zaptec products: - brand: Zaptec description: - generic: Go + generic: Go (OCPP) capabilities: ["rfid"] requirements: description: diff --git a/util/templates/documentation.go b/util/templates/documentation.go index f12f6f786a7..0b491f1e30a 100644 --- a/util/templates/documentation.go +++ b/util/templates/documentation.go @@ -91,6 +91,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er data := map[string]interface{}{ "Template": t.Template, + "ProductIdentifier": product.Identifier(), "ProductBrand": product.Brand, "ProductDescription": product.Description.String(lang), "ProductGroup": t.GroupTitle(lang), diff --git a/util/templates/documentation.tpl b/util/templates/documentation.tpl index 5a7941fcf30..9c395c3fb27 100644 --- a/util/templates/documentation.tpl +++ b/util/templates/documentation.tpl @@ -52,7 +52,9 @@ {{- end }} {{- end -}} +template: {{ .Template }} product: + identifier: {{ .ProductIdentifier }} {{- if .ProductBrand }} brand: {{ .ProductBrand }} {{- end }} @@ -96,3 +98,31 @@ render: {{- include "advanced" . | indent 4 }} {{- end }} {{- end }} +params: + {{- range .Params }} + {{- if and (not (eq .Name "usage")) (not .IsDeprecated) }} + - name: {{ .Name | quote }} + {{- if .Example }} + example: {{ .Example | quote }} + {{- end }} + {{- if .Default }} + default: {{ .Default }} + {{- end }} + {{- if .Choice }} + choice: [{{ join ", " .Choice }}] + {{- end }} + {{- if .Unit }} + unit: {{ .Unit }} + {{- end }} + {{- $description := localize .Description | replace "\n" " " }} + {{- if $description }} + description: {{ $description | quote }} + {{- end }} + {{- $help := localize .Help | replace "\n" " " }} + {{- if $help }} + help: {{ $help | quote }} + {{- end }} + advanced: {{ .IsAdvanced }} + optional: {{ not .IsRequired }} + {{- end }} + {{- end }} diff --git a/util/templates/generate/main.go b/util/templates/generate/main.go index aef52012022..c42309ef4f2 100644 --- a/util/templates/generate/main.go +++ b/util/templates/generate/main.go @@ -57,7 +57,7 @@ func generateClass(class templates.Class, lang string) error { return err } - for index, product := range tmpl.Products { + for _, product := range tmpl.Products { fmt.Println(tmpl.Template + ": " + product.Title(lang)) b, err := tmpl.RenderDocumentation(product, lang) @@ -65,7 +65,12 @@ func generateClass(class templates.Class, lang string) error { return err } - filename := fmt.Sprintf("%s/%s/%s/%s_%d.yaml", docsPath, lang, strings.ToLower(class.String()), tmpl.Template, index) + filename := fmt.Sprintf("%s/%s/%s/%s.yaml", docsPath, lang, strings.ToLower(class.String()), product.Identifier()) + + if _, err := os.Stat(filename); err == nil { + return fmt.Errorf("file already exists: %s - product titles must be unique", filename) + } + if err := os.WriteFile(filename, b, 0o644); err != nil { return err } diff --git a/util/templates/types.go b/util/templates/types.go index 1569c4fb925..6cf28f2b5fb 100644 --- a/util/templates/types.go +++ b/util/templates/types.go @@ -8,6 +8,7 @@ import ( "strings" "dario.cat/mergo" + "github.com/gosimple/slug" ) const ( @@ -259,10 +260,17 @@ type Product struct { Description TextLanguage `json:",omitempty"` // product name } +// Title returns the product title in the given language func (p Product) Title(lang string) string { return strings.TrimSpace(fmt.Sprintf("%s %s", p.Brand, p.Description.String(lang))) } +// Identifier returns a unique language-independent identifier for the product +func (p Product) Identifier() string { + slug.CustomSub = map[string]string{"+": "plus"} + return slug.Make(p.Title("en")) +} + type CountryCode string func (c CountryCode) IsValid() bool { From 10c4f8b3591d8b6ee3f00af2a3fa8559a82eff5f Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Fri, 6 Jun 2025 09:58:58 +0200 Subject: [PATCH 2/8] add modbus --- util/templates/documentation.go | 3 ++- util/templates/documentation.tpl | 18 ++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/util/templates/documentation.go b/util/templates/documentation.go index 0b491f1e30a..fabb398964a 100644 --- a/util/templates/documentation.go +++ b/util/templates/documentation.go @@ -43,6 +43,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er } var modbusRender string + modbusData := make(map[string]interface{}) if modbusChoices := t.ModbusChoices(); len(modbusChoices) > 0 { if i, _ := t.ParamByName(ParamModbus); i > -1 { modbusTmpl, err := template.New("yaml").Funcs(sprig.FuncMap()).Parse(documentationModbusTmpl) @@ -50,7 +51,6 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er panic(err) } - modbusData := make(map[string]interface{}) t.ModbusValues(RenderModeDocs, modbusData) out := new(bytes.Buffer) @@ -103,6 +103,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er "AdvancedParams": hasAdvancedParams, "Usages": t.Usages(), "Modbus": modbusRender, + "ModbusData": modbusData, } out := new(bytes.Buffer) diff --git a/util/templates/documentation.tpl b/util/templates/documentation.tpl index 9c395c3fb27..572cafc2fb0 100644 --- a/util/templates/documentation.tpl +++ b/util/templates/documentation.tpl @@ -102,27 +102,21 @@ params: {{- range .Params }} {{- if and (not (eq .Name "usage")) (not .IsDeprecated) }} - name: {{ .Name | quote }} - {{- if .Example }} example: {{ .Example | quote }} - {{- end }} - {{- if .Default }} default: {{ .Default }} - {{- end }} - {{- if .Choice }} choice: [{{ join ", " .Choice }}] - {{- end }} - {{- if .Unit }} unit: {{ .Unit }} - {{- end }} {{- $description := localize .Description | replace "\n" " " }} - {{- if $description }} description: {{ $description | quote }} - {{- end }} {{- $help := localize .Help | replace "\n" " " }} - {{- if $help }} help: {{ $help | quote }} - {{- end }} advanced: {{ .IsAdvanced }} optional: {{ not .IsRequired }} {{- end }} {{- end }} +{{- if .ModbusData }} +modbus: +{{- range $key, $value := .ModbusData }} + {{ $key }}: {{ $value }} +{{- end }} +{{- end }} \ No newline at end of file From c822b0e8b571d3bce95d99fc31eead02a964330c Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Fri, 6 Jun 2025 10:00:06 +0200 Subject: [PATCH 3/8] review feedback --- util/templates/documentation.tpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util/templates/documentation.tpl b/util/templates/documentation.tpl index 572cafc2fb0..fc2c7956bcc 100644 --- a/util/templates/documentation.tpl +++ b/util/templates/documentation.tpl @@ -4,8 +4,8 @@ - {{ . }} {{- end }} {{- $unit := .Unit -}} - {{- $description := localize .Description | replace "\n" " " -}} - {{- $help := localize .Help | replace "\n" " " -}} + {{- $description := localize .Description | replace "\n" " " | trim -}} + {{- $help := localize .Help | replace "\n" " " | trim -}} {{- $choices := join ", " .Choice -}} {{- $optional := not .IsRequired -}} {{- if or $help $choices $optional $description }} # {{end}} @@ -106,9 +106,9 @@ params: default: {{ .Default }} choice: [{{ join ", " .Choice }}] unit: {{ .Unit }} - {{- $description := localize .Description | replace "\n" " " }} + {{- $description := localize .Description | replace "\n" " " | trim }} description: {{ $description | quote }} - {{- $help := localize .Help | replace "\n" " " }} + {{- $help := localize .Help | replace "\n" " " | trim }} help: {{ $help | quote }} advanced: {{ .IsAdvanced }} optional: {{ not .IsRequired }} From bfb16e83048fbefb791a8ffead594c949c55719b Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Fri, 6 Jun 2025 10:02:32 +0200 Subject: [PATCH 4/8] review feedback --- util/templates/generate/main.go | 3 +++ util/templates/types.go | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/util/templates/generate/main.go b/util/templates/generate/main.go index c42309ef4f2..bed8e14d647 100644 --- a/util/templates/generate/main.go +++ b/util/templates/generate/main.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/evcc-io/evcc/util/templates" + "github.com/gosimple/slug" ) const ( @@ -19,6 +20,8 @@ const ( //go:generate go run main.go func main() { + slug.CustomSub = map[string]string{"+": "plus"} + for _, lang := range []string{"de", "en"} { if err := generateDocs(lang); err != nil { panic(err) diff --git a/util/templates/types.go b/util/templates/types.go index 6cf28f2b5fb..5cad711b1f0 100644 --- a/util/templates/types.go +++ b/util/templates/types.go @@ -267,7 +267,6 @@ func (p Product) Title(lang string) string { // Identifier returns a unique language-independent identifier for the product func (p Product) Identifier() string { - slug.CustomSub = map[string]string{"+": "plus"} return slug.Make(p.Title("en")) } From 7dadd35051aaac83a99ef6c69231b2a6b3a5c906 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Fri, 6 Jun 2025 10:04:14 +0200 Subject: [PATCH 5/8] tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8672a6fb1db..309b0a2062a 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 + github.com/gosimple/slug v1.15.0 github.com/gregdel/pushover v1.3.1 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/grid-x/modbus v0.0.0-20250516072809-4b99c910e8e7 @@ -151,7 +152,6 @@ require ( github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/gosimple/slug v1.15.0 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect github.com/huandu/xstrings v1.5.0 // indirect From b70640b0dc4ad48c3da7cfafaf2413e03cf11bf2 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Fri, 6 Jun 2025 20:13:00 +0200 Subject: [PATCH 6/8] generate list of all products for icon referencing --- util/templates/generate/main.go | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/util/templates/generate/main.go b/util/templates/generate/main.go index bed8e14d647..776b81e7363 100644 --- a/util/templates/generate/main.go +++ b/util/templates/generate/main.go @@ -15,6 +15,7 @@ import ( const ( docsPath = "../../../templates/docs" websitePath = "../../../templates/evcc.io" + iconsPath = "../../../templates/icons" ) //go:generate go run main.go @@ -31,6 +32,10 @@ func main() { if err := generateBrandJSON(); err != nil { panic(err) } + + if err := generateProductJSON(); err != nil { + panic(err) + } } func generateDocs(lang string) error { @@ -172,3 +177,39 @@ func generateBrandJSON() error { return err } + +func generateProductJSON() error { + type ProductInfo struct { + Brand string `json:"brand"` + Description string `json:"description"` + } + + products := make(map[string]map[string]ProductInfo) + + for _, class := range templates.ClassValues() { + classKey := strings.ToLower(class.String()) + products[classKey] = make(map[string]ProductInfo) + + for _, tmpl := range templates.ByClass(class) { + for _, product := range tmpl.Products { + products[classKey][product.Identifier()] = ProductInfo{ + Brand: product.Brand, + Description: product.Description.String("en"), + } + } + } + } + + if _, err := os.Stat(iconsPath); os.IsNotExist(err) { + if err := os.MkdirAll(iconsPath, 0o755); err != nil { + return err + } + } + + file, err := json.MarshalIndent(products, "", " ") + if err == nil { + err = os.WriteFile(iconsPath+"/products.json", file, 0o644) + } + + return err +} From 58a6da5d85ed5128a60f5ce6c410fa37014127c8 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Sat, 7 Jun 2025 10:42:22 +0200 Subject: [PATCH 7/8] chore: adjust e2e (restart button) --- tests/config-mqtt.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/config-mqtt.spec.js b/tests/config-mqtt.spec.js index 9ec14c2f1b2..d17180d01fa 100644 --- a/tests/config-mqtt.spec.js +++ b/tests/config-mqtt.spec.js @@ -60,7 +60,6 @@ test.describe("mqtt", async () => { await restart(CONFIG); // config error - await expect(restartButton).not.toBeVisible(); await expect(page.getByTestId("mqtt")).toHaveClass(/round-box--error/); await expect(page.getByTestId("mqtt")).toContainText( ["Broker", INVALID_BROKER, "Topic", VALID_TOPIC].join("") From fc1a475dc562e04e1d3d848b6fe600be697abeaf Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Sat, 7 Jun 2025 10:42:44 +0200 Subject: [PATCH 8/8] revert --- tests/config-mqtt.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/config-mqtt.spec.js b/tests/config-mqtt.spec.js index d17180d01fa..9ec14c2f1b2 100644 --- a/tests/config-mqtt.spec.js +++ b/tests/config-mqtt.spec.js @@ -60,6 +60,7 @@ test.describe("mqtt", async () => { await restart(CONFIG); // config error + await expect(restartButton).not.toBeVisible(); await expect(page.getByTestId("mqtt")).toHaveClass(/round-box--error/); await expect(page.getByTestId("mqtt")).toContainText( ["Broker", INVALID_BROKER, "Topic", VALID_TOPIC].join("")