From 4e119e8f6d9133b9f77794b9f4a7eeb0efaf8101 Mon Sep 17 00:00:00 2001 From: Andreas Linde <42185+DerAndereAndi@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:31:31 +0100 Subject: [PATCH] cli configure updates (#1966) - Add `--advanced` mode, where params defined as `advanced: true` will be asked - Add support for handling user input for lists, e.g. `identifiers` - Bool values are now a No/Yes selection instead of text input - Common definitions (params and render) are moved into single `parambaselist.yaml` file - Some refactoring --- charger/config_test.go | 1 + cmd/configure.go | 8 +- cmd/configure/configure.go | 1 - cmd/configure/flow.go | 6 +- cmd/configure/helper.go | 235 ++++++++++-------- cmd/configure/localization/de.toml | 5 +- cmd/configure/localization/en.toml | 5 +- cmd/configure/main.go | 5 +- cmd/configure/survey.go | 28 ++- cmd/configure/texts.go | 2 +- templates/README.md | 7 +- .../definition/charger/porsche-pmcc.yaml | 1 - .../definition/charger/porsche-pmcp.yaml | 1 - templates/definition/embed.go | 3 + .../definition/meter/sma-homemanager.yaml | 1 - templates/definition/parambaselist.yaml | 36 +++ templates/definition/vehicle/audi.yaml | 10 +- templates/definition/vehicle/bmw.yaml | 10 +- templates/definition/vehicle/carwings.yaml | 10 +- templates/definition/vehicle/citroen.yaml | 10 +- templates/definition/vehicle/ds.yaml | 10 +- templates/definition/vehicle/fiat.yaml | 10 +- templates/definition/vehicle/ford.yaml | 10 +- templates/definition/vehicle/hyundai.yaml | 7 +- templates/definition/vehicle/kia.yaml | 7 +- templates/definition/vehicle/mini.yaml | 10 +- templates/definition/vehicle/nissan.yaml | 7 +- templates/definition/vehicle/opel.yaml | 10 +- templates/definition/vehicle/peugeot.yaml | 10 +- templates/definition/vehicle/porsche.yaml | 13 +- templates/definition/vehicle/renault.yaml | 10 +- templates/definition/vehicle/seat.yaml | 12 +- templates/definition/vehicle/skoda.yaml | 12 +- templates/definition/vehicle/skodaenyaq.yaml | 12 +- templates/definition/vehicle/volvo.yaml | 12 +- templates/definition/vehicle/vw.yaml | 12 +- templates/definition/vehicle/vwid.yaml | 13 +- util/templates/proxy.tpl | 5 + util/templates/template.go | 113 ++++++--- 39 files changed, 325 insertions(+), 355 deletions(-) create mode 100644 templates/definition/parambaselist.yaml diff --git a/charger/config_test.go b/charger/config_test.go index c36423ee7..19fe8b426 100644 --- a/charger/config_test.go +++ b/charger/config_test.go @@ -23,6 +23,7 @@ var acceptable = []string{ "can only have either uri or device", // modbus "sponsorship required, see https://github.com/evcc-io/evcc#sponsorship", "eebus not configured", + "unexpected status: 400", // easee } func TestConfigChargers(t *testing.T) { diff --git a/cmd/configure.go b/cmd/configure.go index 3fbc4f1a1..0ea890acd 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -19,6 +19,7 @@ var configureCmd = &cobra.Command{ func init() { rootCmd.AddCommand(configureCmd) configureCmd.Flags().String("lang", "", "Define the localization to be used (en, de)") + configureCmd.Flags().Bool("advanced", false, "Enables handling of advanced configuration options") configureCmd.Flags().Bool("expand", false, "Enables rendering expanded configuration files") } @@ -30,6 +31,11 @@ func runConfigure(cmd *cobra.Command, args []string) { log.FATAL.Fatal(err) } + advanced, err := cmd.Flags().GetBool("advanced") + if err != nil { + panic(err) + } + expand, err := cmd.Flags().GetBool("expand") if err != nil { panic(err) @@ -37,5 +43,5 @@ func runConfigure(cmd *cobra.Command, args []string) { util.LogLevel(viper.GetString("log"), nil) - impl.Run(log, lang, expand) + impl.Run(log, lang, advanced, expand) } diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go index 2d1dbcd82..990ee5985 100644 --- a/cmd/configure/configure.go +++ b/cmd/configure/configure.go @@ -11,7 +11,6 @@ import ( type device struct { Name string Title string - LogLevel string Yaml string ChargerHasMeter bool // only used with chargers to detect if we need to ask for a charge meter } diff --git a/cmd/configure/flow.go b/cmd/configure/flow.go index ee1f693a6..0737d4f83 100644 --- a/cmd/configure/flow.go +++ b/cmd/configure/flow.go @@ -53,7 +53,7 @@ func (c *CmdConfigure) configureDeviceGuidedSetup() { // we only ask for the configuration for the first usage deviceCategory = supportedDeviceCategories[0] - values = c.processConfig(templateItem.Params, deviceCategory, false) + values = c.processConfig(templateItem.Params, deviceCategory) deviceItem, err = c.processDeviceValues(values, templateItem, deviceItem, deviceCategory) if err != nil { @@ -120,7 +120,7 @@ func (c *CmdConfigure) configureLinkedTypes(templateItem templates.Template) { break } - values := c.processConfig(linkedTemplateItem.Params, category, false) + values := c.processConfig(linkedTemplateItem.Params, category) deviceItem, err = c.processDeviceValues(values, linkedTemplateItem, deviceItem, category) if err != nil { if !errors.Is(err, c.errDeviceNotValid) { @@ -164,7 +164,7 @@ func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (d } deviceDescription = templateItem.Description - values := c.processConfig(templateItem.Params, deviceCategory, false) + values := c.processConfig(templateItem.Params, deviceCategory) device, err = c.processDeviceValues(values, templateItem, device, deviceCategory) if err != nil { diff --git a/cmd/configure/helper.go b/cmd/configure/helper.go index 90a973ae3..74eb6e30a 100644 --- a/cmd/configure/helper.go +++ b/cmd/configure/helper.go @@ -32,7 +32,6 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa device.Name = fmt.Sprintf("%s%d", DeviceCategories[deviceCategory].defaultName, c.addedDeviceIndex) device.Title = templateItem.Description - device.LogLevel = templateItem.LogLevel for item, value := range values { if strings.ToLower(item) != "title" { continue @@ -233,120 +232,158 @@ func (c *CmdConfigure) paramChoiceValues(params []templates.Param, name string) // processConfig processes an EVCC configuration item // Returns: // a map with param name and values -func (c *CmdConfigure) processConfig(paramItems []templates.Param, deviceCategory DeviceCategory, includeAdvanced bool) map[string]interface{} { +func (c *CmdConfigure) processConfig(paramItems []templates.Param, deviceCategory DeviceCategory) map[string]interface{} { usageFilter := DeviceCategories[deviceCategory].categoryFilter additionalConfig := make(map[string]interface{}) - selectedModbusKey := "" fmt.Println() fmt.Println(c.localizedString("Config_Title", nil)) fmt.Println() for _, param := range paramItems { - if param.Name == templates.ParamModbus { - // baudrate and comset defaults can be overwritten, as they are device specific - deviceDefaultBaudrate := templates.ModbusParamValueBaudrate - if param.Baudrate != 0 { - deviceDefaultBaudrate = param.Baudrate + switch param.Name { + case templates.ParamModbus: + c.processModbusConfig(param, deviceCategory, additionalConfig) + case templates.ParamUsage: + if usageFilter != "" { + additionalConfig[param.Name] = usageFilter.String() } - deviceDefaultComset := templates.ModbusParamValueComset - if param.Comset != "" { - deviceDefaultBaudrate = param.Baudrate - } - - choices := []string{} - choiceKeys := []string{} - for _, choice := range param.Choice { - switch choice { - case templates.ModbusChoiceRS485: - choices = append(choices, "Serial (USB-RS485 Adapter)") - choiceKeys = append(choiceKeys, templates.ModbusKeyRS485Serial) - choices = append(choices, "Serial (Ethernet-RS485 Adapter)") - choiceKeys = append(choiceKeys, templates.ModbusKeyRS485TCPIP) - case templates.ModbusChoiceTCPIP: - choices = append(choices, "TCP/IP") - choiceKeys = append(choiceKeys, templates.ModbusKeyTCPIP) - } - } - - if len(choices) > 0 { - // ask for modbus address - id := c.askValue(question{ - label: "ID", - help: "Modbus ID", - defaultValue: 1, - valueType: templates.ParamValueTypeNumber, - required: true}) - additionalConfig[templates.ModbusParamNameId] = id - - // ask for modbus interface type - index := 0 - if len(choices) > 1 { - index, _ = c.askChoice(c.localizedString("Config_ModbusInterface", nil), choices) - } - selectedModbusKey = choiceKeys[index] - switch selectedModbusKey { - case templates.ModbusKeyRS485Serial: - device := c.askValue(question{ - label: "Device", - help: "USB-RS485 Adapter Adresse", - exampleValue: templates.ModbusParamValueDevice, - required: true}) - additionalConfig[templates.ModbusParamNameDevice] = device - - baudrate := c.askValue(question{ - label: "Baudrate", - defaultValue: deviceDefaultBaudrate, - valueType: templates.ParamValueTypeNumber, - required: true}) - additionalConfig[templates.ModbusParamNameBaudrate] = baudrate - - comset := c.askValue(question{ - label: "ComSet", - defaultValue: deviceDefaultComset, - required: true}) - additionalConfig[templates.ModbusParamNameComset] = comset - - case templates.ModbusKeyRS485TCPIP, templates.ModbusKeyTCPIP: - if selectedModbusKey == templates.ModbusKeyRS485TCPIP { - additionalConfig[templates.ModbusParamNameRTU] = "true" - } - host := c.askValue(question{ - label: "Host", - exampleValue: templates.ModbusParamValueHost, - required: true}) - additionalConfig[templates.ModbusParamNameHost] = host - - port := c.askValue(question{ - label: "Port", - defaultValue: templates.ModbusParamValuePort, - valueType: templates.ParamValueTypeNumber, - required: true}) - additionalConfig[templates.ModbusParamNamePort] = port - } - } - } else if param.Name != templates.ParamUsage { - if !includeAdvanced && param.Advanced { + default: + if !c.advancedMode && param.Advanced { continue } - userFriendly := c.userFriendlyTexts(param) - value := c.askValue(question{ - label: userFriendly.Name, - defaultValue: userFriendly.Default, - exampleValue: userFriendly.Example, - help: userFriendly.Help.String(c.lang), - valueType: userFriendly.ValueType, - mask: userFriendly.Mask, - required: userFriendly.Required}) - additionalConfig[param.Name] = value - } else if param.Name == templates.ParamUsage { - if usageFilter != "" { - additionalConfig[param.Name] = usageFilter.String() + switch param.ValueType { + case templates.ParamValueTypeStringList: + additionalConfig[param.Name] = c.processListInputConfig(param) + default: + additionalConfig[param.Name] = c.processInputConfig(param) } } } return additionalConfig } + +// handle user input of multiple items in a list +func (c *CmdConfigure) processListInputConfig(param templates.Param) []string { + var values []string + + // ask for values until the decides stops + for ok := true; ok; { + newValue := c.processInputConfig(param) + values = append(values, newValue) + + if newValue == "" { + break + } + + if !c.askYesNo(" " + c.localizedString("Config_AddAnotherValue", nil)) { + break + } + } + + return values +} + +// handle user input for a simple one value input +func (c *CmdConfigure) processInputConfig(param templates.Param) string { + userFriendly := c.userFriendlyTexts(param) + return c.askValue(question{ + label: userFriendly.Name, + defaultValue: userFriendly.Default, + exampleValue: userFriendly.Example, + help: userFriendly.Help.String(c.lang), + valueType: userFriendly.ValueType, + mask: userFriendly.Mask, + required: userFriendly.Required}) +} + +// handle user input for a device modbus configuration +func (c *CmdConfigure) processModbusConfig(param templates.Param, deviceCategory DeviceCategory, additionalConfig map[string]interface{}) { + var selectedModbusKey string + + // baudrate and comset defaults can be overwritten, as they are device specific + deviceDefaultBaudrate := templates.ModbusParamValueBaudrate + if param.Baudrate != 0 { + deviceDefaultBaudrate = param.Baudrate + } + deviceDefaultComset := templates.ModbusParamValueComset + if param.Comset != "" { + deviceDefaultBaudrate = param.Baudrate + } + + var choices []string + var choiceKeys []string + + for _, choice := range param.Choice { + switch choice { + case templates.ModbusChoiceRS485: + choices = append(choices, "Serial (USB-RS485 Adapter)") + choiceKeys = append(choiceKeys, templates.ModbusKeyRS485Serial) + choices = append(choices, "Serial (Ethernet-RS485 Adapter)") + choiceKeys = append(choiceKeys, templates.ModbusKeyRS485TCPIP) + case templates.ModbusChoiceTCPIP: + choices = append(choices, "TCP/IP") + choiceKeys = append(choiceKeys, templates.ModbusKeyTCPIP) + } + } + + if len(choices) > 0 { + // ask for modbus address + id := c.askValue(question{ + label: "ID", + help: "Modbus ID", + defaultValue: 1, + valueType: templates.ParamValueTypeNumber, + required: true}) + additionalConfig[templates.ModbusParamNameId] = id + + // ask for modbus interface type + var index int + if len(choices) > 1 { + index, _ = c.askChoice(c.localizedString("Config_ModbusInterface", nil), choices) + } + selectedModbusKey = choiceKeys[index] + switch selectedModbusKey { + case templates.ModbusKeyRS485Serial: + device := c.askValue(question{ + label: "Device", + help: "USB-RS485 Adapter Adresse", + exampleValue: templates.ModbusParamValueDevice, + required: true}) + additionalConfig[templates.ModbusParamNameDevice] = device + + baudrate := c.askValue(question{ + label: "Baudrate", + defaultValue: deviceDefaultBaudrate, + valueType: templates.ParamValueTypeNumber, + required: true}) + additionalConfig[templates.ModbusParamNameBaudrate] = baudrate + + comset := c.askValue(question{ + label: "ComSet", + defaultValue: deviceDefaultComset, + required: true}) + additionalConfig[templates.ModbusParamNameComset] = comset + + case templates.ModbusKeyRS485TCPIP, templates.ModbusKeyTCPIP: + if selectedModbusKey == templates.ModbusKeyRS485TCPIP { + additionalConfig[templates.ModbusParamNameRTU] = "true" + } + host := c.askValue(question{ + label: "Host", + exampleValue: templates.ModbusParamValueHost, + required: true}) + additionalConfig[templates.ModbusParamNameHost] = host + + port := c.askValue(question{ + label: "Port", + defaultValue: templates.ModbusParamValuePort, + valueType: templates.ParamValueTypeNumber, + required: true}) + additionalConfig[templates.ModbusParamNamePort] = port + } + } +} diff --git a/cmd/configure/localization/de.toml b/cmd/configure/localization/de.toml index 1724fa40c..6ad6d1e81 100644 --- a/cmd/configure/localization/de.toml +++ b/cmd/configure/localization/de.toml @@ -75,6 +75,9 @@ Requirements_EEBUS_Pairing = "Du hast eine Wallbox ausgewählt, welche über das Config_Title = "Führe folgende Einstellungen durch:" Config_ModbusInterface = "Wähle die ModBus Schnittstelle aus" +Config_AddAnotherValue = "Möchtest du einen weiteren Wert hinzufügen?" +Config_Yes = "Ja" +Config_No = "Nein" Cancel = "Die Konfiguration wurde abgebrochen.\n\nFalls diese geführte Konfiguration für dich noch nicht funktioniert, versuche es doch mal mit der manuellen Konfiguration. Details findest du auf der folgenden Webseite: https://docs.evcc.io/docs/installation/configuration\n" InputError = "Bei der Eingabe ist ein Fehler aufgetreten:" @@ -82,10 +85,8 @@ InputError = "Bei der Eingabe ist ein Fehler aufgetreten:" Value_Required = "erforderlich" Value_Optional = "optional" Value_Sample = "Beispiel" -Value_Bool = "'true' für ja oder 'false' für nein" ValueError_Used = "Dieser Wert wird bereits verwendet." ValueError_Empty = "Der Wert darf nicht leer sein." -ValueError_Bool = "Der Wert muss 'true' (für ja) oder 'false' (für nein) sein." ValueError_Float = "Der Wert muss eine Zahl sein. Nachkommastellen mit . anstatt mit , trennen." ValueError_Number = "Der Wert muss eine ganzzahlige Zahl sein." diff --git a/cmd/configure/localization/en.toml b/cmd/configure/localization/en.toml index a610f3471..00bd70917 100644 --- a/cmd/configure/localization/en.toml +++ b/cmd/configure/localization/en.toml @@ -75,6 +75,9 @@ Requirements_EEBUS_Pairing = "You selected a wallbox, which will be access via t Config_Title = "Please provide the following settings:" Config_ModbusInterface = "Choose the ModBus interface" +Config_AddAnotherValue = "Do you want to add another value?" +Config_Yes = "Yes" +Config_No = "No" Cancel = "The configuration was cancelled.\n\nIf this guided configuration process doesn't work for you yet, please try the manual configuration. You can find more details about that on our website: https://docs.evcc.io/docs/installation/configuration\n" InputError = "An input error occurred:" @@ -82,10 +85,8 @@ InputError = "An input error occurred:" Value_Required = "required" Value_Optional = "optional" Value_Sample = "Example" -Value_Bool = "'true' for yes or 'false' for no" ValueError_Used = "This value is aready in use." ValueError_Empty = "The value may not be empty." -ValueError_Bool = "The value has to be 'true' (for yes) or 'false' (for no)." ValueError_Float = "The value has to be a number." ValueError_Number = "The value has to be an integer." diff --git a/cmd/configure/main.go b/cmd/configure/main.go index d0e60aa7f..fb74cfec1 100644 --- a/cmd/configure/main.go +++ b/cmd/configure/main.go @@ -29,14 +29,15 @@ type CmdConfigure struct { log *util.Logger lang string - expandedMode bool + advancedMode, expandedMode bool addedDeviceIndex int errItemNotPresent, errDeviceNotValid error } // Run starts the interactive configuration -func (c *CmdConfigure) Run(log *util.Logger, flagLang string, expandedMode bool) { +func (c *CmdConfigure) Run(log *util.Logger, flagLang string, advancedMode, expandedMode bool) { c.log = log + c.advancedMode = advancedMode c.expandedMode = expandedMode c.log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit) diff --git a/cmd/configure/survey.go b/cmd/configure/survey.go index e8c1e4edc..941dd420b 100644 --- a/cmd/configure/survey.go +++ b/cmd/configure/survey.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "strconv" - "strings" "github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2/terminal" @@ -118,8 +117,26 @@ type question struct { mask, required bool } +// askBoolValue asks for a boolean value selection for a given question +func (c *CmdConfigure) askBoolValue(label string) string { + choices := []string{c.localizedString("Config_No", nil), c.localizedString("Config_Yes", nil)} + values := []string{"false", "true"} + + index, _ := c.askChoice(label, choices) + return values[index] +} + // askValue asks for value input for a given question (template param) func (c *CmdConfigure) askValue(q question) string { + if q.valueType == templates.ParamValueTypeBool { + label := q.label + if q.help != "" { + label = q.help + } + + return c.askBoolValue(label) + } + input := "" var err error @@ -134,12 +151,6 @@ func (c *CmdConfigure) askValue(q question) string { return errors.New(c.localizedString("ValueError_Empty", nil)) } - if q.valueType == templates.ParamValueTypeBool { - if strings.ToLower(value) != "true" && strings.ToLower(value) != "false" { - return errors.New(c.localizedString("ValueError_Bool", nil)) - } - } - if q.valueType == templates.ParamValueTypeFloat { _, err := strconv.ParseFloat(value, 64) if err != nil { @@ -166,9 +177,6 @@ func (c *CmdConfigure) askValue(q question) string { if q.exampleValue != "" { help += fmt.Sprintf(" ("+c.localizedString("Value_Sample", nil)+": %s)", q.exampleValue) } - if q.valueType == templates.ParamValueTypeBool { - help += " (" + c.localizedString("Value_Bool", nil) + ")" - } if q.mask { prompt := &survey.Password{ diff --git a/cmd/configure/texts.go b/cmd/configure/texts.go index 9d4188d8f..ba373fa49 100644 --- a/cmd/configure/texts.go +++ b/cmd/configure/texts.go @@ -81,7 +81,7 @@ func (c *CmdConfigure) userFriendlyTexts(param templates.Param) templates.Param if result.Help.String(c.lang) == "" { result.Help.SetString(c.lang, c.localizedString("UserFriendly_Vin_Help", nil)) } - case "identifier": + case "identifiers": result.Name = c.localizedString("UserFriendly_Identifier_Name", nil) if result.Help.String(c.lang) == "" { result.Help.SetString(c.lang, c.localizedString("UserFriendly_Identifier_Help", nil)) diff --git a/templates/README.md b/templates/README.md index 5191eddba..6f9044ceb 100644 --- a/templates/README.md +++ b/templates/README.md @@ -112,9 +112,14 @@ Allows to define a list of meter devices that are typically installed with this **Possible values**: - `string`: for string values (default) -- `bool`: for `true` and `false` values +- `bool`: for `true` and `false` values. If `help` is provided, than that help text is presented as the question - `number`: for int values - `float`: for float values +- `stringlist`: for a list of strings, e.g.used for defining a list of `identifiers` for `vehicles` + +### `advanced` + +`advanced` allows to specify if the param should only be asked if the cli is run with `--advanced`. Mostly used for non required params that are meant for users with advanced needs and knowledge. ### `help` diff --git a/templates/definition/charger/porsche-pmcc.yaml b/templates/definition/charger/porsche-pmcc.yaml index 2bde1123a..a48454227 100644 --- a/templates/definition/charger/porsche-pmcc.yaml +++ b/templates/definition/charger/porsche-pmcc.yaml @@ -1,6 +1,5 @@ template: pmcc description: Porsche Mobile Charger Connect -loglevel: eebus requirements: eebus: true params: diff --git a/templates/definition/charger/porsche-pmcp.yaml b/templates/definition/charger/porsche-pmcp.yaml index e98d570c4..3ea1482d0 100644 --- a/templates/definition/charger/porsche-pmcp.yaml +++ b/templates/definition/charger/porsche-pmcp.yaml @@ -1,6 +1,5 @@ template: pmcp description: Porsche Mobile Charger Plus -loglevel: eebus requirements: eebus: true params: diff --git a/templates/definition/embed.go b/templates/definition/embed.go index 2208d5b0b..248da7d75 100644 --- a/templates/definition/embed.go +++ b/templates/definition/embed.go @@ -5,4 +5,7 @@ import "embed" var ( //go:embed charger/*.yaml meter/*.yaml vehicle/*.yaml YamlTemplates embed.FS + + //go:embed parambaselist.yaml + ParamBaseListDefinition string ) diff --git a/templates/definition/meter/sma-homemanager.yaml b/templates/definition/meter/sma-homemanager.yaml index 62d245157..a3559627e 100644 --- a/templates/definition/meter/sma-homemanager.yaml +++ b/templates/definition/meter/sma-homemanager.yaml @@ -2,7 +2,6 @@ template: sma-home-manager description: SMA Sunny Home Manager 2.0 requirements: hems: sma -loglevel: sma guidedsetup: enable: true linked: diff --git a/templates/definition/parambaselist.yaml b/templates/definition/parambaselist.yaml new file mode 100644 index 000000000..7f8ab8546 --- /dev/null +++ b/templates/definition/parambaselist.yaml @@ -0,0 +1,36 @@ +vehicle: + params: + - name: title + - name: user + required: true + - name: password + required: true + mask: true + - name: vin + example: W... + - name: capacity + default: '50' + valueType: float + - name: identifiers + advanced: true + valueType: stringlist + render: | + {{define "vehicle-common"}} + {{- if ne .title "" }} + title: {{ .title }} + {{- end }} + user: {{ .user }} + password: {{ .password }} + {{- if ne .capacity "" }} + capacity: {{ .capacity }} + {{- end }} + {{- if ne .vin "" }} + vin: {{ .vin }} + {{- end }} + {{- if ne (len .identifiers) 0 }} + identifiers: + {{- range .identifiers }} + - {{ . }} + {{- end }} + {{- end }} + {{end}} diff --git a/templates/definition/vehicle/audi.yaml b/templates/definition/vehicle/audi.yaml index f13197973..3853d27fa 100644 --- a/templates/definition/vehicle/audi.yaml +++ b/templates/definition/vehicle/audi.yaml @@ -8,12 +8,4 @@ params: default: 60 render: | type: audi - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/bmw.yaml b/templates/definition/vehicle/bmw.yaml index e08188a57..d269abf25 100644 --- a/templates/definition/vehicle/bmw.yaml +++ b/templates/definition/vehicle/bmw.yaml @@ -8,12 +8,4 @@ params: default: 65 render: | type: bmw - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/carwings.yaml b/templates/definition/vehicle/carwings.yaml index 9fe51fd29..364b5d884 100644 --- a/templates/definition/vehicle/carwings.yaml +++ b/templates/definition/vehicle/carwings.yaml @@ -6,12 +6,4 @@ params: default: 50 render: | type: carwings - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/citroen.yaml b/templates/definition/vehicle/citroen.yaml index 9d588d14e..c2de10842 100644 --- a/templates/definition/vehicle/citroen.yaml +++ b/templates/definition/vehicle/citroen.yaml @@ -6,12 +6,4 @@ params: default: 50 render: | type: citroen - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/ds.yaml b/templates/definition/vehicle/ds.yaml index 5349df95b..30896b7b7 100644 --- a/templates/definition/vehicle/ds.yaml +++ b/templates/definition/vehicle/ds.yaml @@ -6,12 +6,4 @@ params: default: 50 render: | type: ds - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/fiat.yaml b/templates/definition/vehicle/fiat.yaml index 2cc023763..cc2f986a4 100644 --- a/templates/definition/vehicle/fiat.yaml +++ b/templates/definition/vehicle/fiat.yaml @@ -9,15 +9,7 @@ params: default: 42 render: | type: fiat - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} {{- if ne .pin "" }} pin: {{ .pin }} #mandatory to deep refresh SoC {{- end }} diff --git a/templates/definition/vehicle/ford.yaml b/templates/definition/vehicle/ford.yaml index 95e3164ce..b4893f2aa 100644 --- a/templates/definition/vehicle/ford.yaml +++ b/templates/definition/vehicle/ford.yaml @@ -8,12 +8,4 @@ params: default: 10 render: | type: ford - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/hyundai.yaml b/templates/definition/vehicle/hyundai.yaml index b6262dcd4..418fc41a5 100644 --- a/templates/definition/vehicle/hyundai.yaml +++ b/templates/definition/vehicle/hyundai.yaml @@ -6,9 +6,4 @@ params: default: 64 render: | type: hyundai - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/kia.yaml b/templates/definition/vehicle/kia.yaml index 907cb0366..72d4d5925 100644 --- a/templates/definition/vehicle/kia.yaml +++ b/templates/definition/vehicle/kia.yaml @@ -6,9 +6,4 @@ params: default: 64 render: | type: kia - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/mini.yaml b/templates/definition/vehicle/mini.yaml index f499607fe..51d69055e 100644 --- a/templates/definition/vehicle/mini.yaml +++ b/templates/definition/vehicle/mini.yaml @@ -8,12 +8,4 @@ params: default: 32 render: | type: mini - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/nissan.yaml b/templates/definition/vehicle/nissan.yaml index 59dcc0d92..9d88d8c7a 100644 --- a/templates/definition/vehicle/nissan.yaml +++ b/templates/definition/vehicle/nissan.yaml @@ -6,9 +6,4 @@ params: default: 60 render: | type: nissan - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/opel.yaml b/templates/definition/vehicle/opel.yaml index be56a138b..8deb392b8 100644 --- a/templates/definition/vehicle/opel.yaml +++ b/templates/definition/vehicle/opel.yaml @@ -8,12 +8,4 @@ params: default: 50 render: | type: opel - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/peugeot.yaml b/templates/definition/vehicle/peugeot.yaml index 30d360dc1..06e3fd01b 100644 --- a/templates/definition/vehicle/peugeot.yaml +++ b/templates/definition/vehicle/peugeot.yaml @@ -6,12 +6,4 @@ params: default: 50 render: | type: peugeot - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/porsche.yaml b/templates/definition/vehicle/porsche.yaml index 6663dd434..52b43957b 100644 --- a/templates/definition/vehicle/porsche.yaml +++ b/templates/definition/vehicle/porsche.yaml @@ -1,20 +1,9 @@ template: porsche description: Porsche -loglevel: porsche paramsbase: vehicle params: - name: capacity default: 83.4 render: | type: porsche - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if .capacity }} - capacity: {{ .capacity }} - {{- end }} - {{- if .vin }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/renault.yaml b/templates/definition/vehicle/renault.yaml index 84c650606..f76df27cf 100644 --- a/templates/definition/vehicle/renault.yaml +++ b/templates/definition/vehicle/renault.yaml @@ -8,12 +8,4 @@ params: default: 60 render: | type: renault - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - capacity: {{ .capacity }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/seat.yaml b/templates/definition/vehicle/seat.yaml index 952a121c9..aa62d826f 100644 --- a/templates/definition/vehicle/seat.yaml +++ b/templates/definition/vehicle/seat.yaml @@ -6,14 +6,4 @@ params: default: 10 render: | type: seat - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if ne .capacity "" }} - capacity: {{ .capacity }} - {{- end }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/skoda.yaml b/templates/definition/vehicle/skoda.yaml index 2ad703588..901db02eb 100644 --- a/templates/definition/vehicle/skoda.yaml +++ b/templates/definition/vehicle/skoda.yaml @@ -6,14 +6,4 @@ params: default: 10 render: | type: skoda - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if ne .capacity "" }} - capacity: {{ .capacity }} - {{- end }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/skodaenyaq.yaml b/templates/definition/vehicle/skodaenyaq.yaml index 00078c154..ed547bf33 100644 --- a/templates/definition/vehicle/skodaenyaq.yaml +++ b/templates/definition/vehicle/skodaenyaq.yaml @@ -6,14 +6,4 @@ params: default: 50 render: | type: enyaq - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if ne .capacity "" }} - capacity: {{ .capacity }} - {{- end }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/volvo.yaml b/templates/definition/vehicle/volvo.yaml index ebdd2b241..b76bad993 100644 --- a/templates/definition/vehicle/volvo.yaml +++ b/templates/definition/vehicle/volvo.yaml @@ -6,14 +6,4 @@ params: default: 50 render: | type: volvo - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if ne .capacity "" }} - capacity: {{ .capacity }} - {{- end }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/vw.yaml b/templates/definition/vehicle/vw.yaml index 0343bc741..d10093351 100644 --- a/templates/definition/vehicle/vw.yaml +++ b/templates/definition/vehicle/vw.yaml @@ -8,14 +8,4 @@ params: default: 10 render: | type: vw - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if ne .capacity "" }} - capacity: {{ .capacity }} - {{- end }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/templates/definition/vehicle/vwid.yaml b/templates/definition/vehicle/vwid.yaml index ec421e5fb..84ac0f35e 100644 --- a/templates/definition/vehicle/vwid.yaml +++ b/templates/definition/vehicle/vwid.yaml @@ -1,6 +1,5 @@ template: id description: Volkswagen (We Connect ID) -loglevel: id paramsbase: vehicle params: - name: vin @@ -9,14 +8,4 @@ params: default: 50 render: | type: id - {{- if ne .title "" }} - title: {{ .title }} - {{- end }} - user: {{ .user }} - password: {{ .password }} - {{- if ne .capacity "" }} - capacity: {{ .capacity }} - {{- end }} - {{- if ne .vin "" }} - vin: {{ .vin }} - {{- end }} + {{include "vehicle-common" .}} diff --git a/util/templates/proxy.tpl b/util/templates/proxy.tpl index 7f4677dc7..62ddc1227 100644 --- a/util/templates/proxy.tpl +++ b/util/templates/proxy.tpl @@ -7,4 +7,9 @@ description: {{ .Description }} {{ .Name }}: {{- if len .Value }} {{ .Value }} {{ end }} {{- if .Help.DE }} # {{ .Help.DE }} {{- end }} +{{- if ne (len .Values) 0 }} +{{- range .Values }} +- {{ . }} +{{- end }} +{{- end }} {{ end -}} diff --git a/util/templates/template.go b/util/templates/template.go index 429c749ef..b1b7673c4 100644 --- a/util/templates/template.go +++ b/util/templates/template.go @@ -4,10 +4,13 @@ import ( "bytes" _ "embed" "fmt" + "strings" "text/template" "github.com/Masterminds/sprig/v3" + "github.com/evcc-io/evcc/templates/definition" "github.com/evcc-io/evcc/util" + "gopkg.in/yaml.v3" ) const ( @@ -45,10 +48,11 @@ const ( var HemsValueTypes = []string{HemsTypeSMA} const ( - ParamValueTypeString = "string" - ParamValueTypeNumber = "number" - ParamValueTypeFloat = "float" - ParamValueTypeBool = "bool" + ParamValueTypeString = "string" + ParamValueTypeNumber = "number" + ParamValueTypeFloat = "float" + ParamValueTypeBool = "bool" + ParamValueTypeStringList = "stringlist" ) var ParamValueTypes = []string{ParamValueTypeString, ParamValueTypeNumber, ParamValueTypeBool} @@ -111,6 +115,7 @@ type Param struct { Help TextLanguage // cli configuration help Test string // testing default value Value string // user provided value via cli configuration + Values []string // user provided list of values ValueType string // string representation of the value type, "string" is default Choice []string // defines which usage choices this config supports, valid elemtents are "grid", "pv", "battery", "charge" Usages []string @@ -118,11 +123,17 @@ type Param struct { Comset string // device specific default for modbus RS485 comset } +type ParamBase struct { + Params []Param + Render string +} + +var paramBaseList map[string]ParamBase + // Template describes is a proxy device for use with cli and automated testing type Template struct { Template string Description string // user friendly description of the device this template describes - LogLevel string // the implementation type of the device, equal to the type value under "Render" Requirements Requirements GuidedSetup GuidedSetup Generic bool // if this describes a generic device type rather than a product @@ -131,31 +142,30 @@ type Template struct { Render string // rendering template } -var paramBases = map[string][]Param{ - "vehicle": { - {Name: "title"}, - {Name: "user", Required: true}, - {Name: "password", Required: true, Mask: true}, - {Name: "vin", Example: "W..."}, - {Name: "capacity", Default: "50", ValueType: ParamValueTypeFloat}, - }, -} - // add the referenced base Params and overwrite existing ones func (t *Template) ResolveParamBase() { if t.ParamsBase == "" { return } - base, ok := paramBases[t.ParamsBase] + if paramBaseList == nil { + err := yaml.Unmarshal([]byte(definition.ParamBaseListDefinition), ¶mBaseList) + if err != nil { + fmt.Printf("Error: failed to parse paramBasesDefinition: %v\n", err) + return + } + } + + base, ok := paramBaseList[t.ParamsBase] if !ok { + fmt.Printf("Error: Could not find parambase definition: %s\n", t.ParamsBase) return } currentParams := make([]Param, len(t.Params)) copy(currentParams, t.Params) - t.Params = make([]Param, len(base)) - copy(t.Params, base) + t.Params = make([]Param, len(base.Params)) + copy(t.Params, base.Params) for _, p := range currentParams { if i, item := t.paramWithName(p.Name); item != nil { // we only allow overwriting a few fields @@ -175,12 +185,17 @@ func (t *Template) ResolveParamBase() { func (t *Template) Defaults(docsOrTests bool) map[string]interface{} { values := make(map[string]interface{}) for _, p := range t.Params { - if p.Test != "" { - values[p.Name] = p.Test - } else if p.Example != "" && docsOrTests { - values[p.Name] = p.Example - } else { - values[p.Name] = p.Default // may be empty + switch p.ValueType { + case ParamValueTypeStringList: + values[p.Name] = []string{} + default: + if p.Test != "" { + values[p.Name] = p.Test + } else if p.Example != "" && docsOrTests { + values[p.Name] = p.Example + } else { + values[p.Name] = p.Default // may be empty + } } } @@ -230,25 +245,39 @@ func (t *Template) RenderProxyWithValues(values map[string]interface{}, includeD for index, p := range t.Params { for k, v := range values { - if p.Name == k { - t.Params[index].Value = v.(string) + if p.Name != k { + continue + } + + switch p.ValueType { + case ParamValueTypeStringList: + for _, e := range v.([]string) { + t.Params[index].Values = append(p.Values, yamlQuote(e)) + } + default: + t.Params[index].Value = yamlQuote(v.(string)) } } } - // remove params with no values, no defaults and no example + // remove params with no values var newParams []Param for _, param := range t.Params { - if param.Value == "" && param.Default == "" && param.Example == "" && !param.Required { - continue + if !param.Required { + switch param.ValueType { + case ParamValueTypeStringList: + if len(param.Values) == 0 { + continue + } + default: + if param.Value == "" { + continue + } + } } newParams = append(newParams, param) } - for index, p := range newParams { - newParams[index].Value = yamlQuote(p.Value) - } - t.Params = newParams out := new(bytes.Buffer) @@ -273,8 +302,24 @@ func (t *Template) RenderResult(docs bool, other map[string]interface{}) ([]byte t.ModbusValues(values) + // add the common templates + for _, v := range paramBaseList { + if !strings.Contains(t.Render, v.Render) { + t.Render = fmt.Sprintf("%s\n%s", t.Render, v.Render) + } + } + for item, p := range values { - values[item] = yamlQuote(fmt.Sprintf("%v", p)) + switch p := p.(type) { + case []string: + var list []string + for _, v := range p { + list = append(list, yamlQuote(v)) + } + values[item] = list + default: + values[item] = yamlQuote(fmt.Sprintf("%v", p)) + } } tmpl := template.New("yaml")