diff --git a/charger/easee.go b/charger/easee.go index 62180a2e71..87ffe3869d 100644 --- a/charger/easee.go +++ b/charger/easee.go @@ -33,7 +33,7 @@ import ( "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/sponsor" "github.com/philippseith/signalr" - "github.com/thoas/go-funk" + "github.com/samber/lo" "golang.org/x/oauth2" ) @@ -109,7 +109,7 @@ func NewEasee(user, password, charger string) (*Easee, error) { } if len(chargers) != 1 { - return c, fmt.Errorf("cannot determine charger id, found: %v", funk.Map(chargers, func(c easee.Charger) string { return c.ID })) + return c, fmt.Errorf("cannot determine charger id, found: %v", lo.Map(chargers, func(c easee.Charger, _ int) string { return c.ID })) } c.charger = chargers[0].ID diff --git a/charger/easee/log.go b/charger/easee/log.go index c5a08ae9ec..785631a345 100644 --- a/charger/easee/log.go +++ b/charger/easee/log.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/philippseith/signalr" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" ) // Logger is a simple logger interface @@ -34,7 +34,7 @@ func (l *logger) Log(keyVals ...interface{}) error { } if i%2 == 0 { - if funk.Contains(skipped, v) { + if slices.Contains(skipped, v.(string)) { skip = true continue } diff --git a/charger/nrgble.go b/charger/nrgble.go index 5fe508994c..bb93c12fda 100644 --- a/charger/nrgble.go +++ b/charger/nrgble.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux package charger diff --git a/charger/template_test.go b/charger/template_test.go index 8c5d8225cf..9ef5e90d8e 100644 --- a/charger/template_test.go +++ b/charger/template_test.go @@ -5,7 +5,7 @@ import ( "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/test" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" ) var acceptable = []string{ @@ -44,7 +44,7 @@ func TestTemplates(t *testing.T) { if values[templates.ParamModbus] != nil { modbusChoices := tmpl.ModbusChoices() // we only test one modbus setup - if funk.ContainsString(modbusChoices, templates.ModbusChoiceTCPIP) { + if slices.Contains(modbusChoices, templates.ModbusChoiceTCPIP) { values[templates.ModbusKeyTCPIP] = true } else { values[templates.ModbusKeyRS485TCPIP] = true diff --git a/cmd/configure/helper.go b/cmd/configure/helper.go index b60bc3fdc8..f10c99f383 100644 --- a/cmd/configure/helper.go +++ b/cmd/configure/helper.go @@ -10,8 +10,8 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/sponsor" "github.com/evcc-io/evcc/util/templates" - "github.com/thoas/go-funk" stripmd "github.com/writeas/go-strip-markdown" + "golang.org/x/exp/slices" "gopkg.in/yaml.v3" ) @@ -128,7 +128,7 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa } func (c *CmdConfigure) processDeviceCapabilities(capabilitites []string) { - if funk.ContainsString(capabilitites, templates.CapabilitySMAHems) { + if slices.Contains(capabilitites, templates.CapabilitySMAHems) { c.capabilitySMAHems = true } } @@ -148,14 +148,14 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template } // check if sponsorship is required - if funk.ContainsString(templateItem.Requirements.EVCC, templates.RequirementSponsorship) && c.configuration.config.SponsorToken == "" { + 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 funk.ContainsString(templateItem.Requirements.EVCC, templates.RequirementMQTT) { + if slices.Contains(templateItem.Requirements.EVCC, templates.RequirementMQTT) { if c.configuration.config.MQTT == "" { mqttConfig, err := c.configureMQTT(templateItem) if err != nil { @@ -172,7 +172,7 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template } // check if we need to setup an EEBUS HEMS - if funk.ContainsString(templateItem.Requirements.EVCC, templates.RequirementEEBUS) { + if slices.Contains(templateItem.Requirements.EVCC, templates.RequirementEEBUS) { if c.configuration.config.EEBUS == "" { fmt.Println() fmt.Println("-- EEBUS -----------------------------------") @@ -219,7 +219,7 @@ func (c *CmdConfigure) processParamRequirements(param templates.Param) error { } // check if sponsorship is required - if funk.ContainsString(param.Requirements.EVCC, templates.RequirementSponsorship) && c.configuration.config.SponsorToken == "" { + if slices.Contains(param.Requirements.EVCC, templates.RequirementSponsorship) && c.configuration.config.SponsorToken == "" { if err := c.askSponsortoken(true, true); err != nil { return err } @@ -384,7 +384,7 @@ func (c *CmdConfigure) fetchElements(deviceCategory DeviceCategory) []templates. func (c *CmdConfigure) paramChoiceContains(params []templates.Param, name, filter string) bool { choices := c.paramChoiceValues(params, name) - return funk.ContainsString(choices, filter) + return slices.Contains(choices, filter) } // paramChoiceValues provides a list of possible values for a param choice @@ -533,7 +533,7 @@ func (c *CmdConfigure) processInputConfig(param templates.Param) string { } help := param.Help.String(c.lang) - if funk.ContainsString(param.Requirements.EVCC, templates.RequirementSponsorship) { + if slices.Contains(param.Requirements.EVCC, templates.RequirementSponsorship) { help = fmt.Sprintf("%s\n\n%s", help, c.localizedString("Requirements_Sponsorship_Feature_Title", nil)) } diff --git a/cmd/configure/main.go b/cmd/configure/main.go index 74b103c7d3..9c3922d908 100644 --- a/cmd/configure/main.go +++ b/cmd/configure/main.go @@ -16,7 +16,7 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/templates" "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" "golang.org/x/text/language" ) @@ -320,7 +320,7 @@ func (c *CmdConfigure) configureLoadpoints() { } var minValue int = 6 - if funk.ContainsString(capabilities, templates.CapabilityISO151182) { + if slices.Contains(capabilities, templates.CapabilityISO151182) { minValue = 2 } diff --git a/cmd/configure/survey.go b/cmd/configure/survey.go index 00e2b57fd6..1b97b33ae6 100644 --- a/cmd/configure/survey.go +++ b/cmd/configure/survey.go @@ -11,8 +11,8 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util/templates" - "github.com/thoas/go-funk" stripmd "github.com/writeas/go-strip-markdown" + "golang.org/x/exp/slices" ) // surveyAskOne asks the user for input @@ -49,7 +49,7 @@ func (c *CmdConfigure) askSelection(message string, items []string) (string, int var selection string err := c.surveyAskOne(prompt, &selection) - return selection, funk.IndexOf(items, selection), err + return selection, slices.Index(items, selection), err } // selectItem selects item from list @@ -153,11 +153,11 @@ func (c *CmdConfigure) askValue(q question) string { validate := func(val interface{}) error { value := val.(string) - if q.invalidValues != nil && funk.ContainsString(q.invalidValues, value) { + if q.invalidValues != nil && slices.Contains(q.invalidValues, value) { return errors.New(c.localizedString("ValueError_Used", nil)) } - if q.validValues != nil && !funk.ContainsString(q.validValues, value) { + if q.validValues != nil && !slices.Contains(q.validValues, value) { return errors.New(c.localizedString("ValueError_Invalid", nil)) } diff --git a/cmd/demoport.go b/cmd/demoport.go index 9c43567624..f6994770ad 100644 --- a/cmd/demoport.go +++ b/cmd/demoport.go @@ -1,5 +1,4 @@ //go:build !gokrazy -// +build !gokrazy package cmd diff --git a/cmd/demoport_gokrazy.go b/cmd/demoport_gokrazy.go index b90c53cda2..3ae82ea4a5 100644 --- a/cmd/demoport_gokrazy.go +++ b/cmd/demoport_gokrazy.go @@ -1,5 +1,4 @@ //go:build gokrazy -// +build gokrazy package cmd diff --git a/cmd/gokrazy.go b/cmd/gokrazy.go index 85d5890b61..c84a7207bd 100644 --- a/cmd/gokrazy.go +++ b/cmd/gokrazy.go @@ -1,5 +1,4 @@ //go:build gokrazy -// +build gokrazy package cmd diff --git a/cmd/health.go b/cmd/health.go index 68dffdc90e..3d9ca2e0c5 100644 --- a/cmd/health.go +++ b/cmd/health.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package cmd diff --git a/cmd/token.go b/cmd/token.go index 7e8c8fce79..b240320c64 100644 --- a/cmd/token.go +++ b/cmd/token.go @@ -6,9 +6,10 @@ import ( "github.com/evcc-io/evcc/server" "github.com/evcc-io/evcc/util" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" "golang.org/x/oauth2" ) @@ -37,15 +38,19 @@ func runToken(cmd *cobra.Command, args []string) { if len(conf.Vehicles) == 1 { vehicleConf = conf.Vehicles[0] } else if len(args) == 1 { - vehicleConf = funk.Find(conf.Vehicles, func(v qualifiedConfig) bool { + idx := slices.IndexFunc(conf.Vehicles, func(v qualifiedConfig) bool { return strings.EqualFold(v.Name, args[0]) - }).(qualifiedConfig) + }) + + if idx >= 0 { + vehicleConf = conf.Vehicles[idx] + } } if vehicleConf.Name == "" { - vehicles := funk.Map(conf.Vehicles, func(v qualifiedConfig) string { + vehicles := lo.Map(conf.Vehicles, func(v qualifiedConfig, _ int) string { return v.Name - }).([]string) + }) log.FATAL.Fatalf("vehicle not found, have %v", vehicles) } diff --git a/core/loadpoint.go b/core/loadpoint.go index e5b0ce7b0c..9fceb8d54e 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -16,7 +16,7 @@ import ( "github.com/evcc-io/evcc/provider" "github.com/evcc-io/evcc/push" "github.com/evcc-io/evcc/util" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" evbus "github.com/asaskevich/EventBus" "github.com/avast/retry-go/v3" @@ -760,7 +760,7 @@ func (lp *LoadPoint) identifyVehicle() { func (lp *LoadPoint) selectVehicleByID(id string) api.Vehicle { // find exact match for _, vehicle := range lp.vehicles { - if funk.ContainsString(vehicle.Identifiers(), id) { + if slices.Contains(vehicle.Identifiers(), id) { return vehicle } } diff --git a/detect/work.go b/detect/work.go index 8d05913498..d5d3d4d78a 100644 --- a/detect/work.go +++ b/detect/work.go @@ -39,7 +39,7 @@ func Work(log *util.Logger, num int, hosts []string) []tasks.Result { // log.INFO.Println( // "\n" + // strings.Join( - // funk.Map(taskList.tasks, func(t tasks.Task) string { + // lo.Map(taskList.tasks, func(t tasks.Task) string { // return fmt.Sprintf("task: %s\ttype: %s\tdepends: %s\n", t.ID, t.Type, t.Depends) // }).([]string), // "", diff --git a/go.mod b/go.mod index 2a9b358910..8edaef3cd1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/evcc-io/evcc -go 1.17 +go 1.18 require ( github.com/AlecAivazis/survey/v2 v2.3.2 @@ -52,6 +52,7 @@ require ( github.com/lorenzodonini/ocpp-go v0.15.0 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/manifoldco/promptui v0.9.0 + github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9 github.com/mitchellh/mapstructure v1.4.3 github.com/mlnoga/rct v0.1.2-0.20220320164346-9f2daa4d6734 github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a @@ -62,6 +63,7 @@ require ( github.com/philippseith/signalr v0.5.3-0.20211205201131-d57b5a34379a github.com/prometheus/client_golang v1.11.0 github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f + github.com/samber/lo v1.11.0 github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/spf13/cobra v1.4.0 @@ -69,12 +71,12 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.10.2-0.20220212101550-5986bd9c0c19 github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 - github.com/thoas/go-funk v0.9.1 github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c github.com/volkszaehler/mbmd v0.0.0-20220329124309-22084e041a33 github.com/writeas/go-strip-markdown v2.0.1+incompatible gitlab.com/bboehmke/sunny v0.15.1-0.20211022160056-2fba1c86ade6 - golang.org/x/net v0.0.0-20220325170049-de3da57026de + golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/text v0.3.7 google.golang.org/api v0.73.0 @@ -122,7 +124,6 @@ require ( github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/miekg/dns v1.1.45 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -146,6 +147,7 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/teivah/onecontext v1.3.0 // indirect + github.com/thoas/go-funk v0.9.1 github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.opencensus.io v0.23.0 // indirect diff --git a/go.sum b/go.sum index ad2d2b5e21..ff29412961 100644 --- a/go.sum +++ b/go.sum @@ -29,7 +29,6 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -123,7 +122,6 @@ github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuD github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -342,7 +340,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= @@ -459,7 +456,6 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb-client-go v1.4.0 h1:+KavOkwhLClHFfYcJMHHnTL5CZQhXJzOm5IKHI9BqJk= github.com/influxdata/influxdb-client-go v1.4.0/go.mod h1:S+oZsPivqbcP1S9ur+T+QqXvrYS3NCZeMQtBoH4D1dw= github.com/influxdata/influxdb-client-go/v2 v2.6.0 h1:bIOaGTgvvv1Na2hG+nIvqyv7PK2UiU2WrJN1ck1ykyM= github.com/influxdata/influxdb-client-go/v2 v2.6.0/go.mod h1:Y/0W1+TZir7ypoQZYd2IrnVOKB3Tq6oegAQeSVN/+EU= @@ -729,6 +725,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/samber/lo v1.11.0 h1:JfeYozXL1xfkhRUFOfH13ociyeiLSC/GRJjGKI668xM= +github.com/samber/lo v1.11.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= @@ -756,7 +754,6 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -832,7 +829,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= gitlab.com/bboehmke/sunny v0.15.1-0.20211022160056-2fba1c86ade6 h1:73pM5aQqFkQfmXyGf3+xeWWg98J03xtDIkA5TE37ERU= gitlab.com/bboehmke/sunny v0.15.1-0.20211022160056-2fba1c86ade6/go.mod h1:F5AIuL7kYteSJFR5E+YEocxIdpyCXmtDciFmMQVjP88= go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16/go.mod h1:iKV5yK9t+J5nG9O3uF6KYdPEz3dyfMyB15MN1rbQ8Qw= @@ -891,6 +887,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 h1:s/+U+w0teGzcoH2mdIlFQ6KfVKGaYpgyGdUefZrn9TU= +golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -980,14 +978,11 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1106,7 +1101,6 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211112164355-7580c6e521dc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1396,7 +1390,6 @@ gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWd gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= diff --git a/meter/discovergy.go b/meter/discovergy.go index 4e4bdd4701..f361433f40 100644 --- a/meter/discovergy.go +++ b/meter/discovergy.go @@ -10,7 +10,7 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/transport" - "github.com/thoas/go-funk" + "github.com/samber/lo" ) func init() { @@ -18,7 +18,7 @@ func init() { } type Discovergy struct { - dataG func() (interface{}, error) + dataG func() (discovergy.Reading, error) scale float64 } @@ -63,17 +63,17 @@ func NewDiscovergyFromConfig(other map[string]interface{}) (api.Meter, error) { } if meterID == "" { - return nil, fmt.Errorf("could not determine meter id: %v", funk.Map(meters, func(m discovergy.Meter) string { + return nil, fmt.Errorf("could not determine meter id: %v", lo.Map(meters, func(m discovergy.Meter, _ int) string { return m.FullSerialNumber })) } - dataG := provider.NewCached(func() (interface{}, error) { + dataG := provider.Cached(func() (discovergy.Reading, error) { var res discovergy.Reading uri := fmt.Sprintf("%s/last_reading?meterId=%s", discovergy.API, meterID) err := client.GetJSON(uri, &res) return res, err - }, cc.Cache).InterfaceGetter() + }, cc.Cache) m := &Discovergy{ dataG: dataG, @@ -91,18 +91,12 @@ var _ api.Meter = (*Discovergy)(nil) func (m *Discovergy) CurrentPower() (float64, error) { res, err := m.dataG() - if res, ok := res.(discovergy.Reading); err == nil && ok { - return m.scale * float64(res.Values.Power) / 1e3, nil - } - return 0, err + return m.scale * float64(res.Values.Power) / 1e3, err } var _ api.MeterEnergy = (*Discovergy)(nil) func (m *Discovergy) TotalEnergy() (float64, error) { res, err := m.dataG() - if res, ok := res.(discovergy.Reading); err == nil && ok { - return float64(res.Values.Energy) / 1e10, nil - } - return 0, err + return float64(res.Values.Energy) / 1e10, err } diff --git a/meter/lgpcs/lgpcs.go b/meter/lgpcs/lgpcs.go index 372d175c2e..7f63ec9efc 100644 --- a/meter/lgpcs/lgpcs.go +++ b/meter/lgpcs/lgpcs.go @@ -41,10 +41,10 @@ type EssData struct { type Com struct { *request.Helper - uri string // URI address of the LG ESS inverter - e.g. "https://192.168.1.28" - password string // registration number of the LG ESS Inverter - e.g. "DE2001..." - authKey string // auth_key returned during login and renewed with new login after expiration - cachedData func() (interface{}, error) + uri string // URI address of the LG ESS inverter - e.g. "https://192.168.1.28" + password string // registration number of the LG ESS Inverter - e.g. "DE2001..." + authKey string // auth_key returned during login and renewed with new login after expiration + dataG func() (EssData, error) } var ( @@ -70,9 +70,7 @@ func GetInstance(uri, password string, cache time.Duration) (*Com, error) { // caches the data access for the "cache" time duration // sends a new request to the pcs if the cache is expired and Data() requested - instance.cachedData = provider.NewCached(func() (interface{}, error) { - return instance.refreshData() - }, cache).InterfaceGetter() + instance.dataG = provider.Cached(instance.refreshData, cache) // do first login if no authKey exists and uri and password exist if instance.authKey == "" && instance.uri != "" && instance.password != "" { @@ -127,8 +125,7 @@ func (m *Com) Login() error { // Data gives the data read from the pcs. func (m *Com) Data() (EssData, error) { - res, err := m.cachedData() - return res.(EssData), err + return m.dataG() } // refreshData reads data from lgess pcs. Tries to re-login if "405" auth_key expired is returned diff --git a/meter/template_test.go b/meter/template_test.go index 29a85546c1..cb7f6470ea 100644 --- a/meter/template_test.go +++ b/meter/template_test.go @@ -5,7 +5,7 @@ import ( "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/test" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" ) var acceptable = []string{ @@ -42,7 +42,7 @@ func TestTemplates(t *testing.T) { if values[templates.ParamModbus] != nil { modbusChoices := tmpl.ModbusChoices() // we only test one modbus setup - if funk.ContainsString(modbusChoices, templates.ModbusChoiceTCPIP) { + if slices.Contains(modbusChoices, templates.ModbusChoiceTCPIP) { values[templates.ModbusKeyTCPIP] = true } else { values[templates.ModbusKeyRS485TCPIP] = true diff --git a/provider/cache.go b/provider/cache.go index 61c516e58d..0c0da6c09b 100644 --- a/provider/cache.go +++ b/provider/cache.go @@ -22,176 +22,60 @@ func ResetCached() { bus.Publish(reset) } -// Cached wraps a getter with a cache -type Cached struct { +// cached wraps a getter with a cache +type cached[T any] struct { mux sync.Mutex clock clock.Clock updated time.Time cache time.Duration - getter interface{} - val interface{} + g func() (T, error) + val T err error } -// NewCached wraps a getter with a cache -func NewCached(getter interface{}, cache time.Duration) *Cached { - c := &Cached{ - clock: clock.New(), - getter: getter, - cache: cache, - } - - _ = bus.Subscribe(reset, c.reset) - - return c -} - -func (c *Cached) reset() { - c.mux.Lock() - c.updated = time.Time{} - c.mux.Unlock() -} - -func (c *Cached) mustUpdate() bool { - return c.clock.Since(c.updated) > c.cache || errors.Is(c.err, api.ErrMustRetry) -} - -// FloatGetter gets float value -func (c *Cached) FloatGetter() func() (float64, error) { - g, ok := c.getter.(func() (float64, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) - } - - return func() (float64, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } - - return c.val.(float64), c.err - } +// Cached wraps a getter with a cache +func Cached[T any](g func() (T, error), cache time.Duration) func() (T, error) { + c := ResettableCached(g, cache) + _ = bus.Subscribe(reset, c.Reset) + return c.Get } -// IntGetter gets int value -func (c *Cached) IntGetter() func() (int64, error) { - g, ok := c.getter.(func() (int64, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) - } - - return func() (int64, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } - - return c.val.(int64), c.err - } +// Cachable is the interface for a resettable cache +type Cachable[T any] interface { + Get() (T, error) + Reset() } -// StringGetter gets string value -func (c *Cached) StringGetter() func() (string, error) { - g, ok := c.getter.(func() (string, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) - } - - return func() (string, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } +var _ Cachable[int64] = (*cached[int64])(nil) - return c.val.(string), c.err +// ResettableCached wraps a getter with a cache. It returns a `Cachable`. +// Instead of the cached getter, the `Get()` and `Reset()` methods are exposed. +func ResettableCached[T any](g func() (T, error), cache time.Duration) *cached[T] { + return &cached[T]{ + clock: clock.New(), + cache: cache, + g: g, } } -// BoolGetter gets bool value -func (c *Cached) BoolGetter() func() (bool, error) { - g, ok := c.getter.(func() (bool, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) - } - - return func() (bool, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } - - return c.val.(bool), c.err - } -} +func (c *cached[T]) Get() (T, error) { + c.mux.Lock() + defer c.mux.Unlock() -// DurationGetter gets time.Duration value -func (c *Cached) DurationGetter() func() (time.Duration, error) { - g, ok := c.getter.(func() (time.Duration, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) + if c.mustUpdate() { + c.val, c.err = c.g() + c.updated = c.clock.Now() } - return func() (time.Duration, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } - - return c.val.(time.Duration), c.err - } + return c.val, c.err } -// TimeGetter gets time.Time value -func (c *Cached) TimeGetter() func() (time.Time, error) { - g, ok := c.getter.(func() (time.Time, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) - } - - return func() (time.Time, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } - - return c.val.(time.Time), c.err - } +func (c *cached[T]) Reset() { + c.mux.Lock() + c.updated = time.Time{} + c.mux.Unlock() } -// InterfaceGetter gets interface value -func (c *Cached) InterfaceGetter() func() (interface{}, error) { - g, ok := c.getter.(func() (interface{}, error)) - if !ok { - log.FATAL.Fatalf("invalid type: %T", c.getter) - } - - return func() (interface{}, error) { - c.mux.Lock() - defer c.mux.Unlock() - - if c.mustUpdate() { - c.val, c.err = g() - c.updated = c.clock.Now() - } - - return c.val, c.err - } +func (c *cached[T]) mustUpdate() bool { + return c.clock.Since(c.updated) > c.cache || errors.Is(c.err, api.ErrMustRetry) } diff --git a/provider/cache_test.go b/provider/cache_test.go index d12fb79e9a..49e26855f9 100644 --- a/provider/cache_test.go +++ b/provider/cache_test.go @@ -27,16 +27,15 @@ func TestCachedGetter(t *testing.T) { } duration := time.Second - c := NewCached(g, duration) + c := ResettableCached(g, duration) clock := clock.NewMock() c.clock = clock - getter := c.FloatGetter() expect := func(s struct { f float64 e error }) { - f, e := getter() + f, e := c.Get() if f != s.f || e != s.e { t.Errorf("unexpected cache value: %f, %v\n", f, e) } @@ -59,12 +58,12 @@ func TestCacheReset(t *testing.T) { return i, nil } - c := NewCached(g, 10*time.Minute) + c := ResettableCached(g, 10*time.Minute) clock := clock.NewMock() c.clock = clock test := func(exp int64) { - v, _ := c.IntGetter()() + v, _ := c.Get() if exp != v { t.Errorf("expected %d, got %d", exp, v) } @@ -72,7 +71,7 @@ func TestCacheReset(t *testing.T) { test(1) test(1) - ResetCached() + c.Reset() test(2) test(2) clock.Add(10*time.Minute + 1) diff --git a/server/http_live.go b/server/http_live.go index 7ca18755ca..6d9fc478a8 100644 --- a/server/http_live.go +++ b/server/http_live.go @@ -1,5 +1,4 @@ //go:build !release -// +build !release package server diff --git a/server/uds.go b/server/uds.go index f775057af7..dc81903821 100644 --- a/server/uds.go +++ b/server/uds.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package server diff --git a/server/uds_windows.go b/server/uds_windows.go index f4c4909329..c05a969da8 100644 --- a/server/uds_windows.go +++ b/server/uds_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows package server diff --git a/server/updater/gokrazy.go b/server/updater/gokrazy.go index 7eb33f2580..62b16df7f5 100644 --- a/server/updater/gokrazy.go +++ b/server/updater/gokrazy.go @@ -1,5 +1,4 @@ //go:build gokrazy -// +build gokrazy package updater diff --git a/server/updater/run.go b/server/updater/run.go index da464c9921..df4ea71fb3 100644 --- a/server/updater/run.go +++ b/server/updater/run.go @@ -1,5 +1,4 @@ //go:build !gokrazy -// +build !gokrazy package updater diff --git a/server/updater/run_gokrazy.go b/server/updater/run_gokrazy.go index cf0760e654..49ea3b219d 100644 --- a/server/updater/run_gokrazy.go +++ b/server/updater/run_gokrazy.go @@ -1,5 +1,4 @@ //go:build gokrazy -// +build gokrazy package updater diff --git a/tools.go b/tools.go index c1d143b892..e8726aaf5e 100644 --- a/tools.go +++ b/tools.go @@ -1,5 +1,4 @@ //go:build tools -// +build tools package main diff --git a/util/goversion/constraint.go b/util/goversion/constraint.go index 7aee559a94..e487d32341 100644 --- a/util/goversion/constraint.go +++ b/util/goversion/constraint.go @@ -1,8 +1,8 @@ // Approach from https://github.com/theckman/goconstraint/ // Package goversion should only be used as a blank import. If imported, it -// will only compile if the Go runtime version is >= 1.17. +// will only compile if the Go runtime version is >= 1.18. package goversion -// This will fail to compile if the Go runtime version isn't >= 1.17. -var _ = __EVCC_REQUIRES_GO_VERSION_1_17__ +// This will fail to compile if the Go runtime version isn't >= 1.18. +var _ = __EVCC_REQUIRES_GO_VERSION_1_18__ diff --git a/util/goversion/version.go b/util/goversion/version.go index 637a80298e..7554eb83d4 100644 --- a/util/goversion/version.go +++ b/util/goversion/version.go @@ -1,6 +1,5 @@ -//go:build go1.17 -// +build go1.17 +//go:build go1.18 package goversion -const __EVCC_REQUIRES_GO_VERSION_1_17__ = uint8(0) +const __EVCC_REQUIRES_GO_VERSION_1_18__ = uint8(0) diff --git a/util/templates/template.go b/util/templates/template.go index fee07495b0..6b1e38850c 100644 --- a/util/templates/template.go +++ b/util/templates/template.go @@ -9,7 +9,7 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/evcc-io/evcc/util" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" ) // Template describes is a proxy device for use with cli and automated testing @@ -27,7 +27,7 @@ type Template struct { // UpdateParamWithDefaults adds default values to specific param name entries func (t *Template) UpdateParamsWithDefaults() error { for i, p := range t.Params { - if p.ValueType == "" || (p.ValueType != "" && !funk.ContainsString(ValidParamValueTypes, p.ValueType)) { + if p.ValueType == "" || (p.ValueType != "" && !slices.Contains(ValidParamValueTypes, p.ValueType)) { t.Params[i].ValueType = ParamValueTypeString } @@ -42,13 +42,13 @@ func (t *Template) UpdateParamsWithDefaults() error { // validate the template (only rudimentary for now) func (t *Template) Validate() error { for _, c := range t.Capabilities { - if !funk.ContainsString(ValidCapabilities, c) { + if !slices.Contains(ValidCapabilities, c) { return fmt.Errorf("invalid capability '%s' in template %s", c, t.Template) } } for _, r := range t.Requirements.EVCC { - if !funk.ContainsString(ValidRequirements, r) { + if !slices.Contains(ValidRequirements, r) { return fmt.Errorf("invalid requirement '%s' in template %s", r, t.Template) } } @@ -57,24 +57,24 @@ func (t *Template) Validate() error { switch p.Name { case ParamUsage: for _, c := range p.Choice { - if !funk.ContainsString(ValidUsageChoices, c) { + if !slices.Contains(ValidUsageChoices, c) { return fmt.Errorf("invalid usage choice '%s' in template %s", c, t.Template) } } case ParamModbus: for _, c := range p.Choice { - if !funk.ContainsString(ValidModbusChoices, c) { + if !slices.Contains(ValidModbusChoices, c) { return fmt.Errorf("invalid modbus choice '%s' in template %s", c, t.Template) } } } - if p.ValueType != "" && !funk.ContainsString(ValidParamValueTypes, p.ValueType) { + if p.ValueType != "" && !slices.Contains(ValidParamValueTypes, p.ValueType) { return fmt.Errorf("invalid value type '%s' in template %s", p.ValueType, t.Template) } for _, d := range p.Dependencies { - if !funk.ContainsString(ValidDependencies, d.Check) { + if !slices.Contains(ValidDependencies, d.Check) { return fmt.Errorf("invalid dependency check '%s' in template %s", d.Check, t.Template) } } @@ -303,7 +303,7 @@ func (t *Template) RenderResult(renderMode string, other map[string]interface{}) for item, p := range values { i, _ := t.ParamByName(item) - if i == -1 && !funk.ContainsString(predefinedTemplateProperties, item) { + if i == -1 && !slices.Contains(predefinedTemplateProperties, item) { return nil, values, fmt.Errorf("invalid element 'name: %s'", item) } diff --git a/vehicle/bluelink.go b/vehicle/bluelink.go index e87984a9a9..e91e217e5e 100644 --- a/vehicle/bluelink.go +++ b/vehicle/bluelink.go @@ -1,8 +1,6 @@ package vehicle import ( - "fmt" - "strings" "time" "github.com/evcc-io/evcc/api" @@ -77,27 +75,17 @@ func newBluelinkFromConfig(brand string, other map[string]interface{}, settings api := bluelink.NewAPI(log, settings.URI, identity, cc.Cache) - vehicles, err := api.Vehicles() + _, vehicle, err := ensureVehicleWithFeature( + cc.VIN, api.Vehicles, + func(v bluelink.Vehicle) (string, bluelink.Vehicle) { + return v.VIN, v + }, + ) + if err != nil { return nil, err } - var vehicle bluelink.Vehicle - if cc.VIN == "" && len(vehicles) == 1 { - vehicle = vehicles[0] - log.DEBUG.Printf("found vehicle: %v", vehicle.VIN) - } else { - for _, v := range vehicles { - if v.VIN == strings.ToUpper(cc.VIN) { - vehicle = v - } - } - } - - if len(vehicle.VIN) == 0 { - return nil, fmt.Errorf("cannot find vehicle: %v", vehicles) - } - v := &Bluelink{ embed: &cc.embed, Provider: bluelink.NewProvider(api, vehicle.VehicleID, cc.Expiry, cc.Cache), diff --git a/vehicle/bluelink/provider.go b/vehicle/bluelink/provider.go index 7e64a2fff8..58034e6573 100644 --- a/vehicle/bluelink/provider.go +++ b/vehicle/bluelink/provider.go @@ -12,8 +12,8 @@ const refreshTimeout = 2 * time.Minute // Provider implements the Kia/Hyundai bluelink api. // Based on https://github.com/Hacksore/bluelinky. type Provider struct { - statusG func() (interface{}, error) - statusLG func() (interface{}, error) + statusG func() (VehicleStatus, error) + statusLG func() (StatusLatestResponse, error) refreshG func() (StatusResponse, error) expiry time.Duration refreshTime time.Time @@ -28,15 +28,15 @@ func NewProvider(api *API, vid string, expiry, cache time.Duration) *Provider { expiry: expiry, } - v.statusG = provider.NewCached(func() (interface{}, error) { + v.statusG = provider.Cached(func() (VehicleStatus, error) { return v.status( func() (StatusLatestResponse, error) { return api.StatusLatest(vid) }, ) - }, cache).InterfaceGetter() + }, cache) - v.statusLG = provider.NewCached(func() (interface{}, error) { + v.statusLG = provider.Cached(func() (StatusLatestResponse, error) { return api.StatusLatest(vid) - }, cache).InterfaceGetter() + }, cache) return v } @@ -97,7 +97,7 @@ var _ api.Battery = (*Provider)(nil) func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { return res.EvStatus.BatteryStatus, nil } @@ -111,7 +111,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { res, err := v.statusG() status := api.StatusNone - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { status = api.StatusA if res.EvStatus.BatteryPlugin > 0 { status = api.StatusB @@ -130,7 +130,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { remaining := res.EvStatus.RemainTime2.Atc.Value if remaining == 0 { @@ -150,7 +150,7 @@ var _ api.VehicleRange = (*Provider)(nil) func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { if dist := res.EvStatus.DrvDistance; len(dist) == 1 { return int64(dist[0].RangeByFuel.EvModeRange.Value), nil } @@ -166,10 +166,5 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusLG() - - if res, ok := res.(StatusLatestResponse); err == nil && ok { - return res.ResMsg.VehicleStatusInfo.Odometer.Value, nil - } - - return 0, err + return res.ResMsg.VehicleStatusInfo.Odometer.Value, err } diff --git a/vehicle/bmw/provider.go b/vehicle/bmw/provider.go index a9150e944c..6ab70e6fef 100644 --- a/vehicle/bmw/provider.go +++ b/vehicle/bmw/provider.go @@ -9,15 +9,15 @@ import ( // Provider implements the evcc vehicle api type Provider struct { - statusG func() (interface{}, error) + statusG func() (VehicleStatus, error) } // NewProvider provides the evcc vehicle api provider func NewProvider(api *API, vin string, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (VehicleStatus, error) { return api.Status(vin) - }, cache).InterfaceGetter(), + }, cache), } return impl } @@ -27,7 +27,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { if cs := res.Properties.ChargingState; cs != nil { return float64(cs.ChargePercentage), nil } @@ -43,7 +43,7 @@ var _ api.ChargeState = (*Provider)(nil) // Status implements the api.ChargeState interface func (v *Provider) Status() (api.ChargeStatus, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { if cs := res.Properties.ChargingState; cs != nil { status := api.StatusA // disconnected @@ -68,7 +68,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { // // FinishTime implements the api.VehicleFinishTimer interface // func (v *Provider) FinishTime() (time.Time, error) { // res, err := v.statusG() -// if res, ok := res.(StatusResponse); err == nil && ok { +// err == nil { // ctr := res.VehicleStatus.ChargingTimeRemaining // return time.Now().Add(time.Duration(ctr) * time.Minute), err // } @@ -81,7 +81,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { if er := res.Properties.ElectricRange; er != nil { return int64(er.Distance.Value), nil } @@ -97,7 +97,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(VehicleStatus); err == nil && ok { + if err == nil { if cm := res.Status.CurrentMileage; cm != nil { return float64(cm.Mileage), nil } diff --git a/vehicle/carwings.go b/vehicle/carwings.go index 5782468cd7..39d72e6bf1 100644 --- a/vehicle/carwings.go +++ b/vehicle/carwings.go @@ -26,8 +26,8 @@ type CarWings struct { *embed user, password string session *carwings.Session - statusG func() (interface{}, error) - climateG func() (interface{}, error) + statusG func() (carwings.BatteryStatus, error) + climateG func() (carwings.ClimateStatus, error) refreshKey string refreshTime time.Time } @@ -91,13 +91,8 @@ func NewCarWingsFromConfig(other map[string]interface{}) (api.Vehicle, error) { return nil, fmt.Errorf("login failed: %w", err) } - v.statusG = provider.NewCached(func() (interface{}, error) { - return v.status() - }, cc.Cache).InterfaceGetter() - - v.climateG = provider.NewCached(func() (interface{}, error) { - return v.session.ClimateControlStatus() - }, cc.Cache).InterfaceGetter() + v.statusG = provider.Cached(v.status, cc.Cache) + v.climateG = provider.Cached(v.session.ClimateControlStatus, cc.Cache) return v, nil } @@ -112,11 +107,11 @@ func (v *CarWings) connectIfRequired(err error) error { return err } -func (v *CarWings) status() (interface{}, error) { +func (v *CarWings) status() (carwings.BatteryStatus, error) { // api result is stale if v.refreshKey != "" { if err := v.refreshResult(); err != nil { - return nil, err + return *new(carwings.BatteryStatus), err } } @@ -125,7 +120,7 @@ func (v *CarWings) status() (interface{}, error) { if err == nil { if elapsed := time.Since(bs.Timestamp); elapsed > carwingsStatusExpiry { if err = v.refreshRequest(); err != nil { - return nil, err + return *new(carwings.BatteryStatus), err } err = api.ErrMustRetry @@ -182,7 +177,7 @@ func (v *CarWings) refreshRequest() (err error) { // SoC implements the api.Vehicle interface func (v *CarWings) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(carwings.BatteryStatus); err == nil && ok { + if err == nil { return float64(res.StateOfCharge), nil } @@ -196,7 +191,7 @@ func (v *CarWings) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(carwings.BatteryStatus); err == nil && ok { + if err == nil { if res.PluginState == carwings.Connected { status = api.StatusB // connected, not charging } @@ -213,7 +208,7 @@ var _ api.VehicleRange = (*CarWings)(nil) // Range implements the api.VehicleRange interface func (v *CarWings) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(carwings.BatteryStatus); err == nil && ok { + if err == nil { return int64(res.CruisingRangeACOn) / 1000, nil } @@ -226,7 +221,7 @@ var _ api.VehicleClimater = (*CarWings)(nil) func (v *CarWings) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { res, err := v.climateG() - if res, ok := res.(carwings.ClimateStatus); err == nil && ok { + if err == nil { active = res.Running targetTemp = float64(res.Temperature) outsideTemp = targetTemp diff --git a/vehicle/cloud.go b/vehicle/cloud.go index cba81b0446..9c27fe0adc 100644 --- a/vehicle/cloud.go +++ b/vehicle/cloud.go @@ -66,7 +66,7 @@ func NewCloudFromConfig(other map[string]interface{}) (api.Vehicle, error) { err = v.prepareVehicle() } - v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() + v.chargeStateG = provider.Cached(v.chargeState, cc.Cache) return v, err } diff --git a/vehicle/fiat/provider.go b/vehicle/fiat/provider.go index 734c29c3ee..19267ae574 100644 --- a/vehicle/fiat/provider.go +++ b/vehicle/fiat/provider.go @@ -11,8 +11,8 @@ import ( const refreshTimeout = 2 * time.Minute type Provider struct { - statusG func() (interface{}, error) - locationG func() (interface{}, error) + statusG func() (StatusResponse, error) + locationG func() (LocationResponse, error) action func(action, cmd string) (ActionResponse, error) expiry time.Duration refreshTime time.Time @@ -20,12 +20,12 @@ type Provider struct { func NewProvider(api *API, vin, pin string, expiry, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (StatusResponse, error) { return api.Status(vin) - }, cache).InterfaceGetter(), - locationG: provider.NewCached(func() (interface{}, error) { + }, cache), + locationG: provider.Cached(func() (LocationResponse, error) { return api.Location(vin) - }, cache).InterfaceGetter(), + }, cache), action: func(action, cmd string) (ActionResponse, error) { return api.Action(vin, pin, action, cmd) }, @@ -34,11 +34,11 @@ func NewProvider(api *API, vin, pin string, expiry, cache time.Duration) *Provid // use pin for refreshing if pin != "" { - impl.statusG = provider.NewCached(func() (interface{}, error) { + impl.statusG = provider.Cached(func() (StatusResponse, error) { return impl.status( func() (StatusResponse, error) { return api.Status(vin) }, ) - }, cache).InterfaceGetter() + }, cache) } return impl @@ -88,7 +88,7 @@ func (v *Provider) status(statusG func() (StatusResponse, error)) (StatusRespons // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if res.EvInfo == nil { return 0, api.ErrNotAvailable } @@ -104,7 +104,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if res.EvInfo == nil { return 0, api.ErrNotAvailable } @@ -120,7 +120,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { return float64(res.VehicleInfo.Odometer.Odometer.Value), nil } @@ -134,7 +134,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if res.EvInfo == nil { return api.StatusNone, api.ErrNotAvailable } @@ -155,7 +155,7 @@ var _ api.VehiclePosition = (*Provider)(nil) // Position implements the api.VehiclePosition interface func (v *Provider) Position() (float64, float64, error) { res, err := v.locationG() - if res, ok := res.(LocationResponse); err == nil && ok { + if err == nil { return res.Latitude, res.Longitude, nil } diff --git a/vehicle/ford/provider.go b/vehicle/ford/provider.go index 1e965c36f6..2e74b58b90 100644 --- a/vehicle/ford/provider.go +++ b/vehicle/ford/provider.go @@ -10,7 +10,7 @@ import ( const refreshTimeout = time.Minute type Provider struct { - statusG func() (interface{}, error) + statusG func() (StatusResponse, error) expiry time.Duration refreshTime time.Time refreshId string @@ -22,13 +22,13 @@ func NewProvider(api *API, vin string, expiry, cache time.Duration) *Provider { expiry: expiry, } - impl.statusG = provider.NewCached(func() (interface{}, error) { + impl.statusG = provider.Cached(func() (StatusResponse, error) { return impl.status( func() (StatusResponse, error) { return api.Status(vin) }, func(id string) (StatusResponse, error) { return api.RefreshResult(vin, id) }, func() (string, error) { return api.RefreshRequest(vin) }, ) - }, cache).InterfaceGetter() + }, cache) impl.wakeup = func() error { return api.WakeUp(vin) } @@ -78,7 +78,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Battery interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { return res.VehicleStatus.BatteryFillLevel.Value, nil } @@ -90,7 +90,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { return int64(res.VehicleStatus.ElVehDTE.Value), nil } @@ -104,7 +104,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if res.VehicleStatus.PlugStatus.Value == 1 { status = api.StatusB // connected, not charging } @@ -121,11 +121,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { - return res.VehicleStatus.Odometer.Value, nil - } - - return 0, err + return res.VehicleStatus.Odometer.Value, err } var _ api.VehiclePosition = (*Provider)(nil) @@ -133,11 +129,7 @@ var _ api.VehiclePosition = (*Provider)(nil) // Position implements the api.VehiclePosition interface func (v *Provider) Position() (float64, float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { - return res.VehicleStatus.Gps.Latitude, res.VehicleStatus.Gps.Longitude, nil - } - - return 0, 0, err + return res.VehicleStatus.Gps.Latitude, res.VehicleStatus.Gps.Longitude, err } var _ api.AlarmClock = (*Provider)(nil) diff --git a/vehicle/helper.go b/vehicle/helper.go index 40bbb4fe74..ad5ae957b8 100644 --- a/vehicle/helper.go +++ b/vehicle/helper.go @@ -3,44 +3,46 @@ package vehicle import ( "fmt" "strings" - - "github.com/thoas/go-funk" ) -// findVehicle finds the first vehicle in the list of VINs or returns an error -func findVehicle(vehicles []string, err error) (string, error) { - if err != nil { - return "", fmt.Errorf("cannot get vehicles: %w", err) - } - - if len(vehicles) != 1 { - return "", fmt.Errorf("cannot find vehicle: %v", vehicles) - } - - vin := strings.TrimSpace(vehicles[0]) - if vin == "" { - return "", fmt.Errorf("cannot find vehicle: %v", vehicles) - } +// ensureVehicleWithFeature extracts VIN from list of VINs returned from `list`` function +func ensureVehicle(vin string, list func() ([]string, error)) (string, error) { + vin, _, err := ensureVehicleWithFeature(vin, list, func(v string) (string, string) { + return v, "" + }) - return vin, nil + return vin, err } -// ensureVehicle ensures that the vehicle is available on the api and returns the VIN -func ensureVehicle(vin string, fun func() ([]string, error)) (string, error) { - vehicles, err := fun() +// ensureVehicleWithFeature extracts VIN and feature from list of vehicles of type V returned from `list`` function +func ensureVehicleWithFeature[Vehicle, Feature any]( + vin string, + list func() ([]Vehicle, error), + extract func(Vehicle) (string, Feature), +) (string, Feature, error) { + vehicles, err := list() if err != nil { - return "", fmt.Errorf("cannot get vehicles: %w", err) + return "", *new(Feature), fmt.Errorf("cannot get vehicles: %w", err) } if vin = strings.ToUpper(vin); vin != "" { - // vin defined but doesn't exist - if !funk.ContainsString(vehicles, vin) { - err = fmt.Errorf("cannot find vehicle: %s", vin) + for _, vehicle := range vehicles { + if v, res := extract(vehicle); v == vin { + return v, res, nil + } } + + // vin defined but doesn't exist + err = fmt.Errorf("cannot find vehicle: %s", vin) } else { // vin empty - vin, err = findVehicle(vehicles, nil) + if len(vehicles) == 1 { + vin, res := extract(vehicles[0]) + return vin, res, nil + } + + err = fmt.Errorf("cannot find vehicle: %v", vehicles) } - return vin, err + return "", *new(Feature), err } diff --git a/vehicle/jlr/provider.go b/vehicle/jlr/provider.go index 5b4f792ccb..3d9f087ab9 100644 --- a/vehicle/jlr/provider.go +++ b/vehicle/jlr/provider.go @@ -8,19 +8,19 @@ import ( ) type Provider struct { - statusG func() (interface{}, error) - positionG func() (interface{}, error) + statusG func() (StatusResponse, error) + positionG func() (PositionResponse, error) actionS func(bool) error } func NewProvider(api *API, vin, user string, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (StatusResponse, error) { return api.Status(vin) - }, cache).InterfaceGetter(), - positionG: provider.NewCached(func() (interface{}, error) { + }, cache), + positionG: provider.Cached(func() (PositionResponse, error) { return api.Position(vin) - }, cache).InterfaceGetter(), + }, cache), actionS: func(start bool) error { return api.ChargeAction(vin, user, start) }, @@ -35,7 +35,7 @@ var _ api.Battery = (*Provider)(nil) func (v *Provider) SoC() (float64, error) { var val float64 res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { val, err = res.VehicleStatus.EvStatus.FloatVal("EV_STATE_OF_CHARGE") } @@ -48,7 +48,7 @@ var _ api.VehicleRange = (*Provider)(nil) func (v *Provider) Range() (int64, error) { var val int64 res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { val, err = res.VehicleStatus.EvStatus.IntVal("EV_RANGE_ON_BATTERY_KM") } @@ -62,7 +62,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if s, err := res.VehicleStatus.EvStatus.StringVal("EV_CHARGING_STATUS"); err == nil { switch s { case "NOTCONNECTED": @@ -83,7 +83,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { i, err := res.VehicleStatus.CoreStatus.IntVal("EV_MINUTES_TO_FULLY_CHARGED") return time.Now().Add(time.Duration(i) * time.Minute), err } @@ -97,7 +97,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) func (v *Provider) Odometer() (float64, error) { var val float64 res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { val, err = res.VehicleStatus.CoreStatus.FloatVal("ODOMETER") } @@ -109,7 +109,7 @@ var _ api.VehiclePosition = (*Provider)(nil) // Position implements the api.VehiclePosition interface func (v *Provider) Position() (float64, float64, error) { res, err := v.positionG() - if res, ok := res.(PositionResponse); err == nil && ok { + if err == nil { return res.Position.Latitude, res.Position.Longitude, nil } diff --git a/vehicle/mercedes.go b/vehicle/mercedes.go index 1c3db1f039..73610b73c3 100644 --- a/vehicle/mercedes.go +++ b/vehicle/mercedes.go @@ -14,7 +14,6 @@ import ( type Mercedes struct { *embed *mercedes.Provider - *mercedes.Identity } func init() { @@ -65,7 +64,6 @@ func NewMercedesFromConfig(other map[string]interface{}) (api.Vehicle, error) { v := &Mercedes{ embed: &cc.embed, - Identity: identity, Provider: mercedes.NewProvider(api, strings.ToUpper(cc.VIN), cc.Cache), } diff --git a/vehicle/mercedes/provider.go b/vehicle/mercedes/provider.go index 22b296f8c2..742e835f03 100644 --- a/vehicle/mercedes/provider.go +++ b/vehicle/mercedes/provider.go @@ -8,19 +8,19 @@ import ( // Provider implements the evcc vehicle api type Provider struct { - chargerG func() (interface{}, error) - rangeG func() (interface{}, error) + chargerG func() (EVResponse, error) + rangeG func() (EVResponse, error) } // NewProvider provides the evcc vehicle api provider func NewProvider(api *API, vin string, cache time.Duration) *Provider { impl := &Provider{ - chargerG: provider.NewCached(func() (interface{}, error) { + chargerG: provider.Cached(func() (EVResponse, error) { return api.SoC(vin) - }, cache).InterfaceGetter(), - rangeG: provider.NewCached(func() (interface{}, error) { + }, cache), + rangeG: provider.Cached(func() (EVResponse, error) { return api.Range(vin) - }, cache).InterfaceGetter(), + }, cache), } return impl } @@ -28,7 +28,7 @@ func NewProvider(api *API, vin string, cache time.Duration) *Provider { // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.chargerG() - if res, ok := res.(EVResponse); err == nil && ok { + if err == nil { return float64(res.SoC.Value), nil } @@ -38,7 +38,7 @@ func (v *Provider) SoC() (float64, error) { // Range implements the api.VehicleRange interface func (v *Provider) Range() (rng int64, err error) { res, err := v.chargerG() - if res, ok := res.(EVResponse); err == nil && ok { + if err == nil { return int64(res.RangeElectric.Value), nil } diff --git a/vehicle/nissan/provider.go b/vehicle/nissan/provider.go index 0f5f72a57c..088b916e96 100644 --- a/vehicle/nissan/provider.go +++ b/vehicle/nissan/provider.go @@ -12,7 +12,7 @@ const refreshTimeout = 2 * time.Minute // Provider is a kamereon provider type Provider struct { - statusG func() (interface{}, error) + statusG func() (StatusResponse, error) action func(value Action) error expiry time.Duration refreshTime time.Time @@ -28,12 +28,12 @@ func NewProvider(api *API, vin string, expiry, cache time.Duration) *Provider { expiry: expiry, } - impl.statusG = provider.NewCached(func() (interface{}, error) { + impl.statusG = provider.Cached(func() (StatusResponse, error) { return impl.status( func() (StatusResponse, error) { return api.BatteryStatus(vin) }, func() (ActionResponse, error) { return api.RefreshRequest(vin, "RefreshBatteryStatus") }, ) - }, cache).InterfaceGetter() + }, cache) return impl } @@ -85,7 +85,7 @@ var _ api.Battery = (*Provider)(nil) func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { return float64(res.Attributes.BatteryLevel), nil } @@ -99,7 +99,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if res.Attributes.PlugStatus > 0 { status = api.StatusB } @@ -117,7 +117,7 @@ var _ api.VehicleRange = (*Provider)(nil) func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { return int64(res.Attributes.RangeHvacOff), nil } @@ -130,7 +130,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { if res.Attributes.RemainingTime == nil { return time.Time{}, api.ErrNotAvailable } diff --git a/vehicle/niu.go b/vehicle/niu.go index 6365c387fb..f8d0699d53 100644 --- a/vehicle/niu.go +++ b/vehicle/niu.go @@ -23,7 +23,7 @@ type Niu struct { user, password string serial string token niu.Token - apiG func() (interface{}, error) + apiG func() (niu.Response, error) } func init() { @@ -58,7 +58,7 @@ func NewNiuFromConfig(other map[string]interface{}) (api.Vehicle, error) { serial: strings.ToUpper(cc.Serial), } - v.apiG = provider.NewCached(v.batteryAPI, cc.Cache).InterfaceGetter() + v.apiG = provider.Cached(v.batteryAPI, cc.Cache) return v, nil } @@ -116,7 +116,7 @@ func (v *Niu) request(uri string) (*http.Request, error) { } // batteryAPI provides battery api response -func (v *Niu) batteryAPI() (interface{}, error) { +func (v *Niu) batteryAPI() (niu.Response, error) { var res niu.Response req, err := v.request(niu.ApiURI + "/v3/motor_data/index_info?sn=" + v.serial) @@ -130,12 +130,7 @@ func (v *Niu) batteryAPI() (interface{}, error) { // SoC implements the api.Vehicle interface func (v *Niu) SoC() (float64, error) { res, err := v.apiG() - - if res, ok := res.(niu.Response); err == nil && ok { - return float64(res.Data.Batteries.CompartmentA.BatteryCharging), nil - } - - return 0, err + return float64(res.Data.Batteries.CompartmentA.BatteryCharging), err } var _ api.ChargeState = (*Niu)(nil) @@ -145,7 +140,7 @@ func (v *Niu) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.apiG() - if res, ok := res.(niu.Response); err == nil && ok { + if err == nil { if res.Data.IsConnected { status = api.StatusB } @@ -162,10 +157,5 @@ var _ api.VehicleRange = (*Niu)(nil) // Range implements the api.VehicleRange interface func (v *Niu) Range() (int64, error) { res, err := v.apiG() - - if res, ok := res.(niu.Response); err == nil && ok { - return res.Data.EstimatedMileage, nil - } - - return 0, err + return res.Data.EstimatedMileage, err } diff --git a/vehicle/ovms.go b/vehicle/ovms.go index b5e4084c45..cb3fc8c362 100644 --- a/vehicle/ovms.go +++ b/vehicle/ovms.go @@ -40,12 +40,13 @@ type ovmsConnectResponse struct { type Ovms struct { *embed *request.Helper - user, password, vehicleId, server string - cache time.Duration - isOnline bool - chargeG func() (interface{}, error) - statusG func() (interface{}, error) - locationG func() (interface{}, error) + user, password string + vehicleId, server string + cache time.Duration + isOnline bool + chargeG func() (ovmsChargeResponse, error) + statusG func() (ovmsStatusResponse, error) + locationG func() (ovmsLocationResponse, error) } func init() { @@ -78,9 +79,9 @@ func NewOvmsFromConfig(other map[string]interface{}) (api.Vehicle, error) { cache: cc.Cache, } - v.chargeG = provider.NewCached(v.batteryAPI, cc.Cache).InterfaceGetter() - v.statusG = provider.NewCached(v.statusAPI, cc.Cache).InterfaceGetter() - v.locationG = provider.NewCached(v.locationAPI, cc.Cache).InterfaceGetter() + v.chargeG = provider.Cached(v.batteryAPI, cc.Cache) + v.statusG = provider.Cached(v.statusAPI, cc.Cache) + v.locationG = provider.Cached(v.locationAPI, cc.Cache) var err error v.Jar, err = cookiejar.New(&cookiejar.Options{ @@ -142,7 +143,7 @@ func (v *Ovms) authFlow() error { } // batteryAPI provides battery-status api response -func (v *Ovms) batteryAPI() (interface{}, error) { +func (v *Ovms) batteryAPI() (ovmsChargeResponse, error) { var resp ovmsChargeResponse resp, err := v.chargeRequest() @@ -162,7 +163,7 @@ func (v *Ovms) batteryAPI() (interface{}, error) { } // statusAPI provides vehicle status api response -func (v *Ovms) statusAPI() (interface{}, error) { +func (v *Ovms) statusAPI() (ovmsStatusResponse, error) { var resp ovmsStatusResponse resp, err := v.statusRequest() @@ -177,7 +178,7 @@ func (v *Ovms) statusAPI() (interface{}, error) { } // location API provides vehicle position api response -func (v *Ovms) locationAPI() (interface{}, error) { +func (v *Ovms) locationAPI() (ovmsLocationResponse, error) { var resp ovmsLocationResponse resp, err := v.locationRequest() @@ -194,12 +195,7 @@ func (v *Ovms) locationAPI() (interface{}, error) { // SoC implements the api.Vehicle interface func (v *Ovms) SoC() (float64, error) { res, err := v.chargeG() - - if res, ok := res.(ovmsChargeResponse); err == nil && ok { - return res.Soc, nil - } - - return 0, err + return res.Soc, err } var _ api.ChargeState = (*Ovms)(nil) @@ -209,7 +205,7 @@ func (v *Ovms) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.chargeG() - if res, ok := res.(ovmsChargeResponse); err == nil && ok { + if err == nil { if res.ChargePortOpen > 0 { status = api.StatusB } @@ -227,7 +223,7 @@ var _ api.VehicleRange = (*Ovms)(nil) func (v *Ovms) Range() (int64, error) { res, err := v.chargeG() - if res, ok := res.(ovmsChargeResponse); err == nil && ok { + if err == nil { return strconv.ParseInt(res.EstimatedRange, 0, 64) } @@ -239,12 +235,7 @@ var _ api.VehicleOdometer = (*Ovms)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Ovms) Odometer() (float64, error) { res, err := v.statusG() - - if res, ok := res.(ovmsStatusResponse); err == nil && ok { - return res.Odometer / 10, nil - } - - return 0, err + return res.Odometer / 10, err } var _ api.VehicleFinishTimer = (*Ovms)(nil) @@ -252,21 +243,11 @@ var _ api.VehicleFinishTimer = (*Ovms)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Ovms) FinishTime() (time.Time, error) { res, err := v.chargeG() - - if res, ok := res.(ovmsChargeResponse); err == nil && ok { - return time.Now().Add(time.Duration(res.ChargeEtrFull) * time.Minute), nil - } - - return time.Time{}, err + return time.Now().Add(time.Duration(res.ChargeEtrFull) * time.Minute), err } // VehiclePosition returns the vehicles position in latitude and longitude func (v *Ovms) Position() (float64, float64, error) { res, err := v.locationG() - - if res, ok := res.(ovmsLocationResponse); err == nil && ok { - return res.Latitude, res.Longitude, nil - } - - return 0, 0, err + return res.Latitude, res.Longitude, err } diff --git a/vehicle/porsche.go b/vehicle/porsche.go index 6069936a03..e158a0ab99 100644 --- a/vehicle/porsche.go +++ b/vehicle/porsche.go @@ -8,7 +8,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/vehicle/porsche" - "github.com/thoas/go-funk" + "github.com/samber/lo" ) // Porsche is an api.Vehicle implementation for Porsche cars @@ -53,15 +53,15 @@ func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc.VIN, err = ensureVehicle(cc.VIN, func() ([]string, error) { mobileVehicles, err := mobile.Vehicles() if err == nil { - return funk.Map(mobileVehicles, func(v porsche.StatusResponseMobile) string { + return lo.Map(mobileVehicles, func(v porsche.StatusResponseMobile, _ int) string { return v.VIN - }).([]string), err + }), err } vehicles, err := api.Vehicles() - return funk.Map(vehicles, func(v porsche.Vehicle) string { + return lo.Map(vehicles, func(v porsche.Vehicle, _ int) string { return v.VIN - }).([]string), err + }), err }) if err != nil { diff --git a/vehicle/porsche/provider.go b/vehicle/porsche/provider.go index d0edaddaaa..ed01233d9d 100644 --- a/vehicle/porsche/provider.go +++ b/vehicle/porsche/provider.go @@ -11,28 +11,28 @@ import ( // Provider is an api.Vehicle implementation for Porsche PHEV cars type Provider struct { - statusG func() (interface{}, error) - emobilityG func() (interface{}, error) - mobileG func() (interface{}, error) + statusG func() (StatusResponse, error) + emobilityG func() (EmobilityResponse, error) + mobileG func() (StatusResponseMobile, error) } // NewProvider creates a new vehicle func NewProvider(log *util.Logger, api *API, emobility *EmobilityAPI, mobile *MobileAPI, vin string, carModel string, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (StatusResponse, error) { return api.Status(vin) - }, cache).InterfaceGetter(), + }, cache), - emobilityG: provider.NewCached(func() (interface{}, error) { + emobilityG: provider.Cached(func() (EmobilityResponse, error) { if carModel != "" { return emobility.Status(vin, carModel) } return EmobilityResponse{}, errors.New("no car model") - }, cache).InterfaceGetter(), + }, cache), - mobileG: provider.NewCached(func() (interface{}, error) { + mobileG: provider.Cached(func() (StatusResponseMobile, error) { return mobile.Status(vin, []string{BATTERY_LEVEL, BATTERY_CHARGING_STATE, CLIMATIZER_STATE, E_RANGE, HEATING_STATE, MILEAGE}) - }, cache).InterfaceGetter(), + }, cache), } return impl @@ -43,7 +43,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.mobileG() - if res, ok := res.(StatusResponseMobile); err == nil && ok { + if err == nil { m, err := res.MeasurementByKey("BATTERY_LEVEL") if err != nil && err != api.ErrNotAvailable { return 0, err @@ -53,14 +53,14 @@ func (v *Provider) SoC() (float64, error) { } } - res, err = v.emobilityG() - if res, ok := res.(EmobilityResponse); err == nil && ok { - return float64(res.BatteryChargeStatus.StateOfChargeInPercentage), nil + res3, err := v.emobilityG() + if err == nil { + return float64(res3.BatteryChargeStatus.StateOfChargeInPercentage), nil } - res, err = v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { - return res.BatteryLevel.Value, nil + res2, err := v.statusG() + if err == nil { + return res2.BatteryLevel.Value, nil } return 0, err @@ -71,7 +71,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.mobileG() - if res, ok := res.(StatusResponseMobile); err == nil && ok { + if err == nil { m, err := res.MeasurementByKey("E_RANGE") if err != nil && err != api.ErrNotAvailable { return 0, err @@ -81,14 +81,14 @@ func (v *Provider) Range() (int64, error) { } } - res, err = v.emobilityG() - if res, ok := res.(EmobilityResponse); err == nil && ok { - return res.BatteryChargeStatus.RemainingERange.ValueInKilometers, nil + res3, err := v.emobilityG() + if err == nil { + return res3.BatteryChargeStatus.RemainingERange.ValueInKilometers, nil } - res, err = v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { - return int64(res.RemainingRanges.ElectricalRange.Distance.Value), nil + res2, err := v.statusG() + if err == nil { + return int64(res2.RemainingRanges.ElectricalRange.Distance.Value), nil } return 0, err @@ -99,11 +99,12 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.mobileG() - if res, ok := res.(StatusResponseMobile); err == nil && ok { + if err == nil { m, err := res.MeasurementByKey("BATTERY_CHARGING_STATE") if err != nil && err != api.ErrNotAvailable { return time.Time{}, err } + if err != api.ErrNotAvailable { if m.Value.EndsAt == "" { if m.Value.LastModified != "" { @@ -115,9 +116,9 @@ func (v *Provider) FinishTime() (time.Time, error) { } } - res, err = v.emobilityG() - if res, ok := res.(EmobilityResponse); err == nil && ok { - return time.Now().Add(time.Duration(res.BatteryChargeStatus.RemainingChargeTimeUntil100PercentInMinutes) * time.Minute), err + res2, err := v.emobilityG() + if err == nil { + return time.Now().Add(time.Duration(res2.BatteryChargeStatus.RemainingChargeTimeUntil100PercentInMinutes) * time.Minute), err } return time.Time{}, err @@ -128,11 +129,12 @@ var _ api.ChargeState = (*Provider)(nil) // Status implements the api.ChargeState interface func (v *Provider) Status() (api.ChargeStatus, error) { res, err := v.mobileG() - if res, ok := res.(StatusResponseMobile); err == nil && ok { + if err == nil { m, err := res.MeasurementByKey("BATTERY_CHARGING_STATE") if err != nil && err != api.ErrNotAvailable { return api.StatusNone, err } + if err != api.ErrNotAvailable { switch m.Value.Status { case "FAST_CHARGING", "NOT_PLUGGED", "UNKNOWN": @@ -150,17 +152,17 @@ func (v *Provider) Status() (api.ChargeStatus, error) { } } - res, err = v.emobilityG() - if res, ok := res.(EmobilityResponse); err == nil && ok { - switch res.BatteryChargeStatus.PlugState { + res2, err := v.emobilityG() + if err == nil { + switch res2.BatteryChargeStatus.PlugState { case "DISCONNECTED": return api.StatusA, nil case "CONNECTED": // ignore if the car is connected to a DC charging station - if res.BatteryChargeStatus.ChargingInDCMode { + if res2.BatteryChargeStatus.ChargingInDCMode { return api.StatusA, nil } - switch res.BatteryChargeStatus.ChargingState { + switch res2.BatteryChargeStatus.ChargingState { case "ERROR": return api.StatusF, nil case "OFF", "COMPLETED": @@ -168,7 +170,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { case "ON", "CHARGING": return api.StatusC, nil default: - return api.StatusNone, errors.New("emobility - unknown charging state: " + res.BatteryChargeStatus.ChargingState) + return api.StatusNone, errors.New("emobility - unknown charging state: " + res2.BatteryChargeStatus.ChargingState) } } } @@ -181,7 +183,7 @@ var _ api.VehicleClimater = (*Provider)(nil) // Climater implements the api.VehicleClimater interface func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { res, err := v.mobileG() - if res, ok := res.(StatusResponseMobile); err == nil && ok { + if err == nil { m, err := res.MeasurementByKey("CLIMATIZER_STATE") if err != nil && err != api.ErrNotAvailable { return active, 20, 20, err @@ -191,15 +193,15 @@ func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp floa } } - res, err = v.emobilityG() - if res, ok := res.(EmobilityResponse); err == nil && ok { - switch res.DirectClimatisation.ClimatisationState { + res2, err := v.emobilityG() + if err == nil { + switch res2.DirectClimatisation.ClimatisationState { case "OFF": return false, 20, 20, nil case "ON": return true, 20, 20, nil default: - return active, outsideTemp, targetTemp, errors.New("emobility - unknown climate state: " + res.DirectClimatisation.ClimatisationState) + return active, outsideTemp, targetTemp, errors.New("emobility - unknown climate state: " + res2.DirectClimatisation.ClimatisationState) } } @@ -211,7 +213,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.mobileG() - if res, ok := res.(StatusResponseMobile); err == nil && ok { + if err == nil { m, err := res.MeasurementByKey("MILEAGE") if err != nil && err != api.ErrNotAvailable { return 0, err @@ -221,9 +223,9 @@ func (v *Provider) Odometer() (float64, error) { } } - res, err = v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { - return res.Mileage.Value, nil + res2, err := v.statusG() + if err == nil { + return res2.Mileage.Value, nil } return 0, err diff --git a/vehicle/psa.go b/vehicle/psa.go index c130c92f72..d69c7e6e9c 100644 --- a/vehicle/psa.go +++ b/vehicle/psa.go @@ -1,9 +1,7 @@ package vehicle import ( - "errors" "fmt" - "strings" "time" "github.com/evcc-io/evcc/api" @@ -98,26 +96,17 @@ func newPSA(log *util.Logger, brand, realm, id, secret string, other map[string] api := psa.NewAPI(log, identity, realm, cc.Credentials.ID) - vehicles, err := api.Vehicles() + _, vid, err := ensureVehicleWithFeature( + cc.VIN, api.Vehicles, + func(v psa.Vehicle) (string, string) { + return v.VIN, v.ID + }, + ) + if err != nil { return nil, err } - var vid string - if cc.VIN == "" && len(vehicles) == 1 { - vid = vehicles[0].ID - } else { - for _, vehicle := range vehicles { - if vehicle.VIN == strings.ToUpper(cc.VIN) { - vid = vehicle.ID - } - } - } - - if vid == "" { - return nil, errors.New("vin not found") - } - v.Provider = psa.NewProvider(api, vid, cc.Cache) return v, err diff --git a/vehicle/psa/provider.go b/vehicle/psa/provider.go index 978629c382..baae4798c4 100644 --- a/vehicle/psa/provider.go +++ b/vehicle/psa/provider.go @@ -10,15 +10,15 @@ import ( // Provider is an api.Vehicle implementation for PSA cars type Provider struct { - statusG func() (interface{}, error) + statusG func() (Status, error) } // NewProvider creates a new vehicle func NewProvider(api *API, vid string, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (Status, error) { return api.Status(vid) - }, cache).InterfaceGetter(), + }, cache), } return impl } @@ -28,7 +28,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { for _, e := range res.Energy { if e.Type != "Electric" { continue @@ -48,7 +48,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { for _, e := range res.Energy { if e.Type != "Electric" { continue @@ -69,7 +69,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { return res.Odometer.Mileage, nil } @@ -81,7 +81,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { for _, e := range res.Energy { if e.Type != "Electric" { continue @@ -101,7 +101,7 @@ var _ api.ChargeState = (*Provider)(nil) // Status implements the api.ChargeState interface func (v *Provider) Status() (api.ChargeStatus, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { for _, e := range res.Energy { if e.Type != "Electric" { continue @@ -131,7 +131,7 @@ var _ api.VehicleClimater = (*Provider)(nil) // Climater implements the api.VehicleClimater interface func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { active := strings.ToLower(res.Preconditionning.AirConditioning.Status) != "disabled" return active, 20, 20, nil } @@ -144,7 +144,7 @@ var _ api.VehiclePosition = (*Provider)(nil) // Position implements the api.VehiclePosition interface func (v *Provider) Position() (float64, float64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { if coord := res.LastPosition.Geometry.Coordinates; len(coord) == 2 { return coord[0], coord[1], nil } diff --git a/vehicle/renault.go b/vehicle/renault.go index efa4eabc6c..e3066186d7 100644 --- a/vehicle/renault.go +++ b/vehicle/renault.go @@ -12,7 +12,7 @@ import ( "github.com/evcc-io/evcc/provider" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" ) // Credits to @@ -105,9 +105,9 @@ type Renault struct { gigya, kamereon configServer gigyaJwtToken string accountID string - batteryG func() (interface{}, error) - cockpitG func() (interface{}, error) - hvacG func() (interface{}, error) + batteryG func() (kamereonResponse, error) + cockpitG func() (kamereonResponse, error) + hvacG func() (kamereonResponse, error) } func init() { @@ -150,11 +150,9 @@ func NewRenaultFromConfig(other map[string]interface{}) (api.Vehicle, error) { }) } - if err == nil { - v.batteryG = provider.NewCached(v.batteryAPI, cc.Cache).InterfaceGetter() - v.cockpitG = provider.NewCached(v.cockpitAPI, cc.Cache).InterfaceGetter() - v.hvacG = provider.NewCached(v.hvacAPI, cc.Cache).InterfaceGetter() - } + v.batteryG = provider.Cached(v.batteryAPI, cc.Cache) + v.cockpitG = provider.Cached(v.cockpitAPI, cc.Cache) + v.hvacG = provider.Cached(v.hvacAPI, cc.Cache) return v, err } @@ -314,7 +312,7 @@ func (v *Renault) kamereonVehicles(accountID string) ([]string, error) { } // batteryAPI provides battery-status api response -func (v *Renault) batteryAPI() (interface{}, error) { +func (v *Renault) batteryAPI() (kamereonResponse, error) { uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v2/cars/%s/battery-status", v.kamereon.Target, v.accountID, v.vin) res, err := v.kamereonRequest(uri) @@ -329,7 +327,7 @@ func (v *Renault) batteryAPI() (interface{}, error) { } // hvacAPI provides hvac-status api response -func (v *Renault) hvacAPI() (interface{}, error) { +func (v *Renault) hvacAPI() (kamereonResponse, error) { uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/hvac-status", v.kamereon.Target, v.accountID, v.vin) res, err := v.kamereonRequest(uri) @@ -344,7 +342,7 @@ func (v *Renault) hvacAPI() (interface{}, error) { } // cockpitAPI provides cockpit api response -func (v *Renault) cockpitAPI() (interface{}, error) { +func (v *Renault) cockpitAPI() (kamereonResponse, error) { uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v2/cars/%s/cockpit", v.kamereon.Target, v.accountID, v.vin) res, err := v.kamereonRequest(uri) @@ -362,7 +360,7 @@ func (v *Renault) cockpitAPI() (interface{}, error) { func (v *Renault) SoC() (float64, error) { res, err := v.batteryG() - if res, ok := res.(kamereonResponse); err == nil && ok { + if err == nil { return float64(res.Data.Attributes.BatteryLevel), nil } @@ -376,7 +374,7 @@ func (v *Renault) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.batteryG() - if res, ok := res.(kamereonResponse); err == nil && ok { + if err == nil { if res.Data.Attributes.PlugStatus > 0 { status = api.StatusB } @@ -394,7 +392,7 @@ var _ api.VehicleRange = (*Renault)(nil) func (v *Renault) Range() (int64, error) { res, err := v.batteryG() - if res, ok := res.(kamereonResponse); err == nil && ok { + if err == nil { return int64(res.Data.Attributes.BatteryAutonomy), nil } @@ -407,7 +405,7 @@ var _ api.VehicleOdometer = (*Renault)(nil) func (v *Renault) Odometer() (float64, error) { res, err := v.cockpitG() - if res, ok := res.(kamereonResponse); err == nil && ok { + if err == nil { return res.Data.Attributes.TotalMileage, nil } @@ -420,7 +418,7 @@ var _ api.VehicleFinishTimer = (*Renault)(nil) func (v *Renault) FinishTime() (time.Time, error) { res, err := v.batteryG() - if res, ok := res.(kamereonResponse); err == nil && ok { + if err == nil { timestamp, err := time.Parse(time.RFC3339, res.Data.Attributes.Timestamp) if res.Data.Attributes.RemainingTime == nil { @@ -444,14 +442,14 @@ func (v *Renault) Climater() (active bool, outsideTemp float64, targetTemp float return false, 0, 0, api.ErrNotAvailable } - if res, ok := res.(kamereonResponse); err == nil && ok { + if err == nil { state := strings.ToLower(res.Data.Attributes.HvacStatus) if state == "" { return false, 0, 0, api.ErrNotAvailable } - active := !funk.ContainsString([]string{"off", "false", "invalid", "error"}, state) + active := !slices.Contains([]string{"off", "false", "invalid", "error"}, state) return active, res.Data.Attributes.ExternalTemperature, 20, nil } diff --git a/vehicle/seat/cupra/provider.go b/vehicle/seat/cupra/provider.go index 0b105797e1..1f9aa064cf 100644 --- a/vehicle/seat/cupra/provider.go +++ b/vehicle/seat/cupra/provider.go @@ -10,16 +10,16 @@ import ( // Provider is an api.Vehicle implementation for Seat Cupra cars type Provider struct { - statusG func() (interface{}, error) + statusG func() (Status, error) action func(string, string) error } // NewProvider creates a new vehicle func NewProvider(api *API, userID, vin string, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (Status, error) { return api.Status(userID, vin) - }, cache).InterfaceGetter(), + }, cache), action: func(action, cmd string) error { return api.Action(vin, action, cmd) }, @@ -32,11 +32,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { - return float64(res.Engines.Primary.Level), nil - } - - return 0, err + return float64(res.Engines.Primary.Level), err } var _ api.ChargeState = (*Provider)(nil) @@ -46,7 +42,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { switch strings.ToLower(res.Services.Charging.Status) { case "connected": status = api.StatusB @@ -63,7 +59,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { rsc := res.Services.Charging if !rsc.Active { return time.Time{}, api.ErrNotAvailable @@ -85,11 +81,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { - return int64(res.Engines.Primary.Range.Value), nil - } - - return 0, err + return int64(res.Engines.Primary.Range.Value), err } var _ api.VehicleClimater = (*Provider)(nil) @@ -97,11 +89,7 @@ var _ api.VehicleClimater = (*Provider)(nil) // Climater implements the api.VehicleClimater interface func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { - return res.Services.Climatisation.Active, 21, 21, nil - } - - return active, 21, 21, err + return res.Services.Climatisation.Active, 21, 21, err } var _ api.VehicleChargeController = (*Provider)(nil) diff --git a/vehicle/silence.go b/vehicle/silence.go index b24b04a79a..bee6b01663 100644 --- a/vehicle/silence.go +++ b/vehicle/silence.go @@ -14,7 +14,7 @@ import ( // Silence is an api.Vehicle implementation for Silence S01 vehicles type Silence struct { *embed - apiG func() (interface{}, error) + apiG func() (silence.Vehicle, error) } func init() { @@ -55,9 +55,9 @@ func NewSilenceFromConfig(other map[string]interface{}) (api.Vehicle, error) { vin, err := ensureVehicle(strings.ToLower(cc.VIN), api.Vehicles) if err == nil { - v.apiG = provider.NewCached(func() (interface{}, error) { + v.apiG = provider.Cached(func() (silence.Vehicle, error) { return api.Status(vin) - }, cc.Cache).InterfaceGetter() + }, cc.Cache) } return v, err @@ -66,12 +66,7 @@ func NewSilenceFromConfig(other map[string]interface{}) (api.Vehicle, error) { // SoC implements the api.Vehicle interface func (v *Silence) SoC() (float64, error) { res, err := v.apiG() - - if res, ok := res.(silence.Vehicle); err == nil && ok { - return float64(res.BatterySoc), nil - } - - return 0, err + return float64(res.BatterySoc), err } var _ api.VehicleRange = (*Silence)(nil) @@ -79,12 +74,7 @@ var _ api.VehicleRange = (*Silence)(nil) // Range implements the api.VehicleRange interface func (v *Silence) Range() (int64, error) { res, err := v.apiG() - - if res, ok := res.(silence.Vehicle); err == nil && ok { - return int64(res.Range), nil - } - - return 0, err + return int64(res.Range), err } var _ api.VehicleOdometer = (*Silence)(nil) @@ -92,10 +82,5 @@ var _ api.VehicleOdometer = (*Silence)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Silence) Odometer() (float64, error) { res, err := v.apiG() - - if res, ok := res.(silence.Vehicle); err == nil && ok { - return float64(res.Odometer), nil - } - - return 0, err + return float64(res.Odometer), err } diff --git a/vehicle/silence/api.go b/vehicle/silence/api.go index 2716480bad..0216eadfc9 100644 --- a/vehicle/silence/api.go +++ b/vehicle/silence/api.go @@ -5,7 +5,7 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" - "github.com/thoas/go-funk" + "github.com/samber/lo" "golang.org/x/oauth2" ) @@ -40,9 +40,9 @@ func (v *API) Vehicles() ([]string, error) { err := v.GetJSON(ApiUri, &resp) if err == nil { - return funk.Map(resp, func(v Vehicle) string { + return lo.Map(resp, func(v Vehicle, _ int) string { return v.FrameNo - }).([]string), nil + }), nil } return nil, err diff --git a/vehicle/skoda/provider.go b/vehicle/skoda/provider.go index 7e85279e54..400c489711 100644 --- a/vehicle/skoda/provider.go +++ b/vehicle/skoda/provider.go @@ -9,19 +9,19 @@ import ( // Provider implements the evcc vehicle api type Provider struct { - chargerG func() (interface{}, error) + chargerG func() (ChargerResponse, error) action func(action, value string) error } // NewProvider provides the evcc vehicle api provider func NewProvider(api *API, vin string, cache time.Duration) *Provider { impl := &Provider{ - chargerG: provider.NewCached(func() (interface{}, error) { + chargerG: provider.Cached(func() (ChargerResponse, error) { return api.Charger(vin) - }, cache).InterfaceGetter(), - // climateG: provider.NewCached(func() (interface{}, error) { + }, cache), + // climateG: provider.Cached(func() (interface{}, error) { // return api.Climater(vin) - // }, cache).InterfaceGetter(), + // }, cache), action: func(action, value string) error { return api.Action(vin, action, value) }, @@ -34,7 +34,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { return float64(res.Battery.StateOfChargeInPercent), nil } @@ -48,7 +48,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { if res.Plug.ConnectionState == "Connected" { status = api.StatusB } @@ -65,7 +65,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { crg := res.Charging // estimate not available @@ -85,7 +85,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (rng int64, err error) { res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { rng = res.Battery.CruisingRangeElectricInMeters / 1e3 } @@ -97,7 +97,7 @@ func (v *Provider) Range() (rng int64, err error) { // // Climater implements the api.VehicleClimater interface // func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { // res, err := v.climateG() -// if res, ok := res.(ClimaterResponse); err == nil && ok { +// err == nil { // state := strings.ToLower(res.Climater.Status.ClimatisationStatusData.ClimatisationState.Content) // active := state != "off" && state != "invalid" && state != "error" diff --git a/vehicle/smart.go b/vehicle/smart.go index f7e822d2e0..188f5f8f2b 100644 --- a/vehicle/smart.go +++ b/vehicle/smart.go @@ -51,12 +51,7 @@ func NewSmartFromConfig(other map[string]interface{}) (api.Vehicle, error) { api := smart.NewAPI(log, identity) - if cc.VIN == "" { - cc.VIN, err = findVehicle(api.Vehicles()) - if err == nil { - log.DEBUG.Printf("found vehicle: %v", cc.VIN) - } - } + cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles) if err == nil { v.Provider = smart.NewProvider(log, api, cc.VIN, cc.Expiry, cc.Cache) diff --git a/vehicle/smart/api.go b/vehicle/smart/api.go index 6dba3cd718..a55846e1ef 100644 --- a/vehicle/smart/api.go +++ b/vehicle/smart/api.go @@ -7,7 +7,7 @@ import ( "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/transport" "github.com/evcc-io/evcc/vehicle/mb" - "github.com/thoas/go-funk" + "github.com/samber/lo" "golang.org/x/oauth2" ) @@ -69,9 +69,9 @@ func (v *API) Vehicles() ([]string, error) { err = fmt.Errorf("%s (%s): %w", res.Error, res.ErrorDescription, err) } - vehicles := funk.Map(res.LicensePlates, func(v vehicle) string { + vehicles := lo.Map(res.LicensePlates, func(v vehicle, _ int) string { return v.FIN - }).([]string) + }) return vehicles, err } diff --git a/vehicle/smart/provider.go b/vehicle/smart/provider.go index 1a67c9d0f5..e17e7d2693 100644 --- a/vehicle/smart/provider.go +++ b/vehicle/smart/provider.go @@ -11,7 +11,7 @@ import ( // https://github.com/TA2k/ioBroker.smart-eq type Provider struct { - statusG func() (interface{}, error) + statusG func() (StatusResponse, error) expiry time.Duration } @@ -20,12 +20,12 @@ func NewProvider(log *util.Logger, api *API, vin string, expiry, cache time.Dura expiry: expiry, } - v.statusG = provider.NewCached(func() (interface{}, error) { + v.statusG = provider.Cached(func() (StatusResponse, error) { return v.status( func() (StatusResponse, error) { return api.Status(vin) }, func() (StatusResponse, error) { return api.Refresh(vin) }, ) - }, cache).InterfaceGetter() + }, cache) return v } @@ -43,12 +43,7 @@ func (v *Provider) status(statusG func() (StatusResponse, error), refreshG func( // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - - if res, ok := res.(StatusResponse); err == nil && ok { - return res.Status.StatusData.Soc.Value, nil - } - - return 0, err + return res.Status.StatusData.Soc.Value, err } var _ api.VehicleRange = (*Provider)(nil) @@ -56,12 +51,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - - if res, ok := res.(StatusResponse); err == nil && ok { - return int64(res.Status.StatusData.RangeElectric.Value), nil - } - - return 0, err + return int64(res.Status.StatusData.RangeElectric.Value), err } var _ api.VehicleOdometer = (*Provider)(nil) @@ -69,10 +59,5 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the Provider.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - - if res, ok := res.(StatusResponse); err == nil && ok { - return res.Status.StatusData.Odo.Value, nil - } - - return 0, err + return res.Status.StatusData.Odo.Value, err } diff --git a/vehicle/tesla.go b/vehicle/tesla.go index 46c7cdc560..0907fbc2f5 100644 --- a/vehicle/tesla.go +++ b/vehicle/tesla.go @@ -2,8 +2,6 @@ package vehicle import ( "context" - "errors" - "strings" "time" "github.com/bogosj/tesla" @@ -18,9 +16,9 @@ import ( type Tesla struct { *embed vehicle *tesla.Vehicle - chargeStateG func() (interface{}, error) - vehicleStateG func() (interface{}, error) - driveStateG func() (interface{}, error) + chargeStateG func() (*tesla.ChargeState, error) + vehicleStateG func() (*tesla.VehicleState, error) + driveStateG func() (*tesla.DriveState, error) } func init() { @@ -65,56 +63,33 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { return nil, err } - vehicles, err := client.Vehicles() + cc.VIN, v.vehicle, err = ensureVehicleWithFeature( + cc.VIN, client.Vehicles, + func(v *tesla.Vehicle) (string, *tesla.Vehicle) { + return v.Vin, v + }, + ) + if err != nil { return nil, err } - if cc.VIN == "" && len(vehicles) == 1 { - v.vehicle = vehicles[0] - } else { - for _, vehicle := range vehicles { - if vehicle.Vin == strings.ToUpper(cc.VIN) { - v.vehicle = vehicle - } - } - } - - if v.vehicle == nil { - return nil, errors.New("vin not found") - } - if v.Title_ == "" { v.Title_ = v.vehicle.DisplayName } - v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).InterfaceGetter() - v.vehicleStateG = provider.NewCached(v.vehicleState, cc.Cache).InterfaceGetter() - v.driveStateG = provider.NewCached(v.driveState, cc.Cache).InterfaceGetter() + v.chargeStateG = provider.Cached(v.vehicle.ChargeState, cc.Cache) + v.vehicleStateG = provider.Cached(v.vehicle.VehicleState, cc.Cache) + v.driveStateG = provider.Cached(v.vehicle.DriveState, cc.Cache) return v, nil } -// chargeState implements the charge state api -func (v *Tesla) chargeState() (interface{}, error) { - return v.vehicle.ChargeState() -} - -// vehicleState implements the climater api -func (v *Tesla) vehicleState() (interface{}, error) { - return v.vehicle.VehicleState() -} - -// driveState implements the climater api -func (v *Tesla) driveState() (interface{}, error) { - return v.vehicle.DriveState() -} - // SoC implements the api.Vehicle interface func (v *Tesla) SoC() (float64, error) { res, err := v.chargeStateG() - if res, ok := res.(*tesla.ChargeState); err == nil && ok { + if err == nil { return float64(res.UsableBatteryLevel), nil } @@ -128,7 +103,7 @@ func (v *Tesla) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.chargeStateG() - if res, ok := res.(*tesla.ChargeState); err == nil && ok { + if err == nil { if res.ChargingState == "Stopped" || res.ChargingState == "NoPower" || res.ChargingState == "Complete" { status = api.StatusB } @@ -146,7 +121,7 @@ var _ api.ChargeRater = (*Tesla)(nil) func (v *Tesla) ChargedEnergy() (float64, error) { res, err := v.chargeStateG() - if res, ok := res.(*tesla.ChargeState); err == nil && ok { + if err == nil { return res.ChargeEnergyAdded, nil } @@ -161,7 +136,7 @@ var _ api.VehicleRange = (*Tesla)(nil) func (v *Tesla) Range() (int64, error) { res, err := v.chargeStateG() - if res, ok := res.(*tesla.ChargeState); err == nil && ok { + if err == nil { // miles to km return int64(kmPerMile * res.BatteryRange), nil } @@ -175,7 +150,7 @@ var _ api.VehicleOdometer = (*Tesla)(nil) func (v *Tesla) Odometer() (float64, error) { res, err := v.vehicleStateG() - if res, ok := res.(*tesla.VehicleState); err == nil && ok { + if err == nil { // miles to km return kmPerMile * res.Odometer, nil } @@ -189,7 +164,7 @@ var _ api.VehicleFinishTimer = (*Tesla)(nil) func (v *Tesla) FinishTime() (time.Time, error) { res, err := v.chargeStateG() - if res, ok := res.(*tesla.ChargeState); err == nil && ok { + if err == nil { t := time.Now() return t.Add(time.Duration(res.MinutesToFullCharge) * time.Minute), err } @@ -204,7 +179,7 @@ var _ api.VehiclePosition = (*Tesla)(nil) // Position implements the api.VehiclePosition interface func (v *Tesla) Position() (float64, float64, error) { res, err := v.driveStateG() - if res, ok := res.(*tesla.DriveState); err == nil && ok { + if err == nil { return res.Latitude, res.Longitude, nil } diff --git a/vehicle/tronity.go b/vehicle/tronity.go index 4472c2f7e8..631926680d 100644 --- a/vehicle/tronity.go +++ b/vehicle/tronity.go @@ -19,10 +19,8 @@ package vehicle import ( "context" - "errors" "fmt" "net/http" - "strings" "time" "github.com/evcc-io/evcc/api" @@ -32,7 +30,7 @@ import ( "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/sponsor" "github.com/evcc-io/evcc/vehicle/tronity" - "github.com/thoas/go-funk" + "golang.org/x/exp/slices" "golang.org/x/oauth2" ) @@ -43,7 +41,7 @@ type Tronity struct { log *util.Logger oc *oauth2.Config vid string - bulkG func() (interface{}, error) + bulkG func() (tronity.Bulk, error) } func init() { @@ -113,41 +111,32 @@ func NewTronityFromConfig(other map[string]interface{}) (api.Vehicle, error) { Base: v.Client.Transport, } - vehicles, err := v.vehicles() + _, vehicle, err := ensureVehicleWithFeature( + cc.VIN, v.vehicles, + func(v tronity.Vehicle) (string, tronity.Vehicle) { + return v.VIN, v + }, + ) + if err != nil { return nil, err } - var vehicle tronity.Vehicle - if cc.VIN == "" && len(vehicles) == 1 { - vehicle = vehicles[0] - } else { - for _, v := range vehicles { - if v.VIN == strings.ToUpper(cc.VIN) { - vehicle = v - } - } - } - - if vehicle.ID == "" { - return nil, errors.New("vin not found") - } - v.vid = vehicle.ID - v.bulkG = provider.NewCached(v.bulk, cc.Cache).InterfaceGetter() + v.bulkG = provider.Cached(v.bulk, cc.Cache) var status func() (api.ChargeStatus, error) - if funk.ContainsString(vehicle.Scopes, tronity.ReadCharge) { + if slices.Contains(vehicle.Scopes, tronity.ReadCharge) { status = v.status } var odometer func() (float64, error) - if funk.ContainsString(vehicle.Scopes, tronity.ReadOdometer) { + if slices.Contains(vehicle.Scopes, tronity.ReadOdometer) { odometer = v.odometer } var start, stop func() error - if funk.ContainsString(vehicle.Scopes, tronity.WriteChargeStartStop) { + if slices.Contains(vehicle.Scopes, tronity.WriteChargeStartStop) { start = v.startCharge stop = v.stopCharge } @@ -189,7 +178,7 @@ func (v *Tronity) vehicles() ([]tronity.Vehicle, error) { } // bulk implements the bulk api -func (v *Tronity) bulk() (interface{}, error) { +func (v *Tronity) bulk() (tronity.Bulk, error) { uri := fmt.Sprintf("%s/v1/vehicles/%s/bulk", tronity.URI, v.vid) var res tronity.Bulk @@ -201,12 +190,7 @@ func (v *Tronity) bulk() (interface{}, error) { // SoC implements the api.Vehicle interface func (v *Tronity) SoC() (float64, error) { res, err := v.bulkG() - - if res, ok := res.(tronity.Bulk); err == nil && ok { - return res.Level, nil - } - - return 0, err + return res.Level, err } // status implements the api.ChargeState interface @@ -214,7 +198,7 @@ func (v *Tronity) status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.bulkG() - if res, ok := res.(tronity.Bulk); err == nil && ok { + if err == nil { if res.Charging == "Charging" { status = api.StatusC } @@ -228,23 +212,13 @@ var _ api.VehicleRange = (*Tronity)(nil) // Range implements the api.VehicleRange interface func (v *Tronity) Range() (int64, error) { res, err := v.bulkG() - - if res, ok := res.(tronity.Bulk); err == nil && ok { - return int64(res.Range), nil - } - - return 0, err + return int64(res.Range), err } // odometer implements the api.VehicleOdometer interface func (v *Tronity) odometer() (float64, error) { res, err := v.bulkG() - - if res, ok := res.(tronity.Bulk); err == nil && ok { - return res.Odometer, nil - } - - return 0, err + return res.Odometer, err } func (v *Tronity) post(uri string) error { diff --git a/vehicle/volvo.go b/vehicle/volvo.go index 4e33325416..c5b22646cd 100644 --- a/vehicle/volvo.go +++ b/vehicle/volvo.go @@ -17,7 +17,7 @@ type Volvo struct { *embed *request.Helper vin string - statusG func() (interface{}, error) + statusG func() (volvo.Status, error) } func init() { @@ -60,9 +60,7 @@ func NewVolvoFromConfig(other map[string]interface{}) (api.Vehicle, error) { }), } - v.statusG = provider.NewCached(func() (interface{}, error) { - return v.status() - }, cc.Cache).InterfaceGetter() + v.statusG = provider.Cached(v.status, cc.Cache) var err error v.vin, err = ensureVehicle(cc.VIN, v.vehicles) @@ -109,11 +107,7 @@ func (v *Volvo) status() (volvo.Status, error) { // SoC implements the api.Vehicle interface func (v *Volvo) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(volvo.Status); err == nil && ok { - return float64(res.HvBattery.HvBatteryLevel), nil - } - - return 0, err + return float64(res.HvBattery.HvBatteryLevel), err } var _ api.ChargeState = (*Volvo)(nil) @@ -121,7 +115,7 @@ var _ api.ChargeState = (*Volvo)(nil) // Status implements the api.ChargeState interface func (v *Volvo) Status() (api.ChargeStatus, error) { res, err := v.statusG() - if res, ok := res.(volvo.Status); err == nil && ok { + if err == nil { switch res.HvBattery.HvBatteryChargeStatusDerived { case "CableNotPluggedInCar": return api.StatusA, nil @@ -140,11 +134,7 @@ var _ api.VehicleRange = (*Volvo)(nil) // VehicleRange implements the api.VehicleRange interface func (v *Volvo) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(volvo.Status); err == nil && ok { - return int64(res.HvBattery.DistanceToHVBatteryEmpty), nil - } - - return 0, err + return int64(res.HvBattery.DistanceToHVBatteryEmpty), err } var _ api.VehicleOdometer = (*Volvo)(nil) @@ -152,11 +142,7 @@ var _ api.VehicleOdometer = (*Volvo)(nil) // VehicleOdometer implements the api.VehicleOdometer interface func (v *Volvo) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(volvo.Status); err == nil && ok { - return res.Odometer / 1e3, nil - } - - return 0, err + return res.Odometer / 1e3, err } var _ api.VehicleFinishTimer = (*Volvo)(nil) @@ -164,7 +150,7 @@ var _ api.VehicleFinishTimer = (*Volvo)(nil) // FinishTime implements the VehicleFinishTimer interface func (v *Volvo) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(volvo.Status); err == nil && ok { + if err == nil { timestamp := res.HvBattery.TimeToHVBatteryFullyChargedTimestamp.Add(time.Duration(res.HvBattery.DistanceToHVBatteryEmpty) * time.Minute) if timestamp.Before(time.Now()) { return time.Time{}, api.ErrNotAvailable diff --git a/vehicle/vw/id/provider.go b/vehicle/vw/id/provider.go index d41e3f4bc0..9e563c99a6 100644 --- a/vehicle/vw/id/provider.go +++ b/vehicle/vw/id/provider.go @@ -10,16 +10,16 @@ import ( // Provider is an api.Vehicle implementation for VW ID cars type Provider struct { - statusG func() (interface{}, error) + statusG func() (Status, error) action func(action, value string) error } // NewProvider creates a new vehicle func NewProvider(api *API, vin string, cache time.Duration) *Provider { impl := &Provider{ - statusG: provider.NewCached(func() (interface{}, error) { + statusG: provider.Cached(func() (Status, error) { return api.Status(vin) - }, cache).InterfaceGetter(), + }, cache), action: func(action, value string) error { return api.Action(vin, action, value) }, @@ -32,7 +32,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { return float64(res.Data.BatteryStatus.CurrentSOCPercent), nil } @@ -46,7 +46,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { if res.Data.PlugStatus.PlugConnectionState == "connected" { status = api.StatusB } @@ -63,7 +63,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { cst := res.Data.ChargingStatus return cst.CarCapturedTimestamp.Add(time.Duration(cst.RemainingChargingTimeToCompleteMin) * time.Minute), err } @@ -76,7 +76,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { return int64(res.Data.BatteryStatus.CruisingRangeElectricKm), nil } @@ -88,7 +88,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { if res.Data.MaintenanceStatus == nil { return 0, api.ErrNotAvailable } @@ -103,7 +103,7 @@ var _ api.VehicleClimater = (*Provider)(nil) // Climater implements the api.VehicleClimater interface func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { res, err := v.statusG() - if res, ok := res.(Status); err == nil && ok { + if err == nil { state := strings.ToLower(res.Data.ClimatisationStatus.ClimatisationState) if state == "" { diff --git a/vehicle/vw/provider.go b/vehicle/vw/provider.go index dc158af3f8..80babe25f6 100644 --- a/vehicle/vw/provider.go +++ b/vehicle/vw/provider.go @@ -12,15 +12,15 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/provider" - "github.com/thoas/go-funk" + "github.com/samber/lo" ) // Provider implements the evcc vehicle api type Provider struct { - chargerG func() (interface{}, error) - statusG func() (interface{}, error) - climateG func() (interface{}, error) - positionG func() (interface{}, error) + chargerG func() (ChargerResponse, error) + statusG func() (StatusResponse, error) + climateG func() (ClimaterResponse, error) + positionG func() (PositionResponse, error) action func(action, value string) error rr func() (RolesRights, error) } @@ -28,18 +28,18 @@ type Provider struct { // NewProvider provides the evcc vehicle api provider func NewProvider(api *API, vin string, cache time.Duration) *Provider { impl := &Provider{ - chargerG: provider.NewCached(func() (interface{}, error) { + chargerG: provider.Cached(func() (ChargerResponse, error) { return api.Charger(vin) - }, cache).InterfaceGetter(), - statusG: provider.NewCached(func() (interface{}, error) { + }, cache), + statusG: provider.Cached(func() (StatusResponse, error) { return api.Status(vin) - }, cache).InterfaceGetter(), - climateG: provider.NewCached(func() (interface{}, error) { + }, cache), + climateG: provider.Cached(func() (ClimaterResponse, error) { return api.Climater(vin) - }, cache).InterfaceGetter(), - positionG: provider.NewCached(func() (interface{}, error) { + }, cache), + positionG: provider.Cached(func() (PositionResponse, error) { return api.Position(vin) - }, cache).InterfaceGetter(), + }, cache), action: func(action, value string) error { return api.Action(vin, action, value) }, @@ -55,7 +55,7 @@ var _ api.Battery = (*Provider)(nil) // SoC implements the api.Vehicle interface func (v *Provider) SoC() (float64, error) { res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { return float64(res.Charger.Status.BatteryStatusData.StateOfCharge.Content), nil } return 0, err @@ -68,7 +68,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) { status := api.StatusA // disconnected res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { if res.Charger.Status.PlugStatusData.PlugState.Content == "connected" { status = api.StatusB } @@ -85,7 +85,7 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { rct := res.Charger.Status.BatteryStatusData.RemainingChargingTime // estimate not available @@ -104,7 +104,7 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (rng int64, err error) { res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { + if err == nil { crsd := res.Charger.Status.CruisingRangeStatusData rng = int64(crsd.PrimaryEngineRange.Content) @@ -121,7 +121,7 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { res, err := v.statusG() - if res, ok := res.(StatusResponse); err == nil && ok { + if err == nil { err = api.ErrNotAvailable if sd := res.ServiceByID(ServiceOdometer); sd != nil { @@ -141,7 +141,7 @@ var _ api.VehicleClimater = (*Provider)(nil) // Climater implements the api.VehicleClimater interface func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { res, err := v.climateG() - if res, ok := res.(ClimaterResponse); err == nil && ok { + if err == nil { state := strings.ToLower(res.Climater.Status.ClimatisationStatusData.ClimatisationState.Content) active := state != "off" && state != "invalid" && state != "error" @@ -162,7 +162,7 @@ var _ api.VehiclePosition = (*Provider)(nil) // Position implements the api.VehiclePosition interface func (v *Provider) Position() (float64, float64, error) { res, err := v.positionG() - if res, ok := res.(PositionResponse); err == nil && ok { + if err == nil { coord := res.FindCarResponse.Position.CarCoordinate return float64(coord.Latitude) / 1e6, float64(coord.Longitude) / 1e6, nil } @@ -204,14 +204,14 @@ func (v *Provider) Diagnose2() { } // list remaining service - services := funk.Map(rr.OperationList.ServiceInfo, func(si ServiceInfo) string { + services := lo.Map(rr.OperationList.ServiceInfo, func(si ServiceInfo, _ int) string { if si.InvocationUrl.Content == "" { return si.ServiceId } return "" - }).([]string) + }) - services = funk.FilterString(services, func(s string) bool { + services = lo.Filter(services, func(s string, _ int) bool { return s != "" })