More template stuff (#2164)
Some checks failed
Default / Build (push) Failing after 2m0s
Default / Build UI (push) Failing after 13s
Release / Publish Docker :release (push) Has been cancelled
Release / call-build-workflow (push) Has been cancelled
Release / Publish Github & APT release (push) Has been cancelled

- Configure now asks for the users knowledge level, so advanced mode can be reached without calling with `--advanced`
- Add template for solaredge inverter
- Add beta template for sungrow inverter
- Add template for MQTT based meter
- Add more template documentation
- Add check and error message for invalid config properties when using templates
- Option to specify language specific template descriptions
- Option to make params dependent on values of other params 
- Option to add sponsortoken in advanced mode
- Option to set minCurrent in advanced mode
- Remove sponsorship requirement from Daheimladen wallbox
- Multiple fixes (FritzDect meter, Kostsal Piko meter, ...)
This commit is contained in:
Andreas Linde 2022-01-09 12:59:54 +01:00 • committed by GitHub
parent 8ba710ff5f
commit cfb174f9f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
144 changed files with 935 additions and 265 deletions

View file

@ -26,7 +26,7 @@ func NewChargerFromTemplateConfig(other map[string]interface{}) (api.Charger, er
return nil, err return nil, err
} }
b, _, err := tmpl.RenderResult(false, other) b, _, err := tmpl.RenderResult(templates.TemplateRenderModeInstance, other)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -35,7 +35,7 @@ func TestChargerTemplates(t *testing.T) {
tmpl := tmpl tmpl := tmpl
// set default values for all params // set default values for all params
values := tmpl.Defaults(true) values := tmpl.Defaults(templates.TemplateRenderModeUnitTest)
// set the template value which is needed for rendering // set the template value which is needed for rendering
values["template"] = tmpl.Template values["template"] = tmpl.Template
@ -43,6 +43,7 @@ func TestChargerTemplates(t *testing.T) {
// set modbus default test values // set modbus default test values
if values[templates.ParamModbus] != nil { if values[templates.ParamModbus] != nil {
modbusChoices := tmpl.ModbusChoices() modbusChoices := tmpl.ModbusChoices()
// we only test one modbus setup
if funk.ContainsString(modbusChoices, templates.ModbusChoiceTCPIP) { if funk.ContainsString(modbusChoices, templates.ModbusChoiceTCPIP) {
values[templates.ModbusTCPIP] = true values[templates.ModbusTCPIP] = true
} else { } else {
@ -53,15 +54,17 @@ func TestChargerTemplates(t *testing.T) {
t.Run(tmpl.Template, func(t *testing.T) { t.Run(tmpl.Template, func(t *testing.T) {
t.Parallel() t.Parallel()
b, values, err := tmpl.RenderResult(true, values) b, values, err := tmpl.RenderResult(templates.TemplateRenderModeUnitTest, values)
if err != nil { if err != nil {
t.Logf("%s: %s", tmpl.Template, b) t.Logf("Template: %s", tmpl.Template)
t.Logf("%s", string(b))
t.Error(err) t.Error(err)
} }
_, err = NewFromConfig("template", values) _, err = NewFromConfig("template", values)
if err != nil && !test.Acceptable(err, acceptable) { if err != nil && !test.Acceptable(err, acceptable) {
t.Logf("%s", tmpl.Template) t.Logf("Template: %s", tmpl.Template)
t.Logf("%s", string(b))
t.Error(err) t.Error(err)
} }
}) })

View file

@ -40,6 +40,7 @@ type config struct {
} }
Hems string Hems string
EEBUS string EEBUS string
MQTT string
SponsorToken string SponsorToken string
} }

View file

@ -49,7 +49,7 @@ func (d *DeviceTest) Test() (DeviceTestResult, error) {
// configure creates a configured device from a template so we can test it // configure creates a configured device from a template so we can test it
func (d *DeviceTest) configure() (interface{}, error) { func (d *DeviceTest) configure() (interface{}, error) {
b, _, err := d.Template.RenderResult(false, d.ConfigValues) b, _, err := d.Template.RenderResult(templates.TemplateRenderModeInstance, d.ConfigValues)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -53,7 +53,7 @@ func (c *CmdConfigure) configureDeviceGuidedSetup() {
// we only ask for the configuration for the first usage // we only ask for the configuration for the first usage
deviceCategory = supportedDeviceCategories[0] deviceCategory = supportedDeviceCategories[0]
values = c.processConfig(templateItem.Params, deviceCategory) values = c.processConfig(templateItem, deviceCategory)
deviceItem, err = c.processDeviceValues(values, templateItem, deviceItem, deviceCategory) deviceItem, err = c.processDeviceValues(values, templateItem, deviceItem, deviceCategory)
if err != nil { if err != nil {
@ -86,7 +86,7 @@ func (c *CmdConfigure) configureDeviceGuidedSetup() {
} }
fmt.Println() fmt.Println()
fmt.Println(templateItem.Description + " " + c.localizedString("Device_Added", nil)) fmt.Println(templateItem.Description.String(c.lang) + " " + c.localizedString("Device_Added", nil))
c.configureLinkedTypes(templateItem) c.configureLinkedTypes(templateItem)
} }
@ -122,7 +122,7 @@ func (c *CmdConfigure) configureLinkedTypes(templateItem templates.Template) {
category := DeviceCategory(linkedTemplate.Usage) category := DeviceCategory(linkedTemplate.Usage)
localizeMap := localizeMap{ localizeMap := localizeMap{
"Linked": linkedTemplateItem.Description, "Linked": linkedTemplateItem.Description.String(c.lang),
"Article": DeviceCategories[category].article, "Article": DeviceCategories[category].article,
"Additional": DeviceCategories[category].additional, "Additional": DeviceCategories[category].additional,
"Category": DeviceCategories[category].title, "Category": DeviceCategories[category].title,
@ -148,7 +148,6 @@ func (c *CmdConfigure) configureLinkedTypes(templateItem templates.Template) {
} }
} }
} }
fmt.Println("DONE")
} }
// configureLinkedTemplate lets the user configure a device that is marked as being linked to a guided device // configureLinkedTemplate lets the user configure a device that is marked as being linked to a guided device
@ -157,7 +156,7 @@ func (c *CmdConfigure) configureLinkedTemplate(template templates.Template, cate
for ok := true; ok; { for ok := true; ok; {
deviceItem := device{} deviceItem := device{}
values := c.processConfig(template.Params, category) values := c.processConfig(template, category)
deviceItem, err := c.processDeviceValues(values, template, deviceItem, category) deviceItem, err := c.processDeviceValues(values, template, deviceItem, category)
if err != nil { if err != nil {
if !errors.Is(err, c.errDeviceNotValid) { if !errors.Is(err, c.errDeviceNotValid) {
@ -173,7 +172,7 @@ func (c *CmdConfigure) configureLinkedTemplate(template templates.Template, cate
c.configuration.AddDevice(deviceItem, category) c.configuration.AddDevice(deviceItem, category)
fmt.Println() fmt.Println()
fmt.Println(template.Description + " " + c.localizedString("Device_Added", nil)) fmt.Println(template.Description.String(c.lang) + " " + c.localizedString("Device_Added", nil))
return true return true
} }
break break
@ -182,7 +181,7 @@ func (c *CmdConfigure) configureLinkedTemplate(template templates.Template, cate
} }
// configureDeviceCategory lets the user select and configure a device from a specific category // configureDeviceCategory lets the user select and configure a device from a specific category
func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (device, error) { func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (device, templates.Capabilities, error) {
fmt.Println() fmt.Println()
fmt.Printf("- %s %s\n", c.localizedString("Device_Configure", nil), DeviceCategories[deviceCategory].title) fmt.Printf("- %s %s\n", c.localizedString("Device_Configure", nil), DeviceCategories[deviceCategory].title)
@ -191,6 +190,7 @@ func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (d
} }
var deviceDescription string var deviceDescription string
var capabilities templates.Capabilities
// repeat until the device is added or the user chooses to continue without adding a device // repeat until the device is added or the user chooses to continue without adding a device
for ok := true; ok; { for ok := true; ok; {
@ -198,11 +198,12 @@ func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (d
templateItem, err := c.processDeviceSelection(deviceCategory) templateItem, err := c.processDeviceSelection(deviceCategory)
if err != nil { if err != nil {
return device, c.errItemNotPresent return device, capabilities, c.errItemNotPresent
} }
deviceDescription = templateItem.Description deviceDescription = templateItem.Description.String(c.lang)
values := c.processConfig(templateItem.Params, deviceCategory) capabilities = templateItem.Capabilities
values := c.processConfig(templateItem, deviceCategory)
device, err = c.processDeviceValues(values, templateItem, device, deviceCategory) device, err = c.processDeviceValues(values, templateItem, device, deviceCategory)
if err != nil { if err != nil {
@ -213,7 +214,7 @@ func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (d
// ask if the user wants to add the // ask if the user wants to add the
fmt.Println() fmt.Println()
if !c.askConfigFailureNextStep() { if !c.askConfigFailureNextStep() {
return device, err return device, capabilities, err
} }
continue continue
} }
@ -231,5 +232,5 @@ func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (d
fmt.Println() fmt.Println()
fmt.Println(deviceDescription + deviceTitle + " " + c.localizedString("Device_Added", nil)) fmt.Println(deviceDescription + deviceTitle + " " + c.localizedString("Device_Added", nil))
return device, nil return device, capabilities, nil
} }

View file

@ -5,6 +5,8 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/evcc-io/evcc/provider/mqtt"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/sponsor" "github.com/evcc-io/evcc/util/sponsor"
"github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/templates"
"github.com/thoas/go-funk" "github.com/thoas/go-funk"
@ -15,7 +17,7 @@ import (
func (c *CmdConfigure) processDeviceSelection(deviceCategory DeviceCategory) (templates.Template, error) { func (c *CmdConfigure) processDeviceSelection(deviceCategory DeviceCategory) (templates.Template, error) {
templateItem := c.selectItem(deviceCategory) templateItem := c.selectItem(deviceCategory)
if templateItem.Description == c.localizedString("ItemNotPresent", nil) { if templateItem.Description.String(c.lang) == c.localizedString("ItemNotPresent", nil) {
return templateItem, c.errItemNotPresent return templateItem, c.errItemNotPresent
} }
@ -31,7 +33,7 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa
c.addedDeviceIndex++ c.addedDeviceIndex++
device.Name = fmt.Sprintf("%s%d", DeviceCategories[deviceCategory].defaultName, c.addedDeviceIndex) device.Name = fmt.Sprintf("%s%d", DeviceCategories[deviceCategory].defaultName, c.addedDeviceIndex)
device.Title = templateItem.Description device.Title = templateItem.Description.String(c.lang)
for item, value := range values { for item, value := range values {
if strings.ToLower(item) != "title" { if strings.ToLower(item) != "title" {
continue continue
@ -45,9 +47,9 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa
fmt.Println() fmt.Println()
if categoryWithUsage { if categoryWithUsage {
fmt.Println(c.localizedString("TestingDevice_TitleUsage", localizeMap{"Device": templateItem.Description, "Usage": deviceCategory.String()})) fmt.Println(c.localizedString("TestingDevice_TitleUsage", localizeMap{"Device": templateItem.Description.String(c.lang), "Usage": deviceCategory.String()}))
} else { } else {
fmt.Println(c.localizedString("TestingDevice_Title", localizeMap{"Device": templateItem.Description})) fmt.Println(c.localizedString("TestingDevice_Title", localizeMap{"Device": templateItem.Description.String(c.lang)}))
} }
deviceTest := DeviceTest{ deviceTest := DeviceTest{
@ -61,9 +63,9 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa
fmt.Println(" ", c.localizedString("Error", localizeMap{"Error": err})) fmt.Println(" ", c.localizedString("Error", localizeMap{"Error": err}))
fmt.Println() fmt.Println()
question := c.localizedString("TestingDevice_AddFailed", localizeMap{"Device": templateItem.Description}) question := c.localizedString("TestingDevice_AddFailed", localizeMap{"Device": templateItem.Description.String(c.lang)})
if categoryWithUsage { if categoryWithUsage {
question = c.localizedString("TestingDevice_AddFailedUsage", localizeMap{"Device": templateItem.Description, "Usage": deviceCategory.String()}) question = c.localizedString("TestingDevice_AddFailedUsage", localizeMap{"Device": templateItem.Description.String(c.lang), "Usage": deviceCategory.String()})
} }
if !c.askYesNo(question) { if !c.askYesNo(question) {
c.addedDeviceIndex-- c.addedDeviceIndex--
@ -77,7 +79,7 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa
templateItem.Params = append(templateItem.Params, templates.Param{Name: "name", Value: device.Name}) templateItem.Params = append(templateItem.Params, templates.Param{Name: "name", Value: device.Name})
if !c.expandedMode { if !c.expandedMode {
b, err := templateItem.RenderProxyWithValues(values, false) b, err := templateItem.RenderProxyWithValues(values, c.lang, false)
if err != nil { if err != nil {
c.addedDeviceIndex-- c.addedDeviceIndex--
return device, err return device, err
@ -91,7 +93,7 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa
templateItem.Render = fmt.Sprintf("name: {{ .name }}\n%s", templateItem.Render) templateItem.Render = fmt.Sprintf("name: {{ .name }}\n%s", templateItem.Render)
} }
} }
b, _, err := templateItem.RenderResult(false, values) b, _, err := templateItem.RenderResult(templates.TemplateRenderModeInstance, values)
if err != nil { if err != nil {
c.addedDeviceIndex-- c.addedDeviceIndex--
return device, err return device, err
@ -115,21 +117,9 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template
// check if sponsorship is required // check if sponsorship is required
if templateItem.Requirements.Sponsorship && c.configuration.config.SponsorToken == "" { if templateItem.Requirements.Sponsorship && c.configuration.config.SponsorToken == "" {
fmt.Println("-- Sponsorship -----------------------------") if err := c.askSponsortoken(true); err != nil {
fmt.Println() return err
fmt.Println(c.localizedString("Requirements_Sponsorship_Title", nil))
fmt.Println()
if !c.askYesNo(c.localizedString("Requirements_Sponsorship_Token", nil)) {
return c.errItemNotPresent
} }
sponsortoken := c.askValue(question{
label: c.localizedString("Requirements_Sponsorship_Token_Input", nil),
mask: true,
required: true})
c.configuration.config.SponsorToken = sponsortoken
sponsor.Subject = sponsortoken
fmt.Println()
fmt.Println("--------------------------------------------")
} }
// check if we need to setup a HEMS // check if we need to setup a HEMS
@ -140,6 +130,23 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template
} }
} }
// check if we need to setup an MQTT broker
if templateItem.Requirements.Mqtt {
if c.configuration.config.MQTT == "" {
mqttConfig, err := c.configureMQTT()
if err != nil {
return err
}
mqttYaml, err := yaml.Marshal(mqttConfig)
if err != nil {
return err
}
c.configuration.config.MQTT = string(mqttYaml)
}
}
// check if we need to setup an EEBUS HEMS // check if we need to setup an EEBUS HEMS
if templateItem.Requirements.Eebus { if templateItem.Requirements.Eebus {
if c.configuration.config.EEBUS == "" { if c.configuration.config.EEBUS == "" {
@ -173,12 +180,90 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template
return nil return nil
} }
func (c *CmdConfigure) askSponsortoken(required bool) error {
fmt.Println("-- Sponsorship -----------------------------")
if required {
fmt.Println()
fmt.Println(c.localizedString("Requirements_Sponsorship_Title", nil))
}
fmt.Println()
if !c.askYesNo(c.localizedString("Requirements_Sponsorship_Token", nil)) {
return c.errItemNotPresent
}
sponsortoken := c.askValue(question{
label: c.localizedString("Requirements_Sponsorship_Token_Input", nil),
mask: true,
required: true})
c.configuration.config.SponsorToken = sponsortoken
sponsor.Subject = sponsortoken
fmt.Println()
fmt.Println("--------------------------------------------")
return nil
}
func (c *CmdConfigure) configureMQTT() (map[string]interface{}, error) {
fmt.Println()
fmt.Println("-- MQTT Broker ----------------------------")
var err error
for ok := true; ok; {
fmt.Println()
host := c.askValue(question{
label: c.localizedString("UserFriendly_Host_Name", nil),
mask: false,
required: true})
port := c.askValue(question{
label: c.localizedString("UserFriendly_Port_Name", nil),
mask: false,
required: true})
user := c.askValue(question{
label: c.localizedString("UserFriendly_User_Name", nil),
mask: false,
required: false})
password := c.askValue(question{
label: c.localizedString("UserFriendly_Password_Name", nil),
mask: true,
required: false})
fmt.Println()
fmt.Println("-------------------------------------------")
broker := fmt.Sprintf("%s:%s", host, port)
mqttConfig := map[string]interface{}{
"broker": broker,
"user": user,
"password": password,
}
log := util.NewLogger("mqtt")
if mqtt.Instance, err = mqtt.RegisteredClient(log, broker, user, password, "", 1); err == nil {
return mqttConfig, nil
}
fmt.Println()
question := c.localizedString("TestingMQTTFailed", nil)
if !c.askYesNo(question) {
return nil, fmt.Errorf("failed configuring mqtt: %w", err)
}
}
return nil, fmt.Errorf("failed configuring mqtt: %w", err)
}
// fetchElements returns template items of a given class // fetchElements returns template items of a given class
func (c *CmdConfigure) fetchElements(deviceCategory DeviceCategory) []templates.Template { func (c *CmdConfigure) fetchElements(deviceCategory DeviceCategory) []templates.Template {
var items []templates.Template var items []templates.Template
for _, tmpl := range templates.ByClass(DeviceCategories[deviceCategory].class.String()) { for _, tmpl := range templates.ByClass(DeviceCategories[deviceCategory].class.String()) {
if len(tmpl.Params) == 0 || len(tmpl.Description) == 0 { if len(tmpl.Params) == 0 || len(tmpl.Description.String(c.lang)) == 0 {
continue continue
} }
@ -202,7 +287,7 @@ func (c *CmdConfigure) fetchElements(deviceCategory DeviceCategory) []templates.
if !items[i].Generic && items[j].Generic { if !items[i].Generic && items[j].Generic {
return true return true
} }
return strings.ToLower(items[i].Description) < strings.ToLower(items[j].Description) return strings.ToLower(items[i].Description.String(c.lang)) < strings.ToLower(items[j].Description.String(c.lang))
}) })
return items return items
@ -233,7 +318,7 @@ func (c *CmdConfigure) paramChoiceValues(params []templates.Param, name string)
// processConfig processes an EVCC configuration item // processConfig processes an EVCC configuration item
// Returns: // Returns:
// a map with param name and values // a map with param name and values
func (c *CmdConfigure) processConfig(paramItems []templates.Param, deviceCategory DeviceCategory) map[string]interface{} { func (c *CmdConfigure) processConfig(templateItem templates.Template, deviceCategory DeviceCategory) map[string]interface{} {
usageFilter := DeviceCategories[deviceCategory].categoryFilter usageFilter := DeviceCategories[deviceCategory].categoryFilter
additionalConfig := make(map[string]interface{}) additionalConfig := make(map[string]interface{})
@ -242,7 +327,33 @@ func (c *CmdConfigure) processConfig(paramItems []templates.Param, deviceCategor
fmt.Println(c.localizedString("Config_Title", nil)) fmt.Println(c.localizedString("Config_Title", nil))
fmt.Println() fmt.Println()
for _, param := range paramItems { for _, param := range templateItem.Params {
if param.Dependencies != nil {
valid := true
for _, dep := range param.Dependencies {
_, valueParam := templateItem.ParamByName(dep.Name)
if valueParam == nil {
break
}
value := valueParam.Value
switch dep.Check {
case templates.DependencyCheckEmpty:
valid = value == ""
case templates.DependencyCheckNotEmpty:
valid = value != ""
case templates.DependencyCheckEqual:
valid = value == dep.Value
}
if !valid {
break
}
}
if !valid {
continue
}
}
switch param.Name { switch param.Name {
case templates.ParamModbus: case templates.ParamModbus:
c.processModbusConfig(param, deviceCategory, additionalConfig) c.processModbusConfig(param, deviceCategory, additionalConfig)
@ -271,7 +382,7 @@ func (c *CmdConfigure) processConfig(paramItems []templates.Param, deviceCategor
func (c *CmdConfigure) processListInputConfig(param templates.Param) []string { func (c *CmdConfigure) processListInputConfig(param templates.Param) []string {
var values []string var values []string
// ask for values until the decides stops // ask for values until the user decides to stop
for ok := true; ok; { for ok := true; ok; {
newValue := c.processInputConfig(param) newValue := c.processInputConfig(param)
values = append(values, newValue) values = append(values, newValue)
@ -291,8 +402,15 @@ func (c *CmdConfigure) processListInputConfig(param templates.Param) []string {
// handle user input for a simple one value input // handle user input for a simple one value input
func (c *CmdConfigure) processInputConfig(param templates.Param) string { func (c *CmdConfigure) processInputConfig(param templates.Param) string {
userFriendly := c.userFriendlyTexts(param) userFriendly := c.userFriendlyTexts(param)
label := userFriendly.Name
langLabel := param.Description.String(c.lang)
if langLabel != "" {
label = langLabel
}
return c.askValue(question{ return c.askValue(question{
label: userFriendly.Name, label: label,
defaultValue: userFriendly.Default, defaultValue: userFriendly.Default,
exampleValue: userFriendly.Example, exampleValue: userFriendly.Example,
help: userFriendly.Help.String(c.lang), help: userFriendly.Help.String(c.lang),

View file

@ -1,5 +1,9 @@
Intro = "Die nächsten Schritte führen durch die Einrichtung einer Konfigurationsdatei für evcc.\nBeachte dass dieser Prozess nicht alle möglichen Szenarien berücksichtigen kann.\nDurch Drücken von CTRL-C kann der Prozess abgebrochen werden.\n\nACHTUNG: Diese Funktionalität hat experimentellen Status!\n D.h. es kann möglich sein, dass die hiermit erstellte Konfigurationsdatei\n in einem Update nicht mehr funktionieren könnte und neu erzeugt werden müsste.\n Wir freuen uns auf euer Feedback auf https://github.com/evcc-io/evcc/discussions/\n\nAuf geht`s:" Intro = "Die nächsten Schritte führen durch die Einrichtung einer Konfigurationsdatei für evcc.\nBeachte dass dieser Prozess nicht alle möglichen Szenarien berücksichtigen kann.\nDurch Drücken von CTRL-C kann der Prozess abgebrochen werden.\n\nACHTUNG: Diese Funktionalität hat experimentellen Status!\n D.h. es kann möglich sein, dass die hiermit erstellte Konfigurationsdatei\n in einem Update nicht mehr funktionieren könnte und neu erzeugt werden müsste.\n Wir freuen uns auf euer Feedback auf https://github.com/evcc-io/evcc/discussions/\n\nAuf geht`s:"
Flow_Mode = "In welchem Modus soll die Konfiguration durchgeführt werden?"
Flow_Mode_Standard = "Standard Modus (So einfach und schnell wie möglich)"
Flow_Mode_Advanced = "Fortgeschrittener Modus (Detailliertere Fragen, erfordert jedoch technisches Know-How)"
Flow_Type = "Was möchtest du machen?" Flow_Type = "Was möchtest du machen?"
Flow_Type_NewConfiguration = "Eine neue evcc Konfigurationsdatei erstellen" Flow_Type_NewConfiguration = "Eine neue evcc Konfigurationsdatei erstellen"
Flow_Type_SingleDevice = "Ein einzelnes Gerät konfigurieren (muss manuell in eine Konfigurationsdatei eingetragen werden!)" Flow_Type_SingleDevice = "Ein einzelnes Gerät konfigurieren (muss manuell in eine Konfigurationsdatei eingetragen werden!)"
@ -90,6 +94,8 @@ TestingDevice_RepeatStep = "Möchtest du erneut ein Gerät aus der Liste auswäh
TestingDevice_AddFailed = "Der Test von {{ .Device }} ist fehlgeschlagen. Soll es trotzdem in die Konfiguration aufgenommen werden?" TestingDevice_AddFailed = "Der Test von {{ .Device }} ist fehlgeschlagen. Soll es trotzdem in die Konfiguration aufgenommen werden?"
TestingDevice_AddFailedUsage = "Der Test der {{ .Usage }} Konfiguration von {{ .Device }} ist fehlgeschlagen. Soll es trotzdem in die Konfiguration aufgenommen werden?" TestingDevice_AddFailedUsage = "Der Test der {{ .Usage }} Konfiguration von {{ .Device }} ist fehlgeschlagen. Soll es trotzdem in die Konfiguration aufgenommen werden?"
TestingMQTTFailed = "Der Test der MQTT Konfiguration ist fehlgeschlagen. Möchten Sie die Konfiguration wiederholen?"
Requirements_Title = "Das Gerät muss die folgenden Voraussetzungen erfüllen:" Requirements_Title = "Das Gerät muss die folgenden Voraussetzungen erfüllen:"
Requirements_More = "Weitere Informationen:" Requirements_More = "Weitere Informationen:"
@ -131,7 +137,8 @@ Loadpoint_ResetOnDisconnect = "Soll beim Abstecken des Ladekables von einem Fahr
Loadpoint_WallboxWOMeter = "Die Wallbox hat keinen Ladestromzähler. Hast du einen externen Zähler dafür installiert, der verwendet werden kann?" Loadpoint_WallboxWOMeter = "Die Wallbox hat keinen Ladestromzähler. Hast du einen externen Zähler dafür installiert, der verwendet werden kann?"
Loadpoint_WallboxMaxPower = "Was ist die maximale Leistung, welche die Wallbox zur Verfügung stellen kann?" Loadpoint_WallboxMaxPower = "Was ist die maximale Leistung, welche die Wallbox zur Verfügung stellen kann?"
Loadpoint_WallboxMaxAmperage = "Was ist die maximale Stromstärke, welche die Wallbox auf einer Phase zur Verfügung stellen kann?" Loadpoint_WallboxMinAmperage = "Was ist die MINIMALE Stromstärke, welche die Wallbox auf einer Phase zur Verfügung stellen kann?"
Loadpoint_WallboxMaxAmperage = "Was ist die MAXIMALE Stromstärke, welche die Wallbox auf einer Phase zur Verfügung stellen kann?"
Loadpoint_WallboxPhases = "Mit wievielen Phasen ist die Wallbox angeschlossen?" Loadpoint_WallboxPhases = "Mit wievielen Phasen ist die Wallbox angeschlossen?"
Loadpoint_WallboxPower36kW = "3,6 kW" Loadpoint_WallboxPower36kW = "3,6 kW"
Loadpoint_WallboxPower11kW = "11 kW" Loadpoint_WallboxPower11kW = "11 kW"

View file

@ -1,5 +1,9 @@
Intro = "The next steps will guide you through the creation of a configuration file for evcc.\nPlease note that this process cannot cover all possible scenarios.\nYou can cancel the process by pressing CTRL-C anytime.\n\nNOTE: This functionality is in an experimental state!\n This means that the created configuration file may not work\n in a future update of evcc and would need to be recreated.\n We are looking forward to your feedback at https://github.com/evcc-io/evcc/discussions/\n\nLet's go:" Intro = "The next steps will guide you through the creation of a configuration file for evcc.\nPlease note that this process cannot cover all possible scenarios.\nYou can cancel the process by pressing CTRL-C anytime.\n\nNOTE: This functionality is in an experimental state!\n This means that the created configuration file may not work\n in a future update of evcc and would need to be recreated.\n We are looking forward to your feedback at https://github.com/evcc-io/evcc/discussions/\n\nLet's go:"
Flow_Mode = "In which mode should this process guide you through the configuration?"
Flow_Mode_Standard = "Standard mode (As simple and quick as possible)"
Flow_Mode_Advanced = "Advanced mode (Ask more details, requires technical know-how)"
Flow_Type = "What do you want to do?" Flow_Type = "What do you want to do?"
Flow_Type_NewConfiguration = "Create a new evcc configuration file" Flow_Type_NewConfiguration = "Create a new evcc configuration file"
Flow_Type_SingleDevice = "Configure a single device (has to be added manually to a configuration file!)" Flow_Type_SingleDevice = "Configure a single device (has to be added manually to a configuration file!)"
@ -90,6 +94,8 @@ TestingDevice_RepeatStep = "Do you want to repeat choosing a device and configur
TestingDevice_AddFailed = "Testing {{ .Device }} failed. Do you want to add it anyway?" TestingDevice_AddFailed = "Testing {{ .Device }} failed. Do you want to add it anyway?"
TestingDevice_AddFailedUsage = "Testing of the {{ .Usage }} configuration of {{ .Device }} failed. Do you want to add it anyway?" TestingDevice_AddFailedUsage = "Testing of the {{ .Usage }} configuration of {{ .Device }} failed. Do you want to add it anyway?"
TestingMQTTFailed = "Testing the MQTT configuration failed. Do you want to repeat its configuration?"
Requirements_Title = "The device needs to provide the following requirements:" Requirements_Title = "The device needs to provide the following requirements:"
Requirements_More = "Additional information:" Requirements_More = "Additional information:"
@ -131,7 +137,8 @@ Loadpoint_ResetOnDisconnect = "Shall disconnecting the charging cable from the v
Loadpoint_WallboxWOMeter = "The wallbox does not provide power data. Do you have an external meter that can be used?" Loadpoint_WallboxWOMeter = "The wallbox does not provide power data. Do you have an external meter that can be used?"
Loadpoint_WallboxMaxPower = "What is the maximum power your wallbox can provide?" Loadpoint_WallboxMaxPower = "What is the maximum power your wallbox can provide?"
Loadpoint_WallboxMaxAmperage = "What is the maximum amperage your wallbox can provide on a single phase?" Loadpoint_WallboxMinAmperage = "What is the MINIMUM amperage your wallbox can provide on a single phase?"
Loadpoint_WallboxMaxAmperage = "What is the MAXIMUM amperage your wallbox can provide on a single phase?"
Loadpoint_WallboxPhases = "How many phases are connected to the wallbox?" Loadpoint_WallboxPhases = "How many phases are connected to the wallbox?"
Loadpoint_WallboxPower36kW = "3.6 kW" Loadpoint_WallboxPower36kW = "3.6 kW"
Loadpoint_WallboxPower11kW = "11 kW" Loadpoint_WallboxPower11kW = "11 kW"

View file

@ -65,19 +65,31 @@ func (c *CmdConfigure) Run(log *util.Logger, flagLang string, advancedMode, expa
c.setDefaultTexts() c.setDefaultTexts()
fmt.Println()
fmt.Println(c.localizedString("Intro", nil))
if !c.advancedMode {
// ask the user for his knowledge, so advanced mode can also be turned on this way
fmt.Println()
flowIndex, _ := c.askChoice(c.localizedString("Flow_Mode", nil), []string{
c.localizedString("Flow_Mode_Standard", nil),
c.localizedString("Flow_Mode_Advanced", nil),
})
if flowIndex == 1 {
c.advancedMode = true
}
}
if !c.advancedMode { if !c.advancedMode {
c.flowNewConfigFile() c.flowNewConfigFile()
return return
} }
fmt.Println() fmt.Println()
fmt.Println(c.localizedString("Intro", nil)) flowIndex, _ := c.askChoice(c.localizedString("Flow_Type", nil), []string{
flowChoices := []string{
c.localizedString("Flow_Type_NewConfiguration", nil), c.localizedString("Flow_Type_NewConfiguration", nil),
c.localizedString("Flow_Type_SingleDevice", nil), c.localizedString("Flow_Type_SingleDevice", nil),
} })
fmt.Println()
flowIndex, _ := c.askChoice(c.localizedString("Flow_Type", nil), flowChoices)
switch flowIndex { switch flowIndex {
case 0: case 0:
c.flowNewConfigFile() c.flowNewConfigFile()
@ -143,6 +155,10 @@ func (c *CmdConfigure) flowNewConfigFile() {
c.configureLoadpoints() c.configureLoadpoints()
c.configureSite() c.configureSite()
if c.advancedMode && c.configuration.config.SponsorToken == "" {
_ = c.askSponsortoken(true)
}
yaml, err := c.configuration.RenderConfiguration() yaml, err := c.configuration.RenderConfiguration()
if err != nil { if err != nil {
c.log.FATAL.Fatal(err) c.log.FATAL.Fatal(err)
@ -207,7 +223,7 @@ func (c *CmdConfigure) configureDevices(deviceCategory DeviceCategory, askAdding
} }
for ok := true; ok; { for ok := true; ok; {
device, err := c.configureDeviceCategory(deviceCategory) device, _, err := c.configureDeviceCategory(deviceCategory)
if err != nil { if err != nil {
break break
} }
@ -243,7 +259,7 @@ func (c *CmdConfigure) configureLoadpoints() {
MinCurrent: 6, MinCurrent: 6,
} }
charger, err := c.configureDeviceCategory(DeviceCategoryCharger) charger, capabilities, err := c.configureDeviceCategory(DeviceCategoryCharger)
if err != nil { if err != nil {
break break
} }
@ -253,7 +269,7 @@ func (c *CmdConfigure) configureLoadpoints() {
if !chargerHasMeter { if !chargerHasMeter {
if c.askYesNo(c.localizedString("Loadpoint_WallboxWOMeter", nil)) { if c.askYesNo(c.localizedString("Loadpoint_WallboxWOMeter", nil)) {
chargeMeter, err := c.configureDeviceCategory(DeviceCategoryChargeMeter) chargeMeter, _, err := c.configureDeviceCategory(DeviceCategoryChargeMeter)
if err == nil { if err == nil {
loadpoint.ChargeMeter = chargeMeter.Name loadpoint.ChargeMeter = chargeMeter.Name
chargerHasMeter = true chargerHasMeter = true
@ -272,39 +288,26 @@ func (c *CmdConfigure) configureLoadpoints() {
} }
} }
powerChoices := []string{ var minValue int = 6
c.localizedString("Loadpoint_WallboxPower36kW", nil), if capabilities.ISO151182 {
c.localizedString("Loadpoint_WallboxPower11kW", nil), minValue = 2
c.localizedString("Loadpoint_WallboxPower22kW", nil),
c.localizedString("Loadpoint_WallboxPowerOther", nil),
} }
fmt.Println()
powerIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxMaxPower", nil), powerChoices) if c.advancedMode {
loadpoint.MinCurrent = 6 minAmperage := c.askValue(question{
switch powerIndex { label: c.localizedString("Loadpoint_WallboxMinAmperage", nil),
case 0: valueType: templates.ParamValueTypeNumber,
loadpoint.MaxCurrent = 16 minNumberValue: int64(minValue),
if !chargerHasMeter { maxNumberValue: 32,
loadpoint.Phases = 1 required: true})
} loadpoint.MinCurrent, _ = strconv.Atoi(minAmperage)
case 1: maxAmperage := c.askValue(question{
loadpoint.MaxCurrent = 16
if !chargerHasMeter {
loadpoint.Phases = 3
}
case 2:
loadpoint.MaxCurrent = 32
if !chargerHasMeter {
loadpoint.Phases = 3
}
case 3:
amperage := c.askValue(question{
label: c.localizedString("Loadpoint_WallboxMaxAmperage", nil), label: c.localizedString("Loadpoint_WallboxMaxAmperage", nil),
valueType: templates.ParamValueTypeNumber, valueType: templates.ParamValueTypeNumber,
minNumberValue: 6, minNumberValue: 6,
maxNumberValue: 32, maxNumberValue: 32,
required: true}) required: true})
loadpoint.MaxCurrent, _ = strconv.Atoi(amperage) loadpoint.MaxCurrent, _ = strconv.Atoi(maxAmperage)
if !chargerHasMeter { if !chargerHasMeter {
phaseChoices := []string{"1", "2", "3"} phaseChoices := []string{"1", "2", "3"}
@ -312,6 +315,48 @@ func (c *CmdConfigure) configureLoadpoints() {
phaseIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxPhases", nil), phaseChoices) phaseIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxPhases", nil), phaseChoices)
loadpoint.Phases = phaseIndex + 1 loadpoint.Phases = phaseIndex + 1
} }
} else {
powerChoices := []string{
c.localizedString("Loadpoint_WallboxPower36kW", nil),
c.localizedString("Loadpoint_WallboxPower11kW", nil),
c.localizedString("Loadpoint_WallboxPower22kW", nil),
c.localizedString("Loadpoint_WallboxPowerOther", nil),
}
fmt.Println()
powerIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxMaxPower", nil), powerChoices)
loadpoint.MinCurrent = minValue
switch powerIndex {
case 0:
loadpoint.MaxCurrent = 16
if !chargerHasMeter {
loadpoint.Phases = 1
}
case 1:
loadpoint.MaxCurrent = 16
if !chargerHasMeter {
loadpoint.Phases = 3
}
case 2:
loadpoint.MaxCurrent = 32
if !chargerHasMeter {
loadpoint.Phases = 3
}
case 3:
amperage := c.askValue(question{
label: c.localizedString("Loadpoint_WallboxMaxAmperage", nil),
valueType: templates.ParamValueTypeNumber,
minNumberValue: int64(minValue),
maxNumberValue: 32,
required: true})
loadpoint.MaxCurrent, _ = strconv.Atoi(amperage)
if !chargerHasMeter {
phaseChoices := []string{"1", "2", "3"}
fmt.Println()
phaseIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxPhases", nil), phaseChoices)
loadpoint.Phases = phaseIndex + 1
}
}
} }
fmt.Println() fmt.Println()

View file

@ -64,15 +64,15 @@ func (c *CmdConfigure) askSelection(message string, items []string) (error, stri
// selectItem selects item from list // selectItem selects item from list
func (c *CmdConfigure) selectItem(deviceCategory DeviceCategory) templates.Template { func (c *CmdConfigure) selectItem(deviceCategory DeviceCategory) templates.Template {
var emptyItem templates.Template var emptyItem templates.Template
emptyItem.Description = c.localizedString("ItemNotPresent", nil) emptyItem.Description.SetString(c.localizedString("ItemNotPresent", nil), c.lang)
elements := c.fetchElements(deviceCategory) elements := c.fetchElements(deviceCategory)
elements = append(elements, emptyItem) elements = append(elements, emptyItem)
var items []string var items []string
for _, item := range elements { for _, item := range elements {
if item.Description != "" { if item.Description.String(c.lang) != "" {
items = append(items, item.Description) items = append(items, item.Description.String(c.lang))
} }
} }

View file

@ -26,7 +26,7 @@ func NewMeterFromTemplateConfig(other map[string]interface{}) (api.Meter, error)
return nil, err return nil, err
} }
b, _, err := tmpl.RenderResult(false, other) b, _, err := tmpl.RenderResult(templates.TemplateRenderModeInstance, other)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -33,7 +33,7 @@ func TestMeterTemplates(t *testing.T) {
tmpl := tmpl tmpl := tmpl
// set default values for all params // set default values for all params
values := tmpl.Defaults(true) values := tmpl.Defaults(templates.TemplateRenderModeUnitTest)
// set the template value which is needed for rendering // set the template value which is needed for rendering
values["template"] = tmpl.Template values["template"] = tmpl.Template
@ -41,10 +41,11 @@ func TestMeterTemplates(t *testing.T) {
// set modbus default test values // set modbus default test values
if values[templates.ParamModbus] != nil { if values[templates.ParamModbus] != nil {
modbusChoices := tmpl.ModbusChoices() modbusChoices := tmpl.ModbusChoices()
// we only test one modbus setup
if funk.ContainsString(modbusChoices, templates.ModbusChoiceTCPIP) { if funk.ContainsString(modbusChoices, templates.ModbusChoiceTCPIP) {
values[templates.ModbusKeyTCPIP] = true values[templates.ModbusTCPIP] = true
} else { } else {
values[templates.ModbusKeyRS485TCPIP] = true values[templates.ModbusRS485TCPIP] = true
} }
} }
@ -70,14 +71,16 @@ func runTest(t *testing.T, tmpl templates.Template, values map[string]interface{
t.Run(tmpl.Template, func(t *testing.T) { t.Run(tmpl.Template, func(t *testing.T) {
t.Parallel() t.Parallel()
b, values, err := tmpl.RenderResult(true, values) b, values, err := tmpl.RenderResult(templates.TemplateRenderModeUnitTest, values)
if err != nil { if err != nil {
t.Logf("%s: %s", tmpl.Template, b) t.Logf("Template: %s", tmpl.Template)
t.Logf("%s", string(b))
t.Error(err) t.Error(err)
} }
if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) { if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
t.Logf("%s", tmpl.Template) t.Logf("Template: %s", tmpl.Template)
t.Logf("%s", string(b))
t.Error(err) t.Error(err)
} }
}) })

View file

@ -18,7 +18,7 @@ The following describes each possible element in a yaml file
## `description` ## `description`
`description` expects a human readable description of the device, best language neutral and containing the name of the product `description` expects a human readable description of the device, best containing the name of the product. Values are language specific names via `de`, `en`, `generic` (if string is language independent)
## `generic` ## `generic`
@ -60,6 +60,14 @@ Allows to define a list of meter devices that are typically installed with this
Example Use Case: With SMA Home Manager, there can be a SMA Energy Meter used for getting the PV generation or multiple SMA PV inverters. But never both together. So if the used added an SMA Energy Meter, then the flow shoudn't ask for SMA PV inverters. Example Use Case: With SMA Home Manager, there can be a SMA Energy Meter used for getting the PV generation or multiple SMA PV inverters. But never both together. So if the used added an SMA Energy Meter, then the flow shoudn't ask for SMA PV inverters.
## `capabilities`
`capabilities` provides an option to define special capabilities of the devie
**Possible Values**:
- `iso151182: true`: If the charger supports communicating via ISO15118-2
## `requirements` ## `requirements`
`requirements` provides an option to define various requirements / dependencies that need to be setup `requirements` provides an option to define various requirements / dependencies that need to be setup
@ -95,6 +103,39 @@ Example Use Case: With SMA Home Manager, there can be a SMA Energy Meter used fo
- `usage`: specifies a list of meter classes, the device can be used for. Possible values are `grid`, `pv`, `battery`, and `charger` - `usage`: specifies a list of meter classes, the device can be used for. Possible values are `grid`, `pv`, `battery`, and `charger`
- `modbus`: specifies that this device is accessed via modbus. It requires the `choice` property to have a list of possible interface values the device provides. These values can be `rs485` and `tcpip`. The command will use either to ask the appropriate questions and settings. The `render` section needs to include the string `{{include "modbus" .}}` in all places where the configuration needs modbus settings. - `modbus`: specifies that this device is accessed via modbus. It requires the `choice` property to have a list of possible interface values the device provides. These values can be `rs485` and `tcpip`. The command will use either to ask the appropriate questions and settings. The `render` section needs to include the string `{{include "modbus" .}}` in all places where the configuration needs modbus settings.
#### Modbus Options
- `id`: Device specific default for modbus ID
- `port`: Device specific default for modbus TCPIP port
- `baudrate`: Device specific default for modbus RS485 baudrate
- `comset`: Device specific default for modbus RS485 comset
### `description`
`description` allows to define user friendly and language specific names via `de`, `en`, `generic`
### `dependencies`
`dependencies` allows to define a list of checks, when this param should be presented to the user, if it should be only in special cases
#### `name`
`name` referenced the `param` `name` value
#### `check`
`check` defines which kind of check should be performed
**Possible values**:
- `empty`: if the `value` of the referenced `param` `name` should be empty
- `notempty`: if the `value` of the referenced `param` `name` should be **NOT** empty
- `equal`: if the `value` of the referenced `param` `name` should match the value of the `value` property
#### `value`
`value` property is used in the `equal` `check`
### `required` ### `required`
`required: true` defines if the user has to provide a value. Default is `false` `required: true` defines if the user has to provide a value. Default is `false`

View file

@ -1,5 +1,6 @@
template: abl template: abl
description: ABL eMH / SENEC.Wallbox pro description:
generic: ABL eMH / SENEC.Wallbox pro
requirements: requirements:
sponsorship: true sponsorship: true
params: params:

View file

@ -1,5 +1,6 @@
template: cfos_charger template: cfos
description: cFos PowerBrain description:
generic: cFos PowerBrain
requirements: requirements:
sponsorship: true sponsorship: true
params: params:

View file

@ -1,7 +1,6 @@
template: daheimladen template: daheimladen
description: Daheimladen (Cloud API) description:
requirements: generic: Daheimladen (Cloud API)
sponsorship: true
params: params:
- name: token - name: token
help: help:

View file

@ -1,5 +1,6 @@
template: easee template: easee
description: Easee Home (Cloud API) description:
generic: Easee Home (Cloud API)
requirements: requirements:
sponsorship: true sponsorship: true
params: params:

View file

@ -1,6 +1,10 @@
template: eebus template: eebus
description: EEBUS kompatible Wallbox description:
de: EEBUS kompatible Wallbox
en: EEBUS compatible Wallbox
generic: true generic: true
capabilities:
iso151182: true
requirements: requirements:
eebus: true eebus: true
params: params:

View file

@ -1,5 +1,6 @@
template: evse_din template: evse_din
description: EVSE DIN description:
generic: EVSE DIN
params: params:
- name: modbus - name: modbus
choice: ["rs485"] choice: ["rs485"]

View file

@ -1,5 +1,6 @@
template: evsewifi template: evsewifi
description: EVSE-Wifi description:
generic: EVSE-Wifi
params: params:
- name: host - name: host
required: true required: true

View file

@ -1,5 +1,6 @@
template: fritzdect template: fritzdect
description: FritzDECT description:
generic: FritzDECT
params: params:
- name: uri - name: uri
default: https://fritz.box default: https://fritz.box

View file

@ -1,5 +1,6 @@
template: go-e template: go-e
description: go-eCharger Home/Pro/V3 description:
generic: go-eCharger Home/Pro/V3
requirements: requirements:
description: description:
en: Home und PRO require firmware 040.0 or later, V3 requires firmware 052.1 or later. en: Home und PRO require firmware 040.0 or later, V3 requires firmware 052.1 or later.

View file

@ -1,5 +1,6 @@
template: heidelberg template: heidelberg
description: Heidelberg Energy Control (Modbus RTU) description:
generic: Heidelberg Energy Control (Modbus RTU)
requirements: requirements:
sponsorship: true sponsorship: true
params: params:

View file

@ -1,5 +1,6 @@
template: ichargecion template: ichargecion
description: i-CHARGE CION (Modbus RTU-over-TCP) description:
generic: i-CHARGE CION (Modbus RTU-over-TCP)
params: params:
- name: modbus - name: modbus
choice: ["tcpip"] choice: ["tcpip"]

View file

@ -1,5 +1,6 @@
template: innogy-ebox template: innogy-ebox
description: Innogy eBox description:
generic: Innogy eBox
requirements: requirements:
sponsorship: true sponsorship: true
params: params:

View file

@ -1,5 +1,6 @@
template: keba template: keba
description: KEBA Connect description:
generic: KEBA Connect
requirements: requirements:
description: description:
en: This requires the UDP function to be enabled with DIP 1.3 = ON, see the installation manual. en: This requires the UDP function to be enabled with DIP 1.3 = ON, see the installation manual.

View file

@ -1,5 +1,6 @@
template: nrgkick-bluetooth template: nrgkick-bluetooth
description: NRGKick Bluetooth description:
generic: NRGKick Bluetooth
params: params:
- name: mac - name: mac
required: true required: true

View file

@ -1,5 +1,6 @@
template: nrgkick-connect template: nrgkick-connect
description: NRGKick Connect description:
generic: NRGKick Connect
params: params:
- name: host - name: host
required: true required: true

View file

@ -1,5 +1,6 @@
template: openwb template: openwb
description: openWB (MQTT) description:
generic: openWB (MQTT)
requirements: requirements:
description: description:
en: The wallbox has to be configured running as a loadpoint. en: The wallbox has to be configured running as a loadpoint.

View file

@ -1,5 +1,6 @@
template: phoenix-em-eth template: phoenix-em-eth
description: Phoenix EM-CP-PP-ETH Controller (Modbus TCP) description:
generic: Phoenix EM-CP-PP-ETH Controller (Modbus TCP)
generic: true generic: true
requirements: requirements:
description: description:

View file

@ -1,5 +1,6 @@
template: phoenix-ev-eth template: phoenix-ev-eth
description: Phoenix EV-ETH Controller (Modbus TCP) description:
generic: Phoenix EV-ETH Controller (Modbus TCP)
generic: true generic: true
requirements: requirements:
description: description:

View file

@ -1,5 +1,6 @@
template: phoenix-ev-ser template: phoenix-ev-ser
description: Phoenix EV-SER Controller (Modbus RTU) description:
generic: Phoenix EV-SER Controller (Modbus RTU)
generic: true generic: true
params: params:
- name: modbus - name: modbus

View file

@ -1,5 +1,8 @@
template: pmcc template: pmcc
description: Porsche Mobile Charger Connect description:
generic: Porsche Mobile Charger Connect
capabilities:
iso151182: true
requirements: requirements:
eebus: true eebus: true
params: params:

View file

@ -1,5 +1,6 @@
template: pmcp template: pmcp
description: Porsche Mobile Charger Plus description:
generic: Porsche Mobile Charger Plus
requirements: requirements:
eebus: true eebus: true
params: params:

View file

@ -1,5 +1,6 @@
template: shelly template: shelly
description: Shelly description:
generic: Shelly
params: params:
- name: host - name: host
required: true required: true

View file

@ -1,5 +1,6 @@
template: tasmota template: tasmota
description: Tasmota description:
generic: Tasmota
params: params:
- name: host - name: host
required: true required: true

View file

@ -1,5 +1,6 @@
template: tinkerforge-warp-pro template: tinkerforge-warp-pro
description: TinkerForge WARP Charger Pro description:
generic: TinkerForge WARP Charger Pro
requirements: requirements:
description: description:
en: A MQTT Broker software needs to be running on a computer. The MQTT connection also needs to be configured in the wallbox. en: A MQTT Broker software needs to be running on a computer. The MQTT connection also needs to be configured in the wallbox.

View file

@ -1,5 +1,6 @@
template: tinkerforge-warp template: tinkerforge-warp
description: TinkerForge WARP Charger description:
generic: TinkerForge WARP Charger
requirements: requirements:
description: description:
en: A MQTT Broker software needs to be running on a computer. The MQTT connection also needs to be configured in the wallbox. en: A MQTT Broker software needs to be running on a computer. The MQTT connection also needs to be configured in the wallbox.

View file

@ -1,5 +1,6 @@
template: tplink template: tplink
description: TP-LINK Smart Plug description:
generic: TP-LINK Smart Plug
params: params:
- name: host - name: host
required: true required: true

View file

@ -1,5 +1,7 @@
template: wallbe-meter template: wallbe-meter
description: Wallbe (Eco, Pro) with Meter description:
de: Wallbe (Eco, Pro) mit Strommessgerät
en: Wallbe (Eco, Pro) with Meter
requirements: requirements:
description: description:
en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'. en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'.

View file

@ -1,5 +1,7 @@
template: wallbe-pre2019-meter template: wallbe-pre2019-meter
description: Wallbe (Eco, Pro) (pre ~2019) with Meter description:
de: Wallbe (Eco, Pro) (vor ~2019) mit Strommessgerät
en: Wallbe (Eco, Pro) (pre ~2019) with Meter
requirements: requirements:
description: description:
en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'. en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'.

View file

@ -1,5 +1,7 @@
template: wallbe-pre2019 template: wallbe-pre2019
description: Wallbe (Eco, Pro) (pre ~2019) description:
de: Wallbe (Eco, Pro) (vor ~2019)
en: Wallbe (Eco, Pro) (pre ~2019)
requirements: requirements:
description: description:
en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'. en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'.

View file

@ -1,5 +1,6 @@
template: wallbe template: wallbe
description: Wallbe (Eco, Pro) description:
generic: Wallbe (Eco, Pro)
requirements: requirements:
description: description:
en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'. en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'.

View file

@ -1,5 +1,6 @@
template: cfos-meter template: cfos
description: cFos PowerBrain Meter description:
generic: cFos PowerBrain Meter
requirements: requirements:
sponsorship: true sponsorship: true
params: params:

View file

@ -1,5 +1,6 @@
template: discovergy template: discovergy
description: Discovergy (Cloud) description:
generic: Discovergy (Cloud)
params: params:
- name: usage - name: usage
choice: ["grid", "pv"] choice: ["grid", "pv"]

View file

@ -1,5 +1,6 @@
template: e3dc template: e3dc
description: E3DC description:
generic: E3DC
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,7 @@
template: eastron template: eastron
description: Eastron SDM Modbus Meter description:
de: Eastron SDM Modbus Strommessgeräte
en: Eastron SDM Modbus Meter
params: params:
- name: usage - name: usage
choice: ["grid", "pv", "battery", "charge"] choice: ["grid", "pv", "battery", "charge"]

View file

@ -1,5 +1,6 @@
template: fritzdect template: fritzdect
description: FritzDECT description:
generic: FritzDECT
params: params:
- name: usage - name: usage
choice: ["pv", "charge"] choice: ["pv", "charge"]
@ -17,12 +18,9 @@ params:
help: help:
en: The AIN is printed on the type label on the back of the device. en: The AIN is printed on the type label on the back of the device.
de: Die AIN ist auf dem Typenschild auf der Geräterückseite aufgedruckt. de: Die AIN ist auf dem Typenschild auf der Geräterückseite aufgedruckt.
- name: standbypower
default: 15
render: | render: |
type: fritzdect type: fritzdect
uri: {{ .uri }} uri: {{ .uri }}
user: {{ .user }} user: {{ .user }}
password: {{ .password }} password: {{ .password }}
ain: {{ .ain }} # switch actor identification number without blanks (see AIN number on switch sticker) ain: {{ .ain }} # switch actor identification number without blanks (see AIN number on switch sticker)
standbypower: {{ .standbypower }} # treat as charging above this power

View file

@ -1,5 +1,6 @@
template: fronius-gen24 template: fronius-gen24
description: Fronius Symo GEN24 Plus description:
generic: Fronius Symo GEN24 Plus
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: fronius-solarapi-v1 template: fronius-solarapi-v1
description: Fronius Solar API V1 description:
generic: Fronius Solar API V1
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: huawei-sun2000-8ktl template: huawei-sun2000-8ktl
description: Huawei SUN2000-8KTL description:
generic: Huawei SUN2000-8KTL
params: params:
- name: usage - name: usage
choice: [ "pv" ] choice: [ "pv" ]

View file

@ -1,5 +1,7 @@
template: kostal-ksem-inverter template: kostal-ksem-inverter
description: Kostal Smart Energy Meter via inverter description:
de: Kostal Smart Energy Meter über den Wechselrichter
en: Kostal Smart Energy Meter via inverter
params: params:
- name: usage - name: usage
choice: [ "grid" ] choice: [ "grid" ]

View file

@ -1,5 +1,6 @@
template: kostal-ksem template: kostal-ksem
description: Kostal Smart Energy Meter description:
generic: Kostal Smart Energy Meter
params: params:
- name: usage - name: usage
choice: [ "grid" ] choice: [ "grid" ]

View file

@ -1,5 +1,6 @@
template: kostal-piko-mp-plus template: kostal-piko-mp-plus
description: Kostal Piko MP Plus description:
generic: Kostal Piko MP Plus
guidedsetup: guidedsetup:
enable: true enable: true
linked: linked:

View file

@ -1,5 +1,6 @@
template: kostal-piko template: kostal-piko
description: Kostal Piko + Piko BA description:
generic: Kostal Piko + Piko BA
guidedsetup: guidedsetup:
enable: true enable: true
params: params:
@ -17,18 +18,18 @@ render: |
add: add:
- source: http - source: http
uri: http://{{ .host }}/api/dxs.json?dxsEntries=67109120 # PV AC Ausgang uri: http://{{ .host }}/api/dxs.json?dxsEntries=67109120 # PV AC Ausgang
jq: .dxsEntries[0].value jq: .dxsEntries[0].value | if . == null then 0 else . end
scale: -1.0 scale: -1.0
- source: http - source: http
uri: http://{{ .host }}/api/dxs.json?dxsEntries=83886848 # Netzbezug uri: http://{{ .host }}/api/dxs.json?dxsEntries=83886848 # Netzbezug
jq: .dxsEntries[0].value jq: .dxsEntries[0].value | if . == null then 0 else . end
- source: http - source: http
uri: http://{{ .host }}/api/dxs.json?dxsEntries=83886336 # PV Eigenverbrauch uri: http://{{ .host }}/api/dxs.json?dxsEntries=83886336 # PV Eigenverbrauch
jq: .dxsEntries[0].value jq: .dxsEntries[0].value | if . == null then 0 else . end
{{- end -}} {{- end -}}
{{- if eq .usage "pv" }} {{- if eq .usage "pv" }}
# PV # PV
source: http source: http
uri: http://{{ .host }}/api/dxs.json?dxsEntries=67109120 # PV AC Ausgang uri: http://{{ .host }}/api/dxs.json?dxsEntries=67109120 # PV AC Ausgang
jq: .dxsEntries[0].value jq: .dxsEntries[0].value | if . == null then 0 else . end
{{- end -}} {{- end -}}

View file

@ -1,5 +1,6 @@
template: kostal-plenticore template: kostal-plenticore
description: Kostal Plenticore Hybrid description:
generic: Kostal Plenticore Hybrid
guidedsetup: guidedsetup:
enable: true enable: true
linked: linked:

View file

@ -1,5 +1,6 @@
template: lg-ess-home-8-10 template: lg-ess-home-8-10
description: LG ESS Home 8/10 description:
generic: LG ESS Home 8/10
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -0,0 +1,105 @@
template: mqtt-meter
description:
de: MQTT Strommessgerät
en: MQTT Meter
generic: true
requirements:
mqtt: true
params:
- name: usage
choice: [ "grid", "pv", "charge", "battery" ]
- name: power
description:
de: MQTT Topic für die Leistung (W)
en: MQTT Topic for power (W)
required: true
- name: scalepower
description:
de: Skalierung der Leistungswerte
en: Scaling of the power value
default: 1
help:
de: Die Leistung wird in Watt erwartet. Falls z.b. kW geliefert wird, kann dies hier mit dem Wert 1000 angepasst werden
en: Power is expected to be provided in Watt. If it is provided in kW instead, set this value to 1000 to correct it
- name: energy
description:
de: MQTT Topic für Energiemenge (Wh)
en: MQTT Topic for energy (Wh)
- name: scaleenergy
dependencies:
- name: energy
check: notempty
description:
de: Skalierung der Energiement
en: Scaling of the energy value
default: 1
help:
de: Die Energiemenge wird in kWh erwartet. Falls z.b. Wh geliefert wird, kann dies hier mit dem Wert 0.001 angepasst werden
en: Power is expected to be provided in kWh. If it is provided in Wh instead, set this value to 0.001 to correct it
- name: soc
description:
de: MQTT Topic für den SoC der Batterie (%)
en: MQTT Topic for battery SoC (%)
dependencies:
- name: usage
check: equal
value: battery
- name: currentp1
description:
de: MQTT Topic Stromstärke (A) Phase 1
en: MQTT Topic Amperage (A) phase 1
- name: currentp2
description:
de: MQTT Topic Stromstärke (A) Phase 2
en: MQTT Topic Amperage (A) phase 2
dependencies:
- name: currentp1
check: notempty
- name: currentp3
description:
de: MQTT Topic Stromstärke (A) Phase 3
en: MQTT Topic Amperage (A) phase 3
dependencies:
- name: currentp1
check: notempty
- name: timeout
default: 30
help:
de: Akzeptiere keine Daten die älter als dieser Wert in Sekunden ist
en: Don't accept values older than this value in seconds
render: |
type: custom
power:
source: mqtt
topic: {{ .power }}
scale: {{ .scalepower }}
timeout: {{ .timeout }}
{{- if ne .energy1 "" }}
energy:
source: mqtt
topic: {{ .energy }}
scale: {{ .scaleenergy }}
timeout: {{ .timeout }}
{{- end -}}
{{- if eq .usage "battery" }}
soc:
source: mqtt
topic: {{ .soc }}
timeout: {{ .timeout }}
{{- end -}}
{{- if ne .currentp1 "" }}
currents:
- source: mqtt
topic: {{ .currentp1 }}
timeout: {{ .timeout }}
{{- if ne .currentp2 "" }}
- source: mqtt
topic: {{ .currentp2 }}
timeout: {{ .timeout }}
{{- end -}}
{{- if ne .currentp3 "" }}
- source: mqtt
topic: {{ .currentp3 }}
timeout: {{ .timeout }}
{{- end -}}
{{- end -}}

View file

@ -1,5 +1,6 @@
template: powerdog template: powerdog
description: Powerdog description:
generic: Powerdog
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: powerfox-poweropti template: powerfox-poweropti
description: Powerfox Poweropti (Cloud) description:
generic: Powerfox Poweropti (Cloud)
params: params:
- name: usage - name: usage
choice: [ "grid" ] choice: [ "grid" ]

View file

@ -1,5 +1,6 @@
template: rct-power template: rct-power
description: RCT Power description:
generic: RCT Power
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: senec-home template: senec-home
description: SENEC.Home description:
generic: SENEC.Home
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: shelly-3em template: shelly-3em
description: Shelly 3EM description:
generic: Shelly 3EM
params: params:
- name: usage - name: usage
choice: [ "grid" ] choice: [ "grid" ]

View file

@ -1,5 +1,6 @@
template: sma-energy-meter template: sma-energy-meter
description: SMA Energy Meter description:
generic: SMA Energy Meter (Produktname)
params: params:
- name: usage - name: usage
choice: ["grid", "pv", "battery"] choice: ["grid", "pv", "battery"]

View file

@ -1,5 +1,6 @@
template: sma-home-manager template: sma-home-manager
description: SMA Sunny Home Manager 2.0 description:
generic: SMA Sunny Home Manager 2.0
requirements: requirements:
hems: sma hems: sma
guidedsetup: guidedsetup:

View file

@ -1,5 +1,7 @@
template: sma-inverter template: sma-inverter
description: SMA Inverter/Wechselrichter description:
de: SMA Wechselrichter
en: SMA Inverter
params: params:
- name: usage - name: usage
choice: ["pv", "battery"] choice: ["pv", "battery"]

View file

@ -1,7 +1,12 @@
template: solaredge-hybrid template: solaredge-hybrid
description: Solaredge Hybrid description:
generic: Solaredge Hybrid
guidedsetup: guidedsetup:
enable: true enable: true
requirements:
description:
de: Nur ein System kann auf den Wechselrichter zugreifen!
en: Only one system may access the inverter!
params: params:
- name: usage - name: usage
choice: [ "grid", "pv", "battery" ] choice: [ "grid", "pv", "battery" ]

View file

@ -0,0 +1,17 @@
template: solaredge-inverter
description:
de: Solaredge Wechselrichter
en: Solaredge Inverter
guidedsetup:
enable: true
params:
- name: usage
choice: [ "pv" ]
- name: modbus
choice: [ "tcpip" ]
id: 1
port: 1502
render: |
type: modbus
model: sunspec
{{ include "modbus" . }}

View file

@ -1,5 +1,6 @@
template: solarlog template: solarlog
description: Solarlog description:
generic: Solarlog
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: solarwatt-myreserve template: solarwatt-myreserve
description: Solarwatt MyReserve description:
generic: Solarwatt MyReserve
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: sonnenbatterie-eco10 template: sonnenbatterie-eco10
description: Sonnenbatterie Eco/10 description:
generic: Sonnenbatterie Eco/10
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -0,0 +1,39 @@
template: sungrow
description:
generic: Sungrow (Beta)
guidedsetup:
enable: true
params:
- name: usage
choice: [ "grid", "pv", "battery" ]
- name: modbus
choice: [ "rs485", "tcpip" ]
render: |
type: custom
power:
source: modbus
{{include "modbus" . | indent 2}}
register:
type: input
{{- if eq .usage "grid" }}
address: 13009 # Export power
decode: int32s
scale: -1
{{- end }}
{{- if eq .usage "pv" }}
address: 5016 # Total DC power
decode: uint32s
{{- end }}
{{- if eq .usage "battery" }}
address: 13021 # Battery power
decode: int16
#scale: -1 # charging sign form 13000 & 0x2
soc:
source: modbus
{{include "modbus" . | indent 2}}
register:
address: 13022 # Battery level
type: input
decode: int16
scale: 0.1
{{- end }}

View file

@ -1,5 +1,7 @@
template: sunspec template: sunspec
description: Inverter / Wechselrichter (SunSpec) description:
de: Wechselrichter (SunSpec)
en: Inverter (SunSpec)
generic: true generic: true
params: params:
- name: usage - name: usage

View file

@ -1,5 +1,6 @@
template: tesla-powerwall template: tesla-powerwall
description: Tesla Powerwall description:
generic: Tesla Powerwall
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -1,5 +1,6 @@
template: varta-energiespeicher template: varta-energiespeicher
description: VARTA Energiespeicher description:
generic: VARTA Energiespeicher
guidedsetup: guidedsetup:
enable: true enable: true
params: params:

View file

@ -0,0 +1,69 @@
template: victron-energy
description:
generic: Victron Energy
guidedsetup:
enable: true
params:
- name: usage
choice: [ "grid", "pv", "battery" ]
- name: host
required: true
example: 192.0.2.2
- name: port
default: 502
render: |
type: custom
power:
{{- if eq .usage "grid" }}
source: calc
add:
- source: modbus
uri: {{ .host }}:{{ .port }}
id: 50 # com.victronenergy.grid
register:
address: 2600 # L1 grid power
type: input
decode: int16
- source: modbus
uri: {{ .host }}:{{ .port }}
id: 50 # com.victronenergy.grid
register:
address: 2601 # L2 grid power
type: input
decode: int16
- source: modbus
uri: {{ .host }}:{{ .port }}
id: 50 # com.victronenergy.grid
register:
address: 2602 # L3 grid power
type: input
decode: int16
{{- end }}
{{- if eq .usage "pv" }}
source: modbus
uri: {{ .host }}:{{ .port }}
id: 20 # com.victronenergy.pvinverter
register:
address: 1052 # Total AC Power
type: input
decode: int32
{{- end }}
{{- if eq .usage "battery" }}
source: modbus
uri: {{ .host }}:{{ .port }}
id: 100 # com.victronenergy.system
register:
address: 842 # active DC power
type: input
decode: int16
scale: -1
soc:
source: modbus
uri: {{ .host }}:{{ .port }}
id: 225 # com.victronenergy.battery
register:
address: 266 # SoC
type: input
decode: uint16
scale: 0.1
{{- end }}

View file

@ -1,5 +1,6 @@
template: volkszaehler-http template: volkszaehler-http
description: Volkszähler (HTTP API) description:
generic: Volkszähler (HTTP API)
generic: true generic: true
params: params:
- name: usage - name: usage

View file

@ -1,5 +1,6 @@
template: volkszaehler-importexport template: volkszaehler-importexport
description: Volkszähler (HTTP API, Import & Export) description:
generic: Volkszähler (HTTP API, Import & Export)
generic: true generic: true
params: params:
- name: usage - name: usage

View file

@ -1,5 +1,6 @@
template: volkszaehler-ws template: volkszaehler-ws
description: Volkszähler (WebSocket API) description:
generic: Volkszähler (WebSocket API)
generic: true generic: true
params: params:
- name: usage - name: usage

View file

@ -1,5 +1,6 @@
template: vzlogger template: vzlogger
description: vzlogger description:
generic: vzlogger
generic: true generic: true
params: params:
- name: usage - name: usage

View file

@ -1,5 +1,6 @@
template: audi template: audi
description: Audi description:
generic: Audi
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: bmw template: bmw
description: BMW description:
generic: BMW
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: carwings template: carwings
description: Nissan Leaf (pre 2019) description:
generic: Nissan Leaf (pre 2019)
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: citroen template: citroen
description: Citroen description:
generic: Citroen
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: dacia template: dacia
description: Dacia description:
generic: Dacia
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: ds template: ds
description: DS description:
generic: DS
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,7 @@
template: evnotify template: evnotify
description: evNotify description:
generic: evNotify
generic: true
params: params:
- name: title - name: title
- name: akey - name: akey

View file

@ -1,5 +1,6 @@
template: fiat template: fiat
description: Fiat description:
generic: Fiat
params: params:
- base: vehiclebase - base: vehiclebase
- name: vin - name: vin

View file

@ -1,5 +1,6 @@
template: ford template: ford
description: Ford description:
generic: Ford
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: hyundai template: hyundai
description: Hyundai description:
generic: Hyundai
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: kia template: kia
description: Kia description:
generic: Kia
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: mini template: mini
description: Mini description:
generic: Mini
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: nissan template: nissan
description: Nissan description:
generic: Nissan
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: niu-e-scooter template: niu-e-scooter
description: NIU E-Scooter description:
generic: NIU E-Scooter
params: params:
- name: title - name: title
- name: user - name: user

View file

@ -1,5 +1,6 @@
template: opel template: opel
description: Opel description:
generic: Opel
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,7 @@
template: ovms template: ovms
description: Open Vehicle Monitoring System description:
generic: Open Vehicle Monitoring System
generic: true
params: params:
- name: title - name: title
- name: user - name: user

View file

@ -1,5 +1,6 @@
template: peugeot template: peugeot
description: Peugeot description:
generic: Peugeot
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: porsche template: porsche
description: Porsche description:
generic: Porsche
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: renault template: renault
description: Renault description:
generic: Renault
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

View file

@ -1,5 +1,6 @@
template: seat template: seat
description: Seat (Cupra, Mii) description:
generic: Seat (Cupra, Mii)
params: params:
- base: vehiclebase - base: vehiclebase
- base: vehicleidentify - base: vehicleidentify

Some files were not shown because too many files have changed in this diff Show more