Templates: add capabilities on product level (#31588)

This commit is contained in:
andig 2026-07-09 12:43:27 +02:00 • committed by GitHub
parent 4f71e9675c
commit 023a48dd24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 95 additions and 10 deletions

View file

@ -50,14 +50,40 @@ Note: The official website of the manufacturer or service provider is the refere
## `capabilities`
`capabilities` provides an option to define special capabilities of the device as a list of strings
`capabilities` provides an option to define special capabilities of the device as a list of strings.
**Possible Values**:
**Possible values**:
- `iso151182`: If the charger supports communicating via ISO15118-2
- `rfid`: If the charger supports RFID
- `1p3p`: If the charger supports 1P/3P-phase switching
- `smahems`: If the device can be used as an SMA HEMS device, only used for the SMA Home Manager 2.0 right now
- `iso151182`: The device supports communicating via ISO 15118-2.
- `mA`: The device supports granular (milliamp) current control.
- `rfid`: The device supports RFID.
- `1p3p`: The device supports 1P/3P phase switching.
- `battery-control`: The device supports battery control.
- `meter`: The device has a built-in energy meter.
- `dim`: The device supports EnWG §14a dimming.
- `curtail`: The device supports EEG §9 curtailment.
`capabilities` can be set at two levels:
- **Template level** (next to `template`): applies to every product.
- **Product level** (under a `products` entry): applies to that product only.
Both levels are merged, not overwritten: template-level capabilities are appended to each product's own list. A product cannot remove an inherited capability, and duplicates are rejected, so do not repeat a template-level capability on a product.
**Example** (all products get `1p3p`; only the second product additionally gets `meter`):
```yaml
template: demo-charger
capabilities: ["1p3p"]
products:
- brand: Demo
description:
generic: Basic
- brand: Demo
description:
generic: Plus
capabilities: ["meter"] # effective: ["meter", "1p3p"]
```
## `requirements`

View file

@ -0,0 +1,45 @@
package templates
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCapabilityPushdown(t *testing.T) {
tmpl, err := fromBytes([]byte(`
template: caps-demo
capabilities: ["1p3p"]
products:
- brand: Plain
- brand: Extra
capabilities: ["meter"]
params:
- name: host
required: true
render: |
type: demo
`))
require.NoError(t, err)
// template caps appended to product caps (product first)
require.Equal(t, []Capability{Capability1p3p}, tmpl.Products[0].Capabilities)
require.Equal(t, []Capability{CapabilityMeter, Capability1p3p}, tmpl.Products[1].Capabilities)
}
func TestCapabilityDuplicate(t *testing.T) {
// product repeats a template-level capability
_, err := fromBytes([]byte(`
template: caps-dup
capabilities: ["1p3p"]
products:
- brand: Dup
capabilities: ["1p3p"]
params:
- name: host
required: true
render: |
type: demo
`))
require.ErrorContains(t, err, "duplicate capability '1p3p'")
}

View file

@ -101,7 +101,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
"ProductBrand": product.Brand,
"ProductDescription": product.Description.String(lang),
"ProductGroup": t.GroupTitle(lang),
"Capabilities": t.Capabilities,
"Capabilities": product.Capabilities,
"Countries": t.Countries,
"Requirements": t.Requirements.EVCC,
"RequirementDescription": t.Requirements.Description.String(lang),

View file

@ -79,6 +79,19 @@ func fromBytes(b []byte) (Template, error) {
}
}
// push down capabilities to products
for i := range tmpl.Products {
tmpl.Products[i].Capabilities = append(tmpl.Products[i].Capabilities, tmpl.Capabilities...)
seen := make(map[Capability]struct{}, len(tmpl.Products[i].Capabilities))
for _, c := range tmpl.Products[i].Capabilities {
if _, ok := seen[c]; ok {
return Template{}, fmt.Errorf("template '%s': duplicate capability '%s' for product '%s'", tmpl.Template, c, tmpl.Products[i].Identifier())
}
seen[c] = struct{}{}
}
}
return tmpl, nil
}

View file

@ -22,7 +22,7 @@ type Template 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
Capabilities []Capability `json:",omitempty"`
Capabilities []Capability `json:"-"`
Countries []CountryCode `json:",omitempty"` // list of countries supported by this template
Requirements Requirements `json:",omitempty"`
Caveats []Caveat `json:",omitempty"` // known device limitations

View file

@ -321,8 +321,9 @@ func (p Param) MarshalJSON() ([]byte, error) {
// Product contains naming information about a product a template supports
type Product struct {
Brand string // product brand
Description TextLanguage `json:",omitempty"` // product name
Brand string // product brand
Description TextLanguage `json:",omitempty"` // product name
Capabilities []Capability `json:",omitempty"` // appended to template-level capabilities
}
// Title returns the product title in the given language