From bd7a8ea6dddbf604075b9cafc62aae2b40582204 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 12 Jan 2026 09:27:19 +0100 Subject: [PATCH] cmd/cli: remove configure (BC) (#26633) --- cmd/configure.go | 64 ---- cmd/configure/configure.go | 128 -------- cmd/configure/configure.tpl | 87 ----- cmd/configure/devicetest.go | 159 --------- cmd/configure/eebus.go | 53 --- cmd/configure/flow.go | 232 ------------- cmd/configure/helper.go | 502 ----------------------------- cmd/configure/localization/de.toml | 113 ------- cmd/configure/localization/en.toml | 113 ------- cmd/configure/main.go | 448 ------------------------- cmd/configure/survey.go | 261 --------------- cmd/configure/texts.go | 42 --- cmd/configure/types.go | 79 ----- go.mod | 3 +- go.sum | 2 - 15 files changed, 1 insertion(+), 2285 deletions(-) delete mode 100644 cmd/configure.go delete mode 100644 cmd/configure/configure.go delete mode 100644 cmd/configure/configure.tpl delete mode 100644 cmd/configure/devicetest.go delete mode 100644 cmd/configure/eebus.go delete mode 100644 cmd/configure/flow.go delete mode 100644 cmd/configure/helper.go delete mode 100644 cmd/configure/localization/de.toml delete mode 100644 cmd/configure/localization/en.toml delete mode 100644 cmd/configure/main.go delete mode 100644 cmd/configure/survey.go delete mode 100644 cmd/configure/texts.go delete mode 100644 cmd/configure/types.go diff --git a/cmd/configure.go b/cmd/configure.go deleted file mode 100644 index 34811a61c..000000000 --- a/cmd/configure.go +++ /dev/null @@ -1,64 +0,0 @@ -package cmd - -import ( - "os" - "os/signal" - "syscall" - - "github.com/evcc-io/evcc/cmd/configure" - "github.com/evcc-io/evcc/util" - "github.com/spf13/cobra" -) - -// configureCmd represents the configure command -var configureCmd = &cobra.Command{ - Use: "configure", - Short: "Create configuration (evcc.yaml)", - Run: runConfigure, -} - -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") - configureCmd.Flags().String("category", "", "Pre-select device category for advanced configuration (implies advanced)") -} - -func runConfigure(cmd *cobra.Command, args []string) { - impl := &configure.CmdConfigure{} - - lang, err := cmd.Flags().GetString("lang") - if err != nil { - 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) - } - - category, err := cmd.Flags().GetString("category") - if err != nil { - panic(err) - } - - util.LogLevel(viper.GetString("log"), nil) - - // catch signals - go func() { - signalC := make(chan os.Signal, 1) - signal.Notify(signalC, os.Interrupt, syscall.SIGTERM) - - <-signalC // wait for signal - - os.Exit(1) - }() - - impl.Run(log, lang, advanced, expand, category) -} diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go deleted file mode 100644 index f0ffdb87d..000000000 --- a/cmd/configure/configure.go +++ /dev/null @@ -1,128 +0,0 @@ -package configure - -import ( - "bytes" - _ "embed" - "text/template" - - "github.com/Masterminds/sprig/v3" - "github.com/evcc-io/evcc/util/templates" -) - -type device struct { - Name string - Title string - Yaml string - ChargerHasMeter bool // only used with chargers to detect if we need to ask for a charge meter -} - -type loadpoint struct { - Title string // TODO Perspektivisch können wir was aus core wiederverwenden, für später - Charger string - ChargeMeter string - Vehicle string - Mode string - MinCurrent int - MaxCurrent int - Phases int -} - -type globalConfig struct { - Meters []device - Chargers []device - Vehicles []device - Loadpoints []loadpoint - Site struct { // TODO Perspektivisch können wir was aus core wiederverwenden, für später - Title string - Grid string - PVs []string - Batteries []string - } - Hems string - EEBUS string - MQTT string - SponsorToken string - Plant string - Telemetry bool -} - -type Configure struct { - config globalConfig -} - -// AddDevice adds a device reference of a specific category to the configuration -// e.g. a PV meter to site.PVs -func (c *Configure) AddDevice(d device, category DeviceCategory) { - switch DeviceCategories[category].class { - case templates.Charger: - c.config.Chargers = append(c.config.Chargers, d) - - case templates.Meter: - c.config.Meters = append(c.config.Meters, d) - switch DeviceCategories[category].categoryFilter { - case DeviceCategoryGridMeter: - c.config.Site.Grid = d.Name - case DeviceCategoryPVMeter: - c.config.Site.PVs = append(c.config.Site.PVs, d.Name) - case DeviceCategoryBatteryMeter: - c.config.Site.Batteries = append(c.config.Site.Batteries, d.Name) - } - - case templates.Vehicle: - c.config.Vehicles = append(c.config.Vehicles, d) - - default: - panic("invalid class for category: " + category) - } -} - -// DevicesOfClass returns all configured devices of a given DeviceClass -func (c *Configure) DevicesOfClass(class templates.Class) []device { - switch class { - case templates.Charger: - return c.config.Chargers - case templates.Meter: - return c.config.Meters - case templates.Vehicle: - return c.config.Vehicles - default: - panic("invalid class: " + class.String()) - } -} - -// AddLoadpoint adds a loadpoint to the configuration -func (c *Configure) AddLoadpoint(l loadpoint) { - c.config.Loadpoints = append(c.config.Loadpoints, l) -} - -// MetersOfCategory returns the number of configured meters of a given DeviceCategory -func (c *Configure) MetersOfCategory(category DeviceCategory) int { - switch category { - case DeviceCategoryGridMeter: - if c.config.Site.Grid != "" { - return 1 - } - case DeviceCategoryPVMeter: - return len(c.config.Site.PVs) - case DeviceCategoryBatteryMeter: - return len(c.config.Site.Batteries) - } - - return 0 -} - -//go:embed configure.tpl -var configTmpl string - -// RenderConfiguration creates a yaml configuration -func (c *Configure) RenderConfiguration() ([]byte, error) { - tmpl, err := template.New("yaml").Funcs(sprig.FuncMap()).Parse(configTmpl) - if err != nil { - panic(err) - } - - out := new(bytes.Buffer) - err = tmpl.Execute(out, c.config) - - return bytes.TrimSpace(out.Bytes()), err -} diff --git a/cmd/configure/configure.tpl b/cmd/configure/configure.tpl deleted file mode 100644 index cd32b4e43..000000000 --- a/cmd/configure/configure.tpl +++ /dev/null @@ -1,87 +0,0 @@ -# open evcc at http://evcc.local:7070 -network: - schema: http - host: evcc # announces the hostname on mDNS - port: 7070 - -log: debug -levels: - cache: error - -# unique installation id -plant: {{ .Plant }} - -interval: 30s # control cycle interval -{{- if .SponsorToken }} - -sponsortoken: {{ .SponsorToken }} - -# sponsors can set telemetry: true to enable anonymous data aggregation -# see https://github.com/evcc-io/evcc/discussions/4554 -telemetry: {{ .Telemetry }} -{{- end}} -{{- if .Meters }} - -meters: -{{- range .Meters }} -- {{ .Yaml | indent 2 | trim }} -{{- end }} -{{- end }} -{{- if .Chargers }} - -chargers: -{{- range .Chargers }} -- {{ .Yaml | indent 2 | trim }} -{{- end }} -{{- end }} -{{- if .Vehicles }} - -vehicles: -{{- range .Vehicles }} -- {{ .Yaml | indent 2 | trim }} -{{- end }} -{{- end }} -{{- if .Chargers }} - -loadpoints: -{{- range .Loadpoints }} -- title: {{ .Title }} - charger: {{ .Charger }} -{{- if .ChargeMeter }} - meter: {{ .ChargeMeter }} -{{- end }} -{{- if .Vehicle }} - vehicle: {{ .Vehicle }} -{{- end }} - mode: {{ .Mode }} -{{- end }} -{{- end }} - -site: - title: {{ .Site.Title }} - meters: -{{- if .Site.Grid }} - grid: {{ .Site.Grid }} -{{- end }} -{{- if .Site.PVs }} - pv: - {{- range .Site.PVs }} - - {{ . }} - {{- end }} -{{- end }} -{{- if .Site.Batteries }} - battery: - {{- range .Site.Batteries }} - - {{ . }} - {{- end }} -{{- end }} -{{- if .Hems }} - -hems: -{{ .Hems | indent 2 }} -{{- end }} -{{- if .EEBUS }} - -eebus: -{{ .EEBUS | indent 2 }} -{{- end }} diff --git a/cmd/configure/devicetest.go b/cmd/configure/devicetest.go deleted file mode 100644 index b5852fd4c..000000000 --- a/cmd/configure/devicetest.go +++ /dev/null @@ -1,159 +0,0 @@ -package configure - -import ( - "context" - "errors" - "time" - - "github.com/evcc-io/evcc/api" - "github.com/evcc-io/evcc/charger" - "github.com/evcc-io/evcc/meter" - "github.com/evcc-io/evcc/util/templates" - "github.com/evcc-io/evcc/vehicle" - "go.yaml.in/yaml/v4" -) - -type DeviceTestResult string - -const ( - DeviceTestResultValid DeviceTestResult = "Valid" - DeviceTestResultValidMissingMeter DeviceTestResult = "Valid_MissingMeter" - DeviceTestResultInvalid DeviceTestResult = "Invalid" -) - -type DeviceTest struct { - DeviceCategory DeviceCategory - Template templates.Template - ConfigValues map[string]any -} - -// Test returns: -// - DeviceTestResult: Valid, Valid_MissingMeter, Invalid -// - error: != nil if the device is invalid and can not be configured with the provided settings -func (d *DeviceTest) Test() (DeviceTestResult, error) { - v, err := d.configure() - if err != nil { - return DeviceTestResultInvalid, err - } - - switch DeviceCategories[d.DeviceCategory].class { - case templates.Charger: - return d.testCharger(v) - - case templates.Meter: - return d.testMeter(d.DeviceCategory, v) - - case templates.Vehicle: - return d.testVehicle(v) - - default: - panic("invalid class for category: " + d.DeviceCategory) - } -} - -// configure creates a configured device from a template so we can test it -func (d *DeviceTest) configure() (any, error) { - b, _, err := d.Template.RenderResult(templates.RenderModeInstance, d.ConfigValues) - if err != nil { - return nil, err - } - - var instance struct { - Type string - Other map[string]any `yaml:",inline"` - } - - if err := yaml.Unmarshal(b, &instance); err != nil { - return nil, err - } - - var v any - - ctx := context.TODO() - switch DeviceCategories[d.DeviceCategory].class { - case templates.Meter: - v, err = meter.NewFromConfig(ctx, instance.Type, instance.Other) - case templates.Charger: - v, err = charger.NewFromConfig(ctx, instance.Type, instance.Other) - case templates.Vehicle: - v, err = vehicle.NewFromConfig(ctx, instance.Type, instance.Other) - } - - return v, err -} - -// testCharger tests a charger device -func (d *DeviceTest) testCharger(v any) (DeviceTestResult, error) { - c, ok := v.(api.Charger) - if !ok { - return DeviceTestResultInvalid, errors.New("selected device is not a wallbox") - } - - if _, err := c.Status(); err != nil { - return DeviceTestResultInvalid, err - } - - m, ok := v.(api.Meter) - if !ok { - return DeviceTestResultValidMissingMeter, nil - } - if _, err := m.CurrentPower(); err != nil { - return DeviceTestResultInvalid, err - } - - return DeviceTestResultValid, nil -} - -// testMeter tests a meter device -func (d *DeviceTest) testMeter(deviceCategory DeviceCategory, v any) (DeviceTestResult, error) { - m, ok := v.(api.Meter) - if !ok { - return DeviceTestResultInvalid, errors.New("selected device is not a meter") - } - - power, err := m.CurrentPower() - if err != nil { - return DeviceTestResultInvalid, err - } - - // check if the grid meter reports power 0, which should be impossible - // happens with Kostal Piko charger that do not have a grid meter attached - // but we can't determine this - if power == 0 && deviceCategory == DeviceCategoryGridMeter { - return DeviceTestResultInvalid, errors.New("grid meter reports power 0") - } - - if deviceCategory == DeviceCategoryBatteryMeter { - b, ok := v.(api.Battery) - if !ok { - return DeviceTestResultInvalid, errors.New("selected device is not a battery meter") - } - - _, err := b.Soc() - - for err != nil && errors.Is(err, api.ErrMustRetry) { - time.Sleep(3 * time.Second) - _, err = b.Soc() - } - - if err != nil { - return DeviceTestResultInvalid, err - } - } - - return DeviceTestResultValid, nil -} - -// testVehicle tests a vehicle device -func (d *DeviceTest) testVehicle(v any) (DeviceTestResult, error) { - vv, ok := v.(api.Vehicle) - if !ok { - return DeviceTestResultInvalid, errors.New("selected device is not a vehicle") - } - - if _, err := vv.Soc(); err != nil { - return DeviceTestResultInvalid, err - } - - return DeviceTestResultValid, nil -} diff --git a/cmd/configure/eebus.go b/cmd/configure/eebus.go deleted file mode 100644 index bba5e2a8e..000000000 --- a/cmd/configure/eebus.go +++ /dev/null @@ -1,53 +0,0 @@ -package configure - -import ( - "fmt" - - "github.com/evcc-io/evcc/cmd/shutdown" - "github.com/evcc-io/evcc/server/eebus" - "github.com/evcc-io/evcc/util" -) - -// configureEEBus setup EEBus -func (c *CmdConfigure) configureEEBus(other map[string]any) error { - conf := eebus.Config{ - URI: ":4712", - } - - if err := util.DecodeOther(other, &conf); err != nil { - return err - } - - srv, err := eebus.NewServer(conf) - if err == nil { - eebus.Instance = srv - go eebus.Instance.Run() - shutdown.Register(eebus.Instance.Shutdown) - } - - return err -} - -// eebusCertificate creates EEBUS certificate and returns private/public key -func (c *CmdConfigure) eebusCertificate() (map[string]any, error) { - var eebusConfig map[string]any - - cert, err := eebus.CreateCertificate() - if err != nil { - return eebusConfig, fmt.Errorf("%s", c.localizedString("Error_EEBUS_Certificate_Create")) - } - - pubKey, privKey, err := eebus.GetX509KeyPair(cert) - if err != nil { - return eebusConfig, fmt.Errorf("%s", c.localizedString("Error_EEBUS_Certificate_Use")) - } - - eebusConfig = map[string]any{ - "certificate": map[string]string{ - "public": pubKey, - "private": privKey, - }, - } - - return eebusConfig, nil -} diff --git a/cmd/configure/flow.go b/cmd/configure/flow.go deleted file mode 100644 index 5f6ebe16e..000000000 --- a/cmd/configure/flow.go +++ /dev/null @@ -1,232 +0,0 @@ -package configure - -import ( - "errors" - "fmt" - "slices" - - "github.com/evcc-io/evcc/util/templates" -) - -// configureDeviceGuidedSetup lets the user choose a device that is set to support guided setup -// these are typically devices that -// - contain multiple usages but have the same parameters like host, port, etc. -// - devices that typically are installed with additional specific devices (e.g. SMA Home Manager with SMA Inverters) -func (c *CmdConfigure) configureDeviceGuidedSetup() { - var err error - - var values map[string]any - var deviceCategory DeviceCategory - var supportedDeviceCategories []DeviceCategory - var templateItem templates.Template - - deviceItem := device{} - - for { - fmt.Println() - - templateItem, err = c.processDeviceSelection(DeviceCategoryGuidedSetup) - if err != nil { - return - } - - usageChoices := c.paramChoiceValues(templateItem.Params, templates.ParamUsage) - if len(usageChoices) == 0 { - panic("ERROR: Device template is missing valid usages!") - } - if len(usageChoices) == 0 { - usageChoices = []string{string(DeviceCategoryGridMeter), string(DeviceCategoryPVMeter), string(DeviceCategoryBatteryMeter)} - } - - supportedDeviceCategories = []DeviceCategory{} - - for _, usage := range usageChoices { - switch usage { - case string(DeviceCategoryGridMeter): - supportedDeviceCategories = append(supportedDeviceCategories, DeviceCategoryGridMeter) - case string(DeviceCategoryPVMeter): - supportedDeviceCategories = append(supportedDeviceCategories, DeviceCategoryPVMeter) - case string(DeviceCategoryBatteryMeter): - supportedDeviceCategories = append(supportedDeviceCategories, DeviceCategoryBatteryMeter) - } - } - - // we only ask for the configuration for the first usage - deviceCategory = supportedDeviceCategories[0] - - values = c.processConfig(&templateItem, deviceCategory) - - deviceItem, err = c.processDeviceValues(values, templateItem, deviceItem, deviceCategory) - if err != nil { - if err != c.errDeviceNotValid { - fmt.Println() - fmt.Println(err) - } - fmt.Println() - if !c.askConfigFailureNextStep() { - return - } - continue - } - - break - } - - c.configuration.AddDevice(deviceItem, deviceCategory) - c.processDeviceCapabilities(templateItem.Capabilities) - - if len(supportedDeviceCategories) > 1 { - for _, additionalCategory := range supportedDeviceCategories[1:] { - values[templates.ParamUsage] = additionalCategory.String() - deviceItem, err := c.processDeviceValues(values, templateItem, deviceItem, additionalCategory) - if err != nil { - continue - } - - c.configuration.AddDevice(deviceItem, additionalCategory) - } - } - - fmt.Println() - fmt.Println(templateItem.Title() + " " + c.localizedString("Device_Added")) - - c.configureLinkedTypes(templateItem) -} - -// configureLinkedTypes lets the user configure devices that are marked as being linked to a guided device -// e.g. SMA Inverters, Energy Meter with SMA Home Manager -func (c *CmdConfigure) configureLinkedTypes(templateItem templates.Template) { - added := make([]string, 0) - - for _, linkedTemplate := range templateItem.Linked { - // don't process this linked template if a referenced exclude template was added - if slices.Contains(added, linkedTemplate.ExcludeTemplate) { - continue - } - - linkedTemplateItem, err := templates.ByName(templates.Meter, linkedTemplate.Template) - if err != nil { - fmt.Println("Error: " + err.Error()) - return - } - - if len(linkedTemplateItem.Params) == 0 || linkedTemplate.Usage == "" { - break - } - - linkedTemplateItem.SetCombinedTitle(c.lang) - - category := DeviceCategory(linkedTemplate.Usage) - localizeMap := localizeMap{ - "Linked": linkedTemplateItem.Title(), - "Article": DeviceCategories[category].article, - "Additional": DeviceCategories[category].additional, - "Category": DeviceCategories[category].title, - } - - fmt.Println() - if !c.askYesNo(c.localizedString("AddLinkedDeviceInCategory", localizeMap)) { - continue - } - - for { - if c.configureLinkedTemplate(linkedTemplateItem, category) { - added = append(added, linkedTemplate.Template) - } - - if !linkedTemplate.Multiple { - break - } - - fmt.Println() - if !c.askYesNo(c.localizedString("AddAnotherLinkedDeviceInCategory", localizeMap)) { - break - } - } - } -} - -// configureLinkedTemplate lets the user configure a device that is marked as being linked to a guided device -// returns true if a device was added -func (c *CmdConfigure) configureLinkedTemplate(templateItem templates.Template, category DeviceCategory) bool { - for { - deviceItem := device{} - - values := c.processConfig(&templateItem, category) - deviceItem, err := c.processDeviceValues(values, templateItem, deviceItem, category) - if err != nil { - if !errors.Is(err, c.errDeviceNotValid) { - fmt.Println() - fmt.Println(err) - } - fmt.Println() - if c.askConfigFailureNextStep() { - continue - } - } else { - c.configuration.AddDevice(deviceItem, category) - c.processDeviceCapabilities(templateItem.Capabilities) - - fmt.Println() - fmt.Println(templateItem.Title() + " " + c.localizedString("Device_Added")) - return true - } - break - } - return false -} - -// configureDeviceCategory lets the user select and configure a device from a specific category -func (c *CmdConfigure) configureDeviceCategory(deviceCategory DeviceCategory) (device, []string, error) { - fmt.Println() - fmt.Printf("- %s %s\n", c.localizedString("Device_Configure"), DeviceCategories[deviceCategory].title) - - device := device{ - Name: DeviceCategories[deviceCategory].defaultName, - } - - var deviceDescription string - var capabilities []string - - // repeat until the device is added or the user chooses to continue without adding a device - for { - fmt.Println() - - templateItem, err := c.processDeviceSelection(deviceCategory) - if err != nil { - return device, capabilities, c.errItemNotPresent - } - - deviceDescription = templateItem.Title() - capabilities = templateItem.Capabilities - values := c.processConfig(&templateItem, deviceCategory) - device, err = c.processDeviceValues(values, templateItem, device, deviceCategory) - if err != nil { - if err != c.errDeviceNotValid { - fmt.Println() - fmt.Println(err) - } - // ask if the user wants to add the - fmt.Println() - if !c.askConfigFailureNextStep() { - return device, capabilities, err - } - continue - } - - break - } - - c.configuration.AddDevice(device, deviceCategory) - c.processDeviceCapabilities(capabilities) - - var deviceTitle string - if device.Title != "" { - deviceTitle = " " + device.Title - } - - fmt.Println() - fmt.Println(deviceDescription + deviceTitle + " " + c.localizedString("Device_Added")) - - return device, capabilities, nil -} diff --git a/cmd/configure/helper.go b/cmd/configure/helper.go deleted file mode 100644 index 7a10769e1..000000000 --- a/cmd/configure/helper.go +++ /dev/null @@ -1,502 +0,0 @@ -package configure - -import ( - "fmt" - "slices" - "sort" - "strings" - - "github.com/evcc-io/evcc/plugin/mqtt" - "github.com/evcc-io/evcc/util" - "github.com/evcc-io/evcc/util/sponsor" - "github.com/evcc-io/evcc/util/templates" - stripmd "github.com/writeas/go-strip-markdown/v2" - "go.yaml.in/yaml/v4" -) - -// processDeviceSelection processes the user-selected device, checks -// if it's an actual device and makes sure the requirements are set -func (c *CmdConfigure) processDeviceSelection(deviceCategory DeviceCategory) (templates.Template, error) { - templateItem := c.selectItem(deviceCategory) - - if templateItem.Title() == c.localizedString("ItemNotPresent") { - return templateItem, c.errItemNotPresent - } - - if err := c.processDeviceRequirements(templateItem); err != nil { - return templateItem, err - } - - return templateItem, nil -} - -// processDeviceValues processes the user provided values, creates -// a device configuration and check if it is a valid device -func (c *CmdConfigure) processDeviceValues(values map[string]any, templateItem templates.Template, device device, deviceCategory DeviceCategory) (device, error) { - c.addedDeviceIndex++ - - device.Name = fmt.Sprintf("%s%d", DeviceCategories[deviceCategory].defaultName, c.addedDeviceIndex) - device.Title = templateItem.Title() - for item, value := range values { - if strings.ToLower(item) != "title" { - continue - } - if len(value.(string)) > 0 { - device.Title = value.(string) - } - } - - var categoryWithUsage bool - - fmt.Println() - switch deviceCategory { - case DeviceCategoryPVMeter, DeviceCategoryBatteryMeter, DeviceCategoryGridMeter: - categoryWithUsage = true - fmt.Println(c.localizedString("TestingDevice_TitleUsage", localizeMap{"Device": templateItem.Title(), "Usage": deviceCategory.String()})) - default: - fmt.Println(c.localizedString("TestingDevice_Title", localizeMap{"Device": templateItem.Title()})) - } - - deviceTest := DeviceTest{ - DeviceCategory: deviceCategory, - Template: templateItem, - ConfigValues: values, - } - - testResult, err := deviceTest.Test() - if err != nil { - fmt.Println(" ", c.localizedString("Error", localizeMap{"Error": err})) - fmt.Println() - - question := c.localizedString("TestingDevice_AddFailed", localizeMap{"Device": templateItem.Title()}) - if categoryWithUsage { - question = c.localizedString("TestingDevice_AddFailedUsage", localizeMap{"Device": templateItem.Title(), "Usage": deviceCategory.String()}) - } - if !c.askYesNo(question) { - c.addedDeviceIndex-- - return device, c.errDeviceNotValid - } - } else if deviceCategory == DeviceCategoryCharger && testResult == DeviceTestResultValid { - device.ChargerHasMeter = true - } - - templateItem.Params = append(templateItem.Params, templates.Param{Name: "name", Value: device.Name}) - if !c.expandedMode { - b, err := templateItem.RenderProxyWithValues(values, c.lang) - if err != nil { - c.addedDeviceIndex-- - return device, err - } - - device.Yaml = string(b) - } else { - for _, p := range templateItem.Params { - if p.Name == "name" { - values["name"] = p.Value - templateItem.Render = fmt.Sprintf("name: {{ .name }}\n%s", templateItem.Render) - } - } - - b, _, err := templateItem.RenderResult(templates.RenderModeInstance, values) - if err != nil { - c.addedDeviceIndex-- - return device, err - } - - device.Yaml = string(b) - } - - return device, nil -} - -func (c *CmdConfigure) processDeviceCapabilities(capabilitites []string) { - if slices.Contains(capabilitites, templates.CapabilitySMAHems) { - c.capabilitySMAHems = true - } -} - -// processDeviceRequirements handles device requirements -func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template) error { - requirementDescription := stripmd.Strip(templateItem.Requirements.Description.String(c.lang)) - if len(requirementDescription) > 0 { - fmt.Println() - fmt.Println("-------------------------------------------------") - fmt.Println(c.localizedString("Requirements_Title")) - fmt.Println(requirementDescription) - fmt.Println("-------------------------------------------------") - } - - // check if sponsorship is required - if slices.Contains(templateItem.Requirements.EVCC, templates.RequirementSponsorship) && c.configuration.config.SponsorToken == "" { - if err := c.askSponsortoken(true, false); err != nil { - return err - } - } - - // check if we need to setup an MQTT broker - if slices.Contains(templateItem.Requirements.EVCC, templates.RequirementMQTT) { - if c.configuration.config.MQTT == "" { - mqttConfig, err := c.configureMQTT(templateItem) - 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 - if slices.Contains(templateItem.Requirements.EVCC, templates.RequirementEEBUS) { - if c.configuration.config.EEBUS == "" { - fmt.Println() - fmt.Println("-- EEBUS -----------------------------------") - fmt.Println() - eebusConfig, err := c.eebusCertificate() - if err != nil { - return fmt.Errorf("%s: %s", c.localizedString("Requirements_EEBUS_Cert_Error"), err) - } - - if err := c.configureEEBus(eebusConfig); err != nil { - return err - } - - eebusYaml, err := yaml.Marshal(eebusConfig) - if err != nil { - return err - } - - c.configuration.config.EEBUS = string(eebusYaml) - fmt.Println() - fmt.Println("--------------------------------------------") - } - - fmt.Println() - fmt.Println(c.localizedString("Requirements_EEBUS_Pairing")) - fmt.Scanln() - } - - return nil -} - -func (c *CmdConfigure) askSponsortoken(required, feature bool) error { - fmt.Println("-- Sponsorship -----------------------------") - if required { - fmt.Println() - if feature { - fmt.Println(c.localizedString("Requirements_Sponsorship_Feature_Title")) - } else { - fmt.Println(c.localizedString("Requirements_Sponsorship_Title")) - } - } else { - fmt.Println() - fmt.Println(c.localizedString("Requirements_Sponsorship_Optional_Title")) - } - fmt.Println() - if !c.askYesNo(c.localizedString("Requirements_Sponsorship_Token")) { - fmt.Println() - fmt.Println("--------------------------------------------") - return c.errItemNotPresent - } - - sponsortoken := c.askValue(question{ - label: c.localizedString("Requirements_Sponsorship_Token_Input"), - mask: true, - required: true, - }) - - err := sponsor.ConfigureSponsorship(sponsortoken) - if err != nil { - question := c.localizedString("TestingDevice_AddFailed", localizeMap{"Device": "Sponsorship Token"}) - if c.askYesNo(question) { - err = nil - } - } - if err == nil { - c.configuration.config.SponsorToken = sponsortoken - } - - fmt.Println() - fmt.Println("--------------------------------------------") - - return err -} - -func (c *CmdConfigure) configureMQTT(_ templates.Template) (map[string]any, error) { - fmt.Println() - fmt.Println("-- MQTT Broker ----------------------------") - - var err error - - for { - fmt.Println() - _, paramHost := templates.ConfigDefaults.ParamByName("host") - _, paramPort := templates.ConfigDefaults.ParamByName("port") - _, paramUser := templates.ConfigDefaults.ParamByName("user") - _, paramPassword := templates.ConfigDefaults.ParamByName("password") - _, paramCaCert := templates.ConfigDefaults.ParamByName("caCert") - _, paramClientCert := templates.ConfigDefaults.ParamByName("clientCert") - _, paramClientKey := templates.ConfigDefaults.ParamByName("clientKey") - - host := c.askParam(paramHost) - port := c.askParam(paramPort) - user := c.askParam(paramUser) - password := c.askParam(paramPassword) - caCert := c.askParam(paramCaCert) - clientCert := c.askParam(paramClientCert) - clientKey := c.askParam(paramClientKey) - - fmt.Println() - fmt.Println("--------------------------------------------") - - broker := fmt.Sprintf("%s:%s", host, port) - - mqttConfig := map[string]any{ - "broker": broker, - "user": user, - "password": password, - "caCert": caCert, - "clientCert": clientCert, - "clientKey": clientKey, - } - - log := util.NewLogger("mqtt") - - if mqtt.Instance, err = mqtt.RegisteredClient(log, broker, user, password, "", 1, false, caCert, clientCert, clientKey); err == nil { - return mqttConfig, nil - } - - fmt.Println() - question := c.localizedString("TestingMQTTFailed") - if !c.askYesNo(question) { - return nil, fmt.Errorf("failed configuring mqtt: %w", err) - } - } -} - -// fetchElements returns template items of a given class -func (c *CmdConfigure) fetchElements(deviceCategory DeviceCategory) []templates.Template { - var items []templates.Template - for _, tmpl := range templates.ByClass(DeviceCategories[deviceCategory].class) { - if len(tmpl.Params) == 0 { - continue - } - - for _, t := range tmpl.Titles(c.lang) { - titleTmpl := templates.Template{ - TemplateDefinition: tmpl.TemplateDefinition, - } - title := t - groupTitle := titleTmpl.GroupTitle(c.lang) - if groupTitle != "" { - title += " [" + groupTitle + "]" - } - titleTmpl.SetTitle(title) - - if deviceCategory == DeviceCategoryGuidedSetup { - if tmpl.GuidedSetupEnabled() { - items = append(items, titleTmpl) - } - } else { - if len(DeviceCategories[deviceCategory].categoryFilter) == 0 || - c.paramChoiceContains(tmpl.Params, templates.ParamUsage, DeviceCategories[deviceCategory].categoryFilter.String()) { - items = append(items, titleTmpl) - } - } - } - } - - sort.Slice(items, func(i, j int) bool { - // sort generic templates to the bottom - if items[i].Group != "" && items[j].Group == "" { - return false - } - if items[i].Group == "" && items[j].Group != "" { - return true - } - if items[i].Group != items[j].Group { - return strings.ToLower(items[i].GroupTitle(c.lang)) < strings.ToLower(items[j].GroupTitle(c.lang)) - } - return strings.ToLower(items[i].Title()) < strings.ToLower(items[j].Title()) - }) - - return items -} - -// paramChoiceContains is a helper function to check if a param choice contains a given value -func (c *CmdConfigure) paramChoiceContains(params []templates.Param, name, filter string) bool { - choices := c.paramChoiceValues(params, name) - - return slices.Contains(choices, filter) -} - -// paramChoiceValues provides a list of possible values for a param choice -func (c *CmdConfigure) paramChoiceValues(params []templates.Param, name string) []string { - choices := []string{} - - for _, item := range params { - if item.Name != name { - continue - } - - choices = append(choices, item.Choice...) - } - - return choices -} - -// processConfig processes an EVCC configuration item -// Returns a map with param name and values -func (c *CmdConfigure) processConfig(templateItem *templates.Template, deviceCategory DeviceCategory) map[string]any { - fmt.Println() - fmt.Println(c.localizedString("Config_Title")) - fmt.Println() - - c.processModbusConfig(templateItem) - - return c.processParams(templateItem, deviceCategory) -} - -// process a list of params -func (c *CmdConfigure) processParams(templateItem *templates.Template, deviceCategory DeviceCategory) map[string]any { - usageFilter := DeviceCategories[deviceCategory].categoryFilter - - additionalConfig := make(map[string]any) - - for _, param := range templateItem.Params { - switch param.Name { - case templates.ParamModbus: - additionalConfig[param.Name] = param.Value - - case templates.ParamUsage: - if usageFilter != "" { - additionalConfig[param.Name] = usageFilter.String() - } - - default: - if param.IsAdvanced() && !c.advancedMode || param.IsDeprecated() { - continue - } - - if usageFilter != "" && len(param.Usages) > 0 && !slices.Contains(param.Usages, string(usageFilter)) { - continue - } - - switch param.Type { - case templates.TypeList: - values := c.processListInputConfig(param) - var nonEmptyValues []string - for _, value := range values { - if value != "" { - nonEmptyValues = append(nonEmptyValues, value) - } - } - additionalConfig[param.Name] = nonEmptyValues - - default: - // TODO make processInputConfig aware of default values added by template - if value := c.processInputConfig(param); value != "" { - additionalConfig[param.Name] = value - } - } - } - } - - 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 user decides to stop - for { - newValue := c.processInputConfig(param) - values = append(values, newValue) - - if newValue == "" { - break - } - - if !c.askYesNo(" " + c.localizedString("Config_AddAnotherValue")) { - break - } - } - - return values -} - -// handle user input for a simple one value input -func (c *CmdConfigure) processInputConfig(param templates.Param) string { - label := param.Name - langLabel := param.Description.String(c.lang) - if langLabel != "" { - label = langLabel - } - - if param.Unit != "" { - label = fmt.Sprintf("%s (%s)", label, param.Unit) - } - - value := c.askValue(question{ - label: label, - defaultValue: param.Default, - exampleValue: param.Example, - help: param.Help.ShortString(c.lang), - valueType: param.Type, - choice: param.Choice, - mask: param.IsMasked(), - required: param.IsRequired(), - }) - - return value -} - -// processModbusConfig adds default values from the modbus Param to the template -// and handles user input for interface type selection -func (c *CmdConfigure) processModbusConfig(templateItem *templates.Template) { - var choices []string - var choiceTypes []string - - modbusIndex, modbusParam := templateItem.ParamByName(templates.ParamModbus) - if modbusIndex == -1 { - return - } - - config := templates.ConfigDefaults.Modbus - - for _, choice := range modbusParam.Choice { - if config.Interfaces[choice] == nil { - continue - } - - for _, itype := range config.Interfaces[choice] { - title := config.Types[itype].Description - choices = append(choices, title.String(c.lang)) - choiceTypes = append(choiceTypes, itype) - } - } - - if len(choices) == 0 { - return - } - - // ask for modbus interface type - var index int - if len(choices) > 1 { - index, _ = c.askChoice(c.localizedString("Config_ModbusInterface"), choices) - } - - values := make(map[string]any) - templateItem.Params[modbusIndex].Value = choiceTypes[index] - - // add the interface type specific modbus params - templateItem.ModbusParams(choiceTypes[index], values) - - // update the modbus default values - templateItem.ModbusValues(templates.RenderModeInstance, values) -} diff --git a/cmd/configure/localization/de.toml b/cmd/configure/localization/de.toml deleted file mode 100644 index f6bbf0801..000000000 --- a/cmd/configure/localization/de.toml +++ /dev/null @@ -1,113 +0,0 @@ -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_NewConfiguration = "Eine neue evcc Konfigurationsdatei erstellen" -Flow_Type_SingleDevice = "Ein einzelnes Gerät konfigurieren (muss manuell in eine Konfigurationsdatei eingetragen werden!)" -Flow_NewConfiguration_Setup = "- Hausinstallation einrichten" -Flow_NewConfiguration_Select = "Wähle eines der folgenden integrierten PV-Systeme aus, oder '{{ .ItemNotPresent }}' falls es kein integriertes PV-System vorhanden ist" -Flow_SingleDevice_Setup = "- Ein Gerät konfigurieren" -Flow_SingleDevice_Select = "Wähle eine der folgenden Gerätekategorien aus:" -Flow_SingleDevice_Config = "Die Konfiguration lautet:" -Flow_SMAHems_Setup = "- SMA HEMS konfigurieren" -Flow_SMAHems_Add = "Möchtest du die Wallboxen an den SMA Home Manager anbinden, damit diese z.B. für die Steuerung der Hausbatterie berücksichtigt werden können?" -ItemNotPresent = "Mein Gerät ist nicht in der Liste" -AddDeviceInCategory = "Möchtest du {{ .Article }} {{ .Category }} hinzufügen?" -AddAnotherDeviceInCategory = "Möchtest du noch {{ .Additional }} {{ .Category }} hinzufügen?" -AddLinkedDeviceInCategory = "Möchtest du ein '{{ .Linked }}' Gerät als {{ .Article }} {{ .Category }} hinzufügen?" -AddAnotherLinkedDeviceInCategory = "Möchtest du noch ein '{{ .Linked }}' Gerät als {{ .Article }} {{ .Category }} hinzufügen?" -Error = "Fehler: {{ .Error }}" -Error_ItemNotPresent = "Gerät nicht vorhanden" -Error_DeviceNotValid = "Das Gerät funktioniert nicht" -Error_EEBUS_Certificate_Create = "Konnte das EEBUS Zertifikat nicht erstellen" -Error_EEBUS_Certificate_Use = "Konnte das EEBUS Zertifikat nicht verwenden" -File_Exists = "Die Datei {{ .FileName }} existiert bereits. Soll die Datei überschrieben werden?" -File_Permissions = "Die Datei {{ .FileName }} existiert bereits und kann nicht überschrieben werden." -File_NewFilename = "Bitte gib einen neuen Dateinamen an" -File_Error_SaveFailed = "Die Konfiguration konnte nicht in der Datei {{ .FileName }} gespeichert werden" -File_SaveSuccess = "Die Konfiguration wurde erfolgreich in der Datei {{ .FileName }} gespeichert" -Choose = "Wähle" -Category_ChargerTitle = "Wallbox" -Category_ChargerArticle = "eine" -Category_ChargerAdditional = "eine weitere" -Category_SystemTitle = "integriertes PV-System" -Category_SystemArticle = "ein" -Category_SystemAdditional = "ein weiteres" -Category_GridMeterTitle = "Netz-Stromzähler" -Category_GridMeterArticle = "einen" -Category_GridMeterAdditional = "einen weiteren" -Category_PVMeterTitle = "PV Wechselrichter (oder entsprechender Stromzähler)" -Category_PVMeterArticle = "einen" -Category_PVMeterAdditional = "einen weiteren" -Category_BatteryMeter = "Batterie Wechselrichter (oder entsprechender Stromzähler)" -Category_BatteryMeterArticle = "einen" -Category_BatteryMeterAdditional = "einen weiteren" -Category_ChargeMeterTitle = "Ladestromzähler" -Category_ChargeMeterArticle = "einen" -Category_ChargeMeterAdditional = "einen weiteren" -Category_VehicleTitle = "Fahrzeug" -Category_VehicleArticle = "ein" -Category_VehicleAdditional = "ein weiteres" -TestingDevice_Title = "Teste die Konfiguration von {{ .Device }} ..." -TestingDevice_TitleUsage = "Teste die {{ .Usage}} Konfiguration von {{ .Device }} ..." -TestingDevice_RepeatStep = "Möchtest du erneut ein Gerät aus der Liste auswählen und einrichten?" -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 {{ .Usage }} trotzdem in die Konfiguration aufgenommen werden?" -TestingMQTTFailed = "Der Test der MQTT Konfiguration ist fehlgeschlagen. Möchtest du die Konfiguration wiederholen?" -Requirements_Title = "Das Gerät hat die folgenden Voraussetzungen:" -Requirements_More = "Weitere Informationen:" -Requirements_Sponsorship_Title = "Dieses Gerät benötigt ein Sponsorship von evcc. Wie das funktioniert und was das ist, findest du hier: https://docs.evcc.io/docs/sponsorship" -Requirements_Sponsorship_Optional_Title = "Die Entwicklung von evcc kann über Sponsoring unterstützt werden. Wie das funktioniert und was das ist, findest du hier: https://docs.evcc.io/docs/sponsorship" -Requirements_Sponsorship_Feature_Title = "Dies Verwendung dieser Funktionalität benötigt ein Sponsorship von evcc. Wie das funktioniert und was das ist, findest du hier: https://docs.evcc.io/docs/sponsorship" -Requirements_Sponsorship_Token = "Bist du ein Sponsor?" -Requirements_Sponsorship_Token_Input = "Bitte gib das Sponsortoken ein" -Requirements_EEBUS_Cert_Error = "Fehler: Das EEBUS Zertifikat konnte nicht erstellt werden" -Requirements_EEBUS_Pairing = "Du hast eine Wallbox ausgewählt, welche über das EEBUS Protokoll angesprochen wird.\nDazu muss die Wallbox nun mit evcc verbunden werden. Dies geschieht üblicherweise auf der Webseite der Wallbox.\nDrücke die Enter-Taste, wenn der Prozess gestartet ist." -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 \n" -InputError = "Bei der Eingabe ist ein Fehler aufgetreten:" -Value_Choice = "Auswahl:" -Value_Help = "Hilfe:" -Value_Required = "erforderlich" -Value_Optional = "optional" -Value_Sample = "Beispiel" -ValueError_Invalid = "Ungültiger Wert" -ValueError_Used = "Dieser Wert wird bereits verwendet." -ValueError_Empty = "Der Wert darf nicht leer sein." -ValueError_Float = "Der Wert muss eine Zahl sein. Nachkommastellen mit . anstatt mit , trennen." -ValueError_Number = "Der Wert muss eine ganzzahlige Zahl sein." -ValueError_NumberLowerThanMin = "Der Wert muss größer oder gleich {{ .Min }} sein." -ValueError_NumberBiggerThanMax = "Der Wert muss kleiner oder gleich {{ .Max }} sein." -ValueError_Duration = "Der Wert muss eine Zeitdauer angeben. Zum Beispiel: 1s, 1m, 1h" -Device_Configure = "Konfiguration" -Device_Added = "wurde erfolgreich hinzugefügt." -Loadpoint_Setup = "- Ladepunkt(e) einrichten" -Loadpoint_Title = "Titel des Ladepunktes" -Loadpoint_AddAnother = "Möchtest du einen weiteren Ladepunkt hinzufügen?" -Loadpoint_DefaultTitle = "Garage" -Loadpoint_ResetOnDisconnect = "Soll beim Abstecken des Ladekabels von einem Fahrzeug, die Lade-Standardeinstellungen wieder hergestellt werden?" -Loadpoint_WallboxWOMeter = "Das System konnte nicht erkennen, ob ein Ladestromzähler in der Wallbox eingebaut ist. Möchtest du einen externen Ladestromzähler hinzufügen?" -Loadpoint_WallboxMaxPower = "Was ist die maximale Leistung, welche die Wallbox 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_WallboxPower36kW = "3,6 kW" -Loadpoint_WallboxPower11kW = "11 kW" -Loadpoint_WallboxPower22kW = "22 kW" -Loadpoint_WallboxPowerOther = "Andere Leistung" -Loadpoint_VehicleDisableAutoDetection = "Möchtest du die automatische Fahrzeugerkennung deaktivieren und ein Fahrzeug fest zuweisen? (Automatische Erkennung funktioniert nicht mit Offline Fahrzeugen!)" -Loadpoint_VehicleSelection = "Welches Fahrzeug soll hier fest zugewiesen werden?" -ChargeMode_Question = "Was sollte der Standard-Lademodus sein, wenn ein Fahrzeug angeschlossen wird?" -ChargeModeOff = "Stop" -ChargeModeNow = "Sofort (mit größtmöglicher Leistung)" -ChargeModeMinPV = "Min+PV (mit der kleinstmöglichen Leistung, schneller wenn genügend PV Überschuss vorhanden ist)" -ChargeModePV = "PV (Nur mit PV Überschuss)" -ChargeModeNone = "Keinen (Benutzt den im Ladepunkt gesetzten Lademodus)" -Site_Setup = "- Standort einrichten" -Site_Title = "Titel des Standortes" -Site_DefaultTitle = "Mein Zuhause" diff --git a/cmd/configure/localization/en.toml b/cmd/configure/localization/en.toml deleted file mode 100644 index 4205a1a9d..000000000 --- a/cmd/configure/localization/en.toml +++ /dev/null @@ -1,113 +0,0 @@ -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 configuration file created 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_NewConfiguration = "Create a new evcc configuration file" -Flow_Type_SingleDevice = "Configure a single device (has to be added manually to a configuration file!)" -Flow_NewConfiguration_Setup = "- Setup meters (house installation)" -Flow_NewConfiguration_Select = "Choose one of the following integrated PV systems, or '{{ .ItemNotPresent }}' if you have none of them" -Flow_SingleDevice_Setup = "- Setup a device" -Flow_SingleDevice_Select = "Choose one of the following device categories" -Flow_SingleDevice_Config = "The configuration:" -Flow_SMAHems_Setup = "- Setup SMA HEMS" -Flow_SMAHems_Add = "Do you want to connect your chargers to SMA Home Manager, so that it can consider them e.g. for controlling the in-house battery?" -ItemNotPresent = "My device is not in this list" -AddDeviceInCategory = "Do you want to add {{ .Article }} {{ .Category }}?" -AddAnotherDeviceInCategory = "Do you want to add {{ .Additional }} {{ .Category }}?" -AddLinkedDeviceInCategory = "Do you want to add a '{{ .Linked }}' device as {{ .Article }} {{ .Category }}?" -AddAnotherLinkedDeviceInCategory = "Do you want to add another '{{ .Linked }}' device as {{ .Article }} {{ .Category }}?" -Error = "Error: {{ .Error }}" -Error_ItemNotPresent = "Device not present" -Error_DeviceNotValid = "Device does not work" -Error_EEBUS_Certificate_Create = "Could not create the EEBUS certificate" -Error_EEBUS_Certificate_Use = "Could not process the generated EEBUS certificate" -File_Exists = "The file {{ .FileName }} already exists. Do you want to replace it?" -File_Permissions = "The file {{ .FileName }} already exists and can not be overwritten." -File_NewFilename = "Please provide a new filename" -File_Error_SaveFailed = "The configuration could not be saved in the file {{ .FileName }}" -File_SaveSuccess = "The configuration was successfully saved in the file {{ .FileName }}" -Choose = "Choose" -Category_ChargerTitle = "charger" -Category_ChargerArticle = "a" -Category_ChargerAdditional = "another" -Category_SystemTitle = "integrated PV system" -Category_SystemArticle = "an" -Category_SystemAdditional = "another" -Category_GridMeterTitle = "grid meter" -Category_GridMeterArticle = "a" -Category_GridMeterAdditional = "another" -Category_PVMeterTitle = "PV inverter (or corresponding meter)" -Category_PVMeterArticle = "a" -Category_PVMeterAdditional = "another" -Category_BatteryMeter = "battery inverter (or corresponding meter)" -Category_BatteryMeterArticle = "a" -Category_BatteryMeterAdditional = "another" -Category_ChargeMeterTitle = "charge meter" -Category_ChargeMeterArticle = "a" -Category_ChargeMeterAdditional = "another" -Category_VehicleTitle = "vehicle" -Category_VehicleArticle = "a" -Category_VehicleAdditional = "another" -TestingDevice_Title = "Testing the configuration of {{ .Device }} ..." -TestingDevice_TitleUsage = "Testing the {{ .Usage }} configuration of {{ .Device }} ..." -TestingDevice_RepeatStep = "Do you want to repeat choosing a device and configuring it?" -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 {{ .Usage }} anyway?" -TestingMQTTFailed = "Testing the MQTT configuration failed. Do you want to repeat its configuration?" -Requirements_Title = "The device has the following requirements:" -Requirements_More = "Additional information:" -Requirements_Sponsorship_Title = "This device requires evcc sponsorship. Check the following link for what this is and how it works: https://docs.evcc.io/en/docs/sponsorship" -Requirements_Sponsorship_Optional_Title = "The development of evcc can be supported via sponsoring. Check the following link for what this is and how it works: https://docs.evcc.io/en/docs/sponsorship" -Requirements_Sponsorship_Feature_Title = "To use this feature evcc sponsorship is required. Check the following link for what this is and how it works: https://docs.evcc.io/en/docs/sponsorship" -Requirements_Sponsorship_Token = "Are you already a sponsor?" -Requirements_Sponsorship_Token_Input = "Please enter the sponsorship token" -Requirements_EEBUS_Cert_Error = "Error: The EEBUS certificate couldn't be created" -Requirements_EEBUS_Pairing = "You selected a charger which will be accessed via the EEBUS protocol.\nFor that, the charger must be connected to evcc. This can usually be done in the web interface of the charger.\nPlease press the enter key once you have started the process." -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, please try the manual configuration. You can find more details about that on our website: https://docs.evcc.io/en/ \n" -InputError = "An input error occurred:" -Value_Choice = "Choose:" -Value_Help = "Help:" -Value_Required = "required" -Value_Optional = "optional" -Value_Sample = "Example" -ValueError_Invalid = "This value is invalid" -ValueError_Used = "This value is already in use." -ValueError_Empty = "The value may not be empty." -ValueError_Float = "The value must be a number." -ValueError_Number = "The value must be an integer." -ValueError_NumberLowerThanMin = "The value must be bigger or equal to {{ .Min }}." -ValueError_NumberBiggerThanMax = "The value must be smaller or equal to {{ .Max }}." -ValueError_Duration = "The value must include a duration. For example: 1s, 1m, 1h" -Device_Configure = "Configuration" -Device_Added = "was successfully added." -Loadpoint_Setup = "- Setup loadpoint(s), i.e. group of colocated chargers" -Loadpoint_Title = "Loadpoint title" -Loadpoint_AddAnother = "Do you want to add another loadpoint?" -Loadpoint_DefaultTitle = "Garage" -Loadpoint_ResetOnDisconnect = "Will disconnecting the charging cable from the vehicle reset the charging settings to the defaults?" -Loadpoint_WallboxWOMeter = "The system could not detect if the charger provides power data. Do you want to add an external meter instead?" -Loadpoint_WallboxMaxPower = "What is the maximum power your charger can provide?" -Loadpoint_WallboxMinAmperage = "What is the MINIMUM amperage your charger can provide on a single phase?" -Loadpoint_WallboxMaxAmperage = "What is the MAXIMUM amperage your charger can provide on a single phase?" -Loadpoint_WallboxPhases = "How many phases are connected to the charger?" -Loadpoint_WallboxPower36kW = "3.6 kW" -Loadpoint_WallboxPower11kW = "11 kW" -Loadpoint_WallboxPower22kW = "22 kW" -Loadpoint_WallboxPowerOther = "Other option" -Loadpoint_VehicleDisableAutoDetection = "Do you want to disable the automatic vehicle detection and assign a fixed vehicle? (Automatic detection does not work with offline vehicles!)" -Loadpoint_VehicleSelection = "Which vehicle should be assigned here?" -ChargeMode_Question = "What should be the default charging mode when a vehicle is connected?" -ChargeModeOff = "Off" -ChargeModeNow = "Now (charging with maximum power)" -ChargeModeMinPV = "Min+PV (charging with minimum power, faster if enough PV power surplus is available)" -ChargeModePV = "PV (Only with PV power surplus)" -ChargeModeNone = "None (Use what is set at the loadpoint)" -Site_Setup = "- Setup site" -Site_Title = "Site title" -Site_DefaultTitle = "My home" diff --git a/cmd/configure/main.go b/cmd/configure/main.go deleted file mode 100644 index af40d29e4..000000000 --- a/cmd/configure/main.go +++ /dev/null @@ -1,448 +0,0 @@ -package configure - -import ( - "bufio" - _ "embed" - "errors" - "fmt" - "maps" - "os" - "slices" - "strconv" - "strings" - - "github.com/BurntSushi/toml" - "github.com/cloudfoundry/jibber_jabber" - "github.com/evcc-io/evcc/hems/shm" - "github.com/evcc-io/evcc/util" - "github.com/evcc-io/evcc/util/machine" - "github.com/evcc-io/evcc/util/templates" - "github.com/nicksnyder/go-i18n/v2/i18n" - "golang.org/x/text/language" -) - -//go:embed localization/de.toml -var lang_de string - -//go:embed localization/en.toml -var lang_en string - -type CmdConfigure struct { - configuration Configure - localizer *i18n.Localizer - log *util.Logger - - lang string - advancedMode, expandedMode bool - addedDeviceIndex int - errItemNotPresent, errDeviceNotValid error - - capabilitySMAHems bool -} - -// Run starts the interactive configuration -func (c *CmdConfigure) Run(log *util.Logger, flagLang string, advancedMode, expandedMode bool, category string) { - c.log = log - c.advancedMode = advancedMode - c.expandedMode = expandedMode - - c.log.INFO.Printf("evcc %s", util.FormattedVersion()) - - bundle := i18n.NewBundle(language.German) - bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) - if _, err := bundle.ParseMessageFileBytes([]byte(lang_de), "localization/de.toml"); err != nil { - panic(err) - } - if _, err := bundle.ParseMessageFileBytes([]byte(lang_en), "localization/en.toml"); err != nil { - panic(err) - } - - c.lang = "de" - systemLanguage, err := jibber_jabber.DetectLanguage() - if err == nil { - c.lang = systemLanguage - } - if flagLang != "" { - c.lang = flagLang - } - - c.localizer = i18n.NewLocalizer(bundle, c.lang) - - c.setDefaultTexts() - - // Assign random plant id. Don't use actual machine-id as file might - // be copied around to a different machine. - c.configuration.config.Plant = machine.RandomID() - - if err = machine.CustomID(c.configuration.config.Plant); err != nil { - panic(err) - } - - fmt.Println() - fmt.Println(c.localizedString("Intro")) - - 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"), []string{ - c.localizedString("Flow_Mode_Standard"), - c.localizedString("Flow_Mode_Advanced"), - }) - if flowIndex == 1 { - c.advancedMode = true - } - } - - if !c.advancedMode && category == "" { - c.flowNewConfigFile() - return - } - - if category != "" { - for cat := range DeviceCategories { - if cat == DeviceCategory(category) { - c.flowSingleDevice(DeviceCategory(category)) - return - } - } - - log.FATAL.Fatalln("invalid category:", category, "have:", slices.Collect(maps.Keys(DeviceCategories))) - } - - fmt.Println() - flowIndex, _ := c.askChoice(c.localizedString("Flow_Type"), []string{ - c.localizedString("Flow_Type_NewConfiguration"), - c.localizedString("Flow_Type_SingleDevice"), - }) - switch flowIndex { - case 0: - c.flowNewConfigFile() - case 1: - c.flowSingleDevice("") - } -} - -// configureSingleDevice implements the flow for getting a single device configuration -func (c *CmdConfigure) flowSingleDevice(category DeviceCategory) { - fmt.Println() - fmt.Println(c.localizedString("Flow_SingleDevice_Setup")) - fmt.Println() - fmt.Println(c.localizedString("Flow_SingleDevice_Select")) - - // only consider the device categories that are marked for this flow - categoryChoices := []string{ - DeviceCategories[DeviceCategoryGridMeter].title, - DeviceCategories[DeviceCategoryPVMeter].title, - DeviceCategories[DeviceCategoryBatteryMeter].title, - DeviceCategories[DeviceCategoryChargeMeter].title, - DeviceCategories[DeviceCategoryCharger].title, - DeviceCategories[DeviceCategoryVehicle].title, - } - - if category == "" { - fmt.Println() - _, categoryTitle := c.askChoice(c.localizedString("Flow_SingleDevice_Select"), categoryChoices) - - for item, data := range DeviceCategories { - if data.title == categoryTitle { - category = item - break - } - } - } - - devices := c.configureDevices(category, false, false) - for _, item := range devices { - fmt.Println() - fmt.Println(c.localizedString("Flow_SingleDevice_Config", localizeMap{})) - fmt.Println() - - scanner := bufio.NewScanner(strings.NewReader(item.Yaml)) - for scanner.Scan() { - fmt.Println(" " + scanner.Text()) - } - } - fmt.Println() -} - -// configureNewConfigFile implements the flow for creating a new configuration file -func (c *CmdConfigure) flowNewConfigFile() { - fmt.Println() - fmt.Println(c.localizedString("Flow_NewConfiguration_Setup")) - fmt.Println() - fmt.Println(c.localizedString("Flow_NewConfiguration_Select", localizeMap{"ItemNotPresent": c.localizedString("ItemNotPresent")})) - c.configureDeviceGuidedSetup() - - _ = c.configureDevices(DeviceCategoryGridMeter, true, false) - _ = c.configureDevices(DeviceCategoryPVMeter, true, true) - _ = c.configureDevices(DeviceCategoryBatteryMeter, true, true) - _ = c.configureDevices(DeviceCategoryVehicle, true, true) - - c.configureLoadpoints() - c.configureSite() - - // check if SMA HEMS is available and ask the user if it should be added - if c.capabilitySMAHems { - c.configureSMAHems() - } - - if c.advancedMode && c.configuration.config.SponsorToken == "" { - _ = c.askSponsortoken(false, false) - } - - yaml, err := c.configuration.RenderConfiguration() - if err != nil { - c.log.FATAL.Fatal(err) - } - - fmt.Println() - - filename := DefaultConfigFilename - - for { - file, err := os.OpenFile(filename, os.O_WRONLY, 0o666) - if errors.Is(err, os.ErrNotExist) { - break - } - file.Close() - // in case of permission error, we can't write to the file anyway - if os.IsPermission(err) { - fmt.Println(c.localizedString("File_Permissions", localizeMap{"FileName": filename})) - } else if c.askYesNo(c.localizedString("File_Exists", localizeMap{"FileName": filename})) { - break - } - - filename = c.askValue(question{ - label: c.localizedString("File_NewFilename"), - exampleValue: "evcc_neu.yaml", - required: true, - }) - } - - err = os.WriteFile(filename, yaml, 0o755) - if err != nil { - fmt.Printf("%s: ", c.localizedString("File_Error_SaveFailed", localizeMap{"FileName": filename})) - c.log.FATAL.Fatal(err) - } - fmt.Println(c.localizedString("File_SaveSuccess", localizeMap{"FileName": filename})) -} - -// configureDevices asks device specific questions -func (c *CmdConfigure) configureDevices(deviceCategory DeviceCategory, askAdding, askMultiple bool) []device { - var devices []device - - if deviceCategory == DeviceCategoryGridMeter && c.configuration.MetersOfCategory(deviceCategory) > 0 { - return nil - } - - localizeMap := localizeMap{ - "Article": DeviceCategories[deviceCategory].article, - "Additional": DeviceCategories[deviceCategory].additional, - "Category": DeviceCategories[deviceCategory].title, - } - if askAdding { - addDeviceText := c.localizedString("AddDeviceInCategory", localizeMap) - if c.configuration.MetersOfCategory(deviceCategory) > 0 { - addDeviceText = c.localizedString("AddAnotherDeviceInCategory", localizeMap) - } - - fmt.Println() - if !c.askYesNo(addDeviceText) { - return nil - } - } - - for { - device, capabilities, err := c.configureDeviceCategory(deviceCategory) - if err != nil { - break - } - devices = append(devices, device) - - c.processDeviceCapabilities(capabilities) - - if !askMultiple { - break - } - - fmt.Println() - if !c.askYesNo(c.localizedString("AddAnotherDeviceInCategory", localizeMap)) { - break - } - } - - return devices -} - -// configureSMAHems asks the user if he wants to add the SMA HEMS -func (c *CmdConfigure) configureSMAHems() { - // check if the system provides a machine-id - if _, err := shm.UniqueDeviceID(); err != nil { - return - } - - fmt.Println() - fmt.Println(c.localizedString("Flow_SMAHems_Setup")) - - fmt.Println() - if !c.askYesNo(c.localizedString("Flow_SMAHems_Add")) { - return - } - - // check if we need to setup a HEMS - c.configuration.config.Hems = "type: sma\nAllowControl: false\n" -} - -// configureLoadpoints asks loadpoint specific questions -func (c *CmdConfigure) configureLoadpoints() { - fmt.Println() - fmt.Println(c.localizedString("Loadpoint_Setup")) - - for { - loadpointTitle := c.askValue(question{ - label: c.localizedString("Loadpoint_Title"), - defaultValue: c.localizedString("Loadpoint_DefaultTitle"), - required: true, - }) - loadpoint := loadpoint{ - Title: loadpointTitle, - Phases: 3, - MinCurrent: 6, - } - - charger, capabilities, err := c.configureDeviceCategory(DeviceCategoryCharger) - if err != nil { - break - } - chargerHasMeter := charger.ChargerHasMeter - - loadpoint.Charger = charger.Name - - if !chargerHasMeter { - if c.askYesNo(c.localizedString("Loadpoint_WallboxWOMeter")) { - chargeMeter, _, err := c.configureDeviceCategory(DeviceCategoryChargeMeter) - if err == nil { - loadpoint.ChargeMeter = chargeMeter.Name - chargerHasMeter = true - } - } - } - - vehicles := c.configuration.DevicesOfClass(templates.Vehicle) - if len(vehicles) > 0 { - fmt.Println() - if c.askYesNo(c.localizedString("Loadpoint_VehicleDisableAutoDetection")) { - if len(vehicles) == 1 { - loadpoint.Vehicle = vehicles[0].Name - } else { - fmt.Println() - - var vehicleTitles []string - for _, vehicle := range vehicles { - vehicleTitles = append(vehicleTitles, vehicle.Title) - } - - vehicleIndex, _ := c.askChoice(c.localizedString("Loadpoint_VehicleSelection"), vehicleTitles) - loadpoint.Vehicle = vehicles[vehicleIndex].Name - } - } - } - - var minValue int = 6 - if slices.Contains(capabilities, templates.CapabilityISO151182) { - minValue = 2 - } - - if c.advancedMode { - fmt.Println() - minAmperage := c.askValue(question{ - label: c.localizedString("Loadpoint_WallboxMinAmperage"), - valueType: templates.TypeInt, - minNumberValue: int64(minValue), - maxNumberValue: 32, - required: true, - }) - loadpoint.MinCurrent, _ = strconv.Atoi(minAmperage) - maxAmperage := c.askValue(question{ - label: c.localizedString("Loadpoint_WallboxMaxAmperage"), - valueType: templates.TypeInt, - minNumberValue: 6, - maxNumberValue: 32, - required: true, - }) - loadpoint.MaxCurrent, _ = strconv.Atoi(maxAmperage) - - if !chargerHasMeter { - phaseChoices := []string{"1", "2", "3"} - fmt.Println() - phaseIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxPhases"), phaseChoices) - loadpoint.Phases = phaseIndex + 1 - } - } else { - powerChoices := []string{ - c.localizedString("Loadpoint_WallboxPower36kW"), - c.localizedString("Loadpoint_WallboxPower11kW"), - c.localizedString("Loadpoint_WallboxPower22kW"), - c.localizedString("Loadpoint_WallboxPowerOther"), - } - fmt.Println() - powerIndex, _ := c.askChoice(c.localizedString("Loadpoint_WallboxMaxPower"), 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"), - valueType: templates.TypeInt, - 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"), phaseChoices) - loadpoint.Phases = phaseIndex + 1 - } - } - } - - fmt.Println() - loadpoint.Mode = c.askValue(question{valueType: templates.TypeChargeModes, excludeNone: true}) - c.configuration.AddLoadpoint(loadpoint) - - fmt.Println() - if !c.askYesNo(c.localizedString("Loadpoint_AddAnother")) { - break - } - } -} - -// configureSite asks site specific questions -func (c *CmdConfigure) configureSite() { - fmt.Println() - fmt.Println(c.localizedString("Site_Setup")) - - siteTitle := c.askValue(question{ - label: c.localizedString("Site_Title"), - defaultValue: c.localizedString("Site_DefaultTitle"), - required: true, - }) - c.configuration.config.Site.Title = siteTitle -} diff --git a/cmd/configure/survey.go b/cmd/configure/survey.go deleted file mode 100644 index 6055adfd2..000000000 --- a/cmd/configure/survey.go +++ /dev/null @@ -1,261 +0,0 @@ -package configure - -import ( - "errors" - "fmt" - "os" - "slices" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/AlecAivazis/survey/v2/terminal" - "github.com/evcc-io/evcc/api" - "github.com/evcc-io/evcc/util/templates" - stripmd "github.com/writeas/go-strip-markdown/v2" -) - -// surveyAskOne asks the user for input -func (c *CmdConfigure) surveyAskOne(p survey.Prompt, response any, opts ...survey.AskOpt) error { - opts = append(opts, survey.WithIcons(func(icons *survey.IconSet) { - icons.Question.Text = "" - })) - err := survey.AskOne(p, response, opts...) - if err != nil { - if err == terminal.InterruptErr { - fmt.Println(c.localizedString("Cancel")) - os.Exit(0) - } - fmt.Printf("%s %s\n", c.localizedString("InputError"), err) - } - - return err -} - -// askConfigFailureNextStep asks the user if he/she wants to select another device because the current does not work, or continue -func (c *CmdConfigure) askConfigFailureNextStep() bool { - fmt.Println() - return c.askYesNo(c.localizedString("TestingDevice_RepeatStep")) -} - -// select item from list -func (c *CmdConfigure) askSelection(message string, items []string) (string, int, error) { - prompt := &survey.Select{ - Message: message, - Options: items, - } - - var selection string - err := c.surveyAskOne(prompt, &selection) - - return selection, slices.Index(items, selection), err -} - -// selectItem selects item from list -func (c *CmdConfigure) selectItem(deviceCategory DeviceCategory) templates.Template { - var emptyItem templates.Template - emptyItem.SetTitle(c.localizedString("ItemNotPresent")) - - elements := c.fetchElements(deviceCategory) - elements = append(elements, emptyItem) - - var items []string - for _, item := range elements { - if item.Title() != "" { - items = append(items, item.Title()) - } - } - - text := fmt.Sprintf("%s %s %s:", c.localizedString("Choose"), DeviceCategories[deviceCategory].article, DeviceCategories[deviceCategory].title) - _, selected, err := c.askSelection(text, items) - if err != nil { - c.log.FATAL.Fatal(err) - } - - return elements[selected] -} - -// askChoice selects item from list -func (c *CmdConfigure) askChoice(label string, choices []string) (int, string) { - selection, index, err := c.askSelection(label, choices) - if err != nil { - c.log.FATAL.Fatal(err) - } - - return index, selection -} - -// askYesNo asks yes/no question, return true if yes is selected -func (c *CmdConfigure) askYesNo(label string) bool { - prompt := &survey.Confirm{ - Message: label, - } - - var confirmation bool - if err := c.surveyAskOne(prompt, &confirmation); err != nil { - c.log.FATAL.Fatal(err) - } - - return confirmation -} - -type question struct { - label, help string - defaultValue, exampleValue string - invalidValues []string - choice []string - valueType templates.ParamType - minNumberValue, maxNumberValue int64 - mask, required bool - excludeNone 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"), c.localizedString("Config_Yes")} - values := []string{"false", "true"} - - index, _ := c.askChoice(label, choices) - return values[index] -} - -func (c *CmdConfigure) askParam(p templates.Param) string { - var mask, required bool - if p.IsMasked() { - mask = p.Mask - } - if p.IsRequired() { - required = p.Required - } - - label := p.Description.String(c.lang) - if p.Unit != "" { - label = fmt.Sprintf("%s (%s)", label, p.Unit) - } - - return c.askValue(question{ - label: label, - valueType: p.Type, - choice: p.Choice, - mask: mask, - required: required, - }) -} - -// askValue asks for value input for a given question (template param) -func (c *CmdConfigure) askValue(q question) string { - if q.valueType == templates.TypeBool { - label := q.label - if q.help != "" { - helpDescription := stripmd.Strip(q.help) - fmt.Println("-------------------------------------------------") - fmt.Println(c.localizedString("Value_Help")) - fmt.Println(helpDescription) - fmt.Println("-------------------------------------------------") - } - - return c.askBoolValue(label) - } - - if q.valueType == templates.TypeChoice { - label := strings.TrimSpace(strings.Join([]string{q.label, c.localizedString("Value_Choice")}, " ")) - idx, _ := c.askChoice(label, q.choice) - return q.choice[idx] - } - - if q.valueType == templates.TypeChargeModes { - chargingModes := []string{string(api.ModeOff), string(api.ModeNow), string(api.ModeMinPV), string(api.ModePV)} - chargeModes := []string{ - c.localizedString("ChargeModeOff"), - c.localizedString("ChargeModeNow"), - c.localizedString("ChargeModeMinPV"), - c.localizedString("ChargeModePV"), - } - if !q.excludeNone { - chargingModes = append(chargingModes, "") - chargeModes = append(chargeModes, c.localizedString("ChargeModeNone")) - } - modeChoice, _ := c.askChoice(c.localizedString("ChargeMode_Question"), chargeModes) - return chargingModes[modeChoice] - } - - validate := func(val any) error { - value := val.(string) - if q.invalidValues != nil && slices.Contains(q.invalidValues, value) { - return errors.New(c.localizedString("ValueError_Used")) - } - - if q.choice != nil && !slices.Contains(q.choice, value) { - return errors.New(c.localizedString("ValueError_Invalid")) - } - - if q.required && len(value) == 0 { - return errors.New(c.localizedString("ValueError_Empty")) - } - - if !q.required && len(value) == 0 { - return nil - } - - if q.valueType == templates.TypeFloat { - _, err := strconv.ParseFloat(value, 64) - if err != nil { - return errors.New(c.localizedString("ValueError_Float")) - } - } - - if q.valueType == templates.TypeInt { - intValue, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return errors.New(c.localizedString("ValueError_Number")) - } - if q.minNumberValue != 0 && intValue < q.minNumberValue { - return errors.New(c.localizedString("ValueError_NumberLowerThanMin", localizeMap{"Min": q.minNumberValue})) - } - if q.maxNumberValue != 0 && intValue > q.maxNumberValue { - return errors.New(c.localizedString("ValueError_NumberBiggerThanMax", localizeMap{"Max": q.maxNumberValue})) - } - } - - if q.valueType == templates.TypeDuration { - _, err := time.ParseDuration(value) - if err != nil { - return errors.New(c.localizedString("ValueError_Duration")) - } - } - - return nil - } - - help := q.help - if q.required { - help += " (" + c.localizedString("Value_Required") + ")" - } else { - help += " (" + c.localizedString("Value_Optional") + ")" - } - if q.exampleValue != "" { - help += fmt.Sprintf(" ("+c.localizedString("Value_Sample")+": %s)", q.exampleValue) - } - - var prompt survey.Prompt - if q.mask { - prompt = &survey.Password{ - Message: q.label, - Help: help, - } - } else { - prompt = &survey.Input{ - Message: q.label, - Default: q.defaultValue, - Help: help, - } - } - - var input string - if err := c.surveyAskOne(prompt, &input, survey.WithValidator(validate)); err != nil { - c.log.FATAL.Fatal(err) - } - - return input -} diff --git a/cmd/configure/texts.go b/cmd/configure/texts.go deleted file mode 100644 index b84ea85fa..000000000 --- a/cmd/configure/texts.go +++ /dev/null @@ -1,42 +0,0 @@ -package configure - -import ( - "errors" - - "github.com/nicksnyder/go-i18n/v2/i18n" -) - -// setDefaultTexts sets default language specific texts -func (c *CmdConfigure) setDefaultTexts() { - c.errItemNotPresent = errors.New(c.localizedString("Error_ItemNotPresent")) - c.errDeviceNotValid = errors.New(c.localizedString("Error_DeviceNotValid")) - - c.updateDeviceCategoryTexts(DeviceCategoryCharger, "Category_ChargerTitle", "Category_ChargerArticle", "Category_ChargerAdditional") - c.updateDeviceCategoryTexts(DeviceCategoryGuidedSetup, "Category_SystemTitle", "Category_SystemArticle", "Category_SystemAdditional") - c.updateDeviceCategoryTexts(DeviceCategoryGridMeter, "Category_GridMeterTitle", "Category_GridMeterArticle", "Category_GridMeterAdditional") - c.updateDeviceCategoryTexts(DeviceCategoryPVMeter, "Category_PVMeterTitle", "Category_PVMeterArticle", "Category_PVMeterAdditional") - c.updateDeviceCategoryTexts(DeviceCategoryBatteryMeter, "Category_BatteryMeter", "Category_BatteryMeterArticle", "Category_BatteryMeterAdditional") - c.updateDeviceCategoryTexts(DeviceCategoryChargeMeter, "Category_ChargeMeterTitle", "Category_ChargeMeterArticle", "Category_ChargeMeterAdditional") - c.updateDeviceCategoryTexts(DeviceCategoryVehicle, "Category_VehicleTitle", "Category_VehicleArticle", "Category_VehicleAdditional") -} - -// updateDeviceCategoryTexts updates the texts for a device category -func (c *CmdConfigure) updateDeviceCategoryTexts(category DeviceCategory, title, article, additional string) { - data := DeviceCategories[category] - data.title = c.localizedString(title) - data.article = c.localizedString(article) - data.additional = c.localizedString(additional) - DeviceCategories[category] = data -} - -// localizedString is a helper for getting a localized string -func (c *CmdConfigure) localizedString(key string, data ...localizeMap) string { - var templateData localizeMap - if len(data) == 1 { - templateData = data[0] - } - return c.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: key, - TemplateData: templateData, - }) -} diff --git a/cmd/configure/types.go b/cmd/configure/types.go deleted file mode 100644 index 646b9642c..000000000 --- a/cmd/configure/types.go +++ /dev/null @@ -1,79 +0,0 @@ -package configure - -import ( - "github.com/evcc-io/evcc/util/templates" -) - -const ( - DefaultConfigFilename string = "evcc.yaml" -) - -type UsageChoice string - -type DeviceCategory string - -func (c DeviceCategory) String() string { - return string(c) -} - -const ( - DeviceCategoryCharger DeviceCategory = "wallbox" - DeviceCategoryGridMeter DeviceCategory = "grid" - DeviceCategoryPVMeter DeviceCategory = "pv" - DeviceCategoryBatteryMeter DeviceCategory = "battery" - DeviceCategoryChargeMeter DeviceCategory = "charge" - DeviceCategoryVehicle DeviceCategory = "vehicle" - DeviceCategoryGuidedSetup DeviceCategory = "guided" -) - -const ( - defaultNameCharger = "wallbox" - defaultNameGridMeter = "grid" - defaultNamePVMeter = "pv" - defaultNameBatteryMeter = "battery" - defaultNameChargeMeter = "charge" - defaultNameVehicle = "ev" -) - -type DeviceCategoryData struct { - title, article, additional string - class templates.Class - categoryFilter DeviceCategory - defaultName string -} - -var DeviceCategories = map[DeviceCategory]DeviceCategoryData{ - DeviceCategoryCharger: { - class: templates.Charger, - defaultName: defaultNameCharger, - }, - DeviceCategoryGuidedSetup: { - class: templates.Meter, - }, - DeviceCategoryGridMeter: { - class: templates.Meter, - categoryFilter: DeviceCategoryGridMeter, - defaultName: defaultNameGridMeter, - }, - DeviceCategoryPVMeter: { - class: templates.Meter, - categoryFilter: DeviceCategoryPVMeter, - defaultName: defaultNamePVMeter, - }, - DeviceCategoryBatteryMeter: { - class: templates.Meter, - categoryFilter: DeviceCategoryBatteryMeter, - defaultName: defaultNameBatteryMeter, - }, - DeviceCategoryVehicle: { - class: templates.Vehicle, - defaultName: defaultNameVehicle, - }, - DeviceCategoryChargeMeter: { - class: templates.Meter, - categoryFilter: DeviceCategoryChargeMeter, - defaultName: defaultNameChargeMeter, - }, -} - -type localizeMap map[string]any diff --git a/go.mod b/go.mod index ea66ec084..6c8e80c12 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.25.3 require ( dario.cat/mergo v1.0.2 github.com/AlecAivazis/survey/v2 v2.3.7 - github.com/BurntSushi/toml v1.6.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/PuerkitoBio/goquery v1.11.0 github.com/WulfgarW/sensonet v0.0.7 @@ -102,7 +101,6 @@ require ( github.com/traefik/yaegi v0.16.1 github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c github.com/volkszaehler/mbmd v0.0.0-20251230155010-64e026667a91 - github.com/writeas/go-strip-markdown/v2 v2.1.1 gitlab.com/bboehmke/sunny v0.16.0 go.bug.st/serial v1.6.4 go.uber.org/mock v0.6.0 @@ -121,6 +119,7 @@ require ( ) require ( + github.com/BurntSushi/toml v1.6.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/ahmetb/go-linq/v3 v3.2.0 // indirect diff --git a/go.sum b/go.sum index 9819909ba..0a03e01d2 100644 --- a/go.sum +++ b/go.sum @@ -768,8 +768,6 @@ github.com/volkszaehler/mbmd v0.0.0-20251230155010-64e026667a91 h1:G9VSLyJ4kTXoj github.com/volkszaehler/mbmd v0.0.0-20251230155010-64e026667a91/go.mod h1:ZkD3hHsxHxdD2KbqQrMXQd1veeFfggok+breXT+TqWQ= github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= -github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iLei22Ijc3q2e28= -github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=