diff --git a/assets/js/components/Config/ChargerModal.vue b/assets/js/components/Config/ChargerModal.vue index 128d4392c8..d0c09315a5 100644 --- a/assets/js/components/Config/ChargerModal.vue +++ b/assets/js/components/Config/ChargerModal.vue @@ -221,17 +221,29 @@ export default { } return this.$t(`config.charger.titleEdit`); }, + uniqueProducts() { + // append protocol if multiple products with same name exist + const names = this.products.map((p) => p.name); + return this.products.map((p) => { + if (names.filter((n) => n === p.name).length > 1 && p.protocol) { + const protocol = this.$t(`config.deviceProtocol.${p.protocol}`); + const name = `${p.name} (${protocol})`; + return { ...p, name }; + } + return p; + }); + }, chargerOptions() { - return this.products.filter((p) => !p.group); + return this.uniqueProducts.filter((p) => !p.group); }, genericOptions() { - return this.products.filter((p) => p.group === "generic"); + return this.uniqueProducts.filter((p) => p.group === "generic"); }, switchSocketOptions() { - return this.products.filter((p) => p.group === "switchsockets"); + return this.uniqueProducts.filter((p) => p.group === "switchsockets"); }, heatingdevicesOptions() { - return this.products.filter((p) => p.group === "heating"); + return this.uniqueProducts.filter((p) => p.group === "heating"); }, templateParams() { const params = this.template?.Params || []; diff --git a/server/http_config_metadata_handler.go b/server/http_config_metadata_handler.go index fe3ce2684d..fb714bbffa 100644 --- a/server/http_config_metadata_handler.go +++ b/server/http_config_metadata_handler.go @@ -92,6 +92,7 @@ func productsHandler(w http.ResponseWriter, r *http.Request) { Name: p.Title(lang), Template: t.TemplateDefinition.Template, Group: t.Group, + Protocol: t.Protocol, }) } } diff --git a/server/product.go b/server/product.go index 2ba4f3c512..bc03e15149 100644 --- a/server/product.go +++ b/server/product.go @@ -4,6 +4,7 @@ type product struct { Name string `json:"name"` Template string `json:"template"` Group string `json:"group,omitempty"` + Protocol string `json:"protocol,omitempty"` } type products []product diff --git a/templates/definition/charger/abb.yaml b/templates/definition/charger/abb.yaml index 0ddc3fbb1b..5768c2ac50 100644 --- a/templates/definition/charger/abb.yaml +++ b/templates/definition/charger/abb.yaml @@ -1,4 +1,5 @@ template: abb +protocol: modbus products: - brand: ABB description: diff --git a/templates/definition/charger/abl-em4.yaml b/templates/definition/charger/abl-em4.yaml index 3e59928739..9f74ad0655 100644 --- a/templates/definition/charger/abl-em4.yaml +++ b/templates/definition/charger/abl-em4.yaml @@ -1,4 +1,5 @@ template: abl-em4 +protocol: modbus products: - brand: ABL description: diff --git a/templates/definition/charger/alfen.yaml b/templates/definition/charger/alfen.yaml index 2be02e9dc5..ab50177dc4 100644 --- a/templates/definition/charger/alfen.yaml +++ b/templates/definition/charger/alfen.yaml @@ -1,4 +1,5 @@ template: alfen +protocol: modbus products: - brand: Alfen description: diff --git a/templates/definition/charger/daheimladen-mb.yaml b/templates/definition/charger/daheimladen-mb.yaml index 85d32cb85a..668be32a85 100644 --- a/templates/definition/charger/daheimladen-mb.yaml +++ b/templates/definition/charger/daheimladen-mb.yaml @@ -1,4 +1,5 @@ template: daheimladen-mb +protocol: modbus products: - brand: DaheimLaden description: diff --git a/templates/definition/charger/fronius-wattpilot.yaml b/templates/definition/charger/fronius-wattpilot.yaml index 15d3d8efd2..d6b1bb3185 100644 --- a/templates/definition/charger/fronius-wattpilot.yaml +++ b/templates/definition/charger/fronius-wattpilot.yaml @@ -1,4 +1,5 @@ template: fronius-wattpilot +protocol: localapi deprecated: true products: - brand: Fronius diff --git a/templates/definition/charger/go-e-v3.yaml b/templates/definition/charger/go-e-v3.yaml index 4976819ce1..76d69377d9 100644 --- a/templates/definition/charger/go-e-v3.yaml +++ b/templates/definition/charger/go-e-v3.yaml @@ -1,4 +1,5 @@ template: go-e-v3 +protocol: localapi covers: ["go-e-gemini"] products: - brand: go-e diff --git a/templates/definition/charger/go-e.yaml b/templates/definition/charger/go-e.yaml index e7f95378a2..81e3fd12c7 100644 --- a/templates/definition/charger/go-e.yaml +++ b/templates/definition/charger/go-e.yaml @@ -1,4 +1,5 @@ template: go-e +protocol: localapi products: - brand: go-e description: diff --git a/templates/definition/charger/phoenix-ev-ser.yaml b/templates/definition/charger/phoenix-ev-ser.yaml index c399822898..112640259f 100644 --- a/templates/definition/charger/phoenix-ev-ser.yaml +++ b/templates/definition/charger/phoenix-ev-ser.yaml @@ -1,4 +1,5 @@ template: phoenix-ev-ser +protocol: modbus products: - brand: Phoenix Contact description: diff --git a/templates/definition/charger/tessie.yaml b/templates/definition/charger/tessie.yaml index 5c9d30f537..03321fa4f1 100644 --- a/templates/definition/charger/tessie.yaml +++ b/templates/definition/charger/tessie.yaml @@ -1,5 +1,5 @@ template: tessie -group: generic +protocol: cloudapi products: - description: generic: Tessie diff --git a/templates/definition/charger/zaptec.yaml b/templates/definition/charger/zaptec.yaml index 4e721c12f9..9c2c1d5c75 100644 --- a/templates/definition/charger/zaptec.yaml +++ b/templates/definition/charger/zaptec.yaml @@ -1,4 +1,5 @@ template: zaptec +protocol: localapi products: - brand: Zaptec description: diff --git a/util/templates/defaults.go b/util/templates/defaults.go index 82f58579b3..fbc98aa1e2 100644 --- a/util/templates/defaults.go +++ b/util/templates/defaults.go @@ -13,7 +13,9 @@ var defaults []byte type configDefaults struct { Params []Param // Default values for common parameters Presets map[string]struct { - Params []Param + Params []Param + Protocol string + Requirements Requirements } Modbus struct { // Details about possible ModbusInterfaces and ModbusConnectionTypes Interfaces map[string][]string // Information about physical modbus interface types (rs485, tcpip) diff --git a/util/templates/defaults.yaml b/util/templates/defaults.yaml index 39f0f7e00b..12e603430c 100644 --- a/util/templates/defaults.yaml +++ b/util/templates/defaults.yaml @@ -455,6 +455,7 @@ presets: required: true eebus: + protocol: eebus params: - name: ski help: @@ -480,6 +481,9 @@ presets: - name: icon advanced: true ocpp: + protocol: ocpp + requirements: + evcc: ["sponsorship"] params: - name: stationid type: string @@ -538,6 +542,7 @@ presets: example: Energy.Active.Import.Register,Power.Active.Import,SoC,Current.Offered,Power.Offered,Current.Import,Voltage mqtt: + protocol: mqtt params: - name: host help: diff --git a/util/templates/documentation.go b/util/templates/documentation.go index f12f6f786a..aef01828e4 100644 --- a/util/templates/documentation.go +++ b/util/templates/documentation.go @@ -97,6 +97,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er "Capabilities": t.Capabilities, "Countries": t.Countries, "Requirements": t.Requirements.EVCC, + "Protocol": t.Protocol, "RequirementDescription": t.Requirements.Description.String(lang), "Params": filteredParams, "AdvancedParams": hasAdvancedParams, diff --git a/util/templates/documentation.tpl b/util/templates/documentation.tpl index 74a0af8404..0ec8224c19 100644 --- a/util/templates/documentation.tpl +++ b/util/templates/documentation.tpl @@ -56,6 +56,9 @@ product: {{- if .ProductGroup }} group: {{ .ProductGroup }} {{- end }} +{{- if .Protocol }} +protocol: {{ .Protocol }} +{{- end }} {{- if .Capabilities }} capabilities: ["{{ join "\", \"" .Capabilities }}"] {{- end }} diff --git a/util/templates/template.go b/util/templates/template.go index b482c99ade..ea0426a0df 100644 --- a/util/templates/template.go +++ b/util/templates/template.go @@ -41,7 +41,7 @@ func (t *Template) UpdateParamsWithDefaults() error { func (t *Template) Validate() error { for _, c := range t.Capabilities { if !slices.Contains(ValidCapabilities, c) { - return fmt.Errorf("invalid capability '%s' in template %s", c, t.Template) + return fmt.Errorf("invalid capability '%s' in template %s. valid options: %s", c, t.Template, ValidCapabilities) } } @@ -53,10 +53,14 @@ func (t *Template) Validate() error { for _, r := range t.Requirements.EVCC { if !slices.Contains(ValidRequirements, r) { - return fmt.Errorf("invalid requirement '%s' in template %s", r, t.Template) + return fmt.Errorf("invalid requirement '%s' in template %s. valid options: %s", r, t.Template, ValidCapabilities) } } + if t.Protocol != "" && !slices.Contains(ValidProtocols, t.Protocol) { + return fmt.Errorf("invalid protocol '%s' in template %s. valid options: %s", t.Protocol, t.Template, ValidProtocols) + } + for _, p := range t.Params { switch p.Name { case ParamUsage: @@ -124,7 +128,26 @@ func (t *Template) ResolvePresets() error { return fmt.Errorf("could not find preset definition: %s", p.Preset) } + // add preset params t.Params = append(t.Params, base.Params...) + + // apply protocol if not already set + if t.Protocol == "" && base.Protocol != "" { + t.Protocol = base.Protocol + } + + // set description if not already set + if t.Requirements.Description.DE == "" && t.Requirements.Description.EN == "" && t.Requirements.Description.Generic == "" { + t.Requirements.Description = base.Requirements.Description + } + + // append requirements if not already present + for _, r := range base.Requirements.EVCC { + if !slices.Contains(t.Requirements.EVCC, r) { + t.Requirements.EVCC = append(t.Requirements.EVCC, r) + } + } + continue } diff --git a/util/templates/types.go b/util/templates/types.go index c084541144..46219462e6 100644 --- a/util/templates/types.go +++ b/util/templates/types.go @@ -62,6 +62,17 @@ const ( var ValidRequirements = []string{RequirementEEBUS, RequirementMQTT, RequirementSponsorship, RequirementSkipTest} +const ( + ProtocolOCPP = "ocpp" // Open Charge Point Protocol + ProtocolLocalApi = "localapi" // local vendor-specific API + ProtocolCloudApi = "cloudapi" // cloud-based vendor API + ProtocolModbus = "modbus" // Modbus RTU or TCP + ProtocolMQTT = "mqtt" // MQTT + ProtocolEEBUS = "eebus" // EEBUS +) + +var ValidProtocols = []string{ProtocolOCPP, ProtocolLocalApi, ProtocolCloudApi, ProtocolModbus, ProtocolMQTT, ProtocolEEBUS} + var predefinedTemplateProperties = []string{ "type", "template", "name", ModbusParamNameId, ModbusParamNameDevice, ModbusParamNameBaudrate, ModbusParamNameComset, @@ -277,6 +288,7 @@ type TemplateDefinition struct { Group string `json:",omitempty"` // the group this template belongs to, references groupList entries Covers []string `json:",omitempty"` // list of covered outdated template names Products []Product `json:",omitempty"` // list of products this template is compatible with + Protocol string `json:",omitempty"` // communication protocol used in this template. used do differentiate templates for the same device Capabilities []string `json:",omitempty"` Countries []CountryCode `json:",omitempty"` // list of countries supported by this template Requirements Requirements `json:",omitempty"`