diff --git a/README.md b/README.md index 96c3622350..c333beb764 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Charger is responsible for handling EV state and adjusting charge current. Avail - `phoenix-evcc`: chargers with Phoenix EV-CC-AC1-M controllers (ModBus connection) - `simpleevse`: chargers with SimpleEVSE controllers connected via ModBus (e.g. OpenWB Wallbox, Easy Wallbox B163, ...) - `evsewifi`: chargers with SimpleEVSE controllers using [EVSE-WiFi](https://www.evse-wifi.de/) -- `nrgkick-bt`: NRGkick chargers with Bluetooth connector (Linux only, not supported on Docker) +- `nrgkick-bluetooth`: NRGkick chargers with Bluetooth connector (Linux only, not supported on Docker) - `nrgkick-connect`: NRGkick chargers with additional NRGkick Connect module - `go-e`: go-eCharger chargers (both local and cloud API are supported) - `keba`: KEBA KeContact P20/P30 and BMW chargers (see [Preparation](#keba-preparation)) diff --git a/charger/charger.go b/charger/charger.go index 2f2c4b5474..8d8cba7faa 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -16,6 +16,10 @@ type Charger struct { maxCurrentS func(int64) error } +func init() { + registry.Add("default", NewConfigurableFromConfig) +} + // NewConfigurableFromConfig creates a new configurable charger func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ Status, Enable, Enabled, MaxCurrent provider.Config }{} diff --git a/charger/config.go b/charger/config.go index 968a6e1ea0..60a818047b 100644 --- a/charger/config.go +++ b/charger/config.go @@ -9,38 +9,35 @@ import ( type apiFunction string -// NewFromConfig creates charger from configuration -func NewFromConfig(typ string, other map[string]interface{}) (charger api.Charger, err error) { - switch strings.ToLower(typ) { - case "default", "configurable": - charger, err = NewConfigurableFromConfig(other) - case "wallbe": - charger, err = NewWallbeFromConfig(other) - case "phoenix-emcp": - charger, err = NewPhoenixEMCPFromConfig(other) - case "phoenix-evcc": - charger, err = NewPhoenixEVCCFromConfig(other) - case "nrgkick-bluetooth", "nrgkick-bt", "nrgble": - charger, err = NewNRGKickBLEFromConfig(other) - case "nrgkick-connect", "nrgconnect": - charger, err = NewNRGKickConnectFromConfig(other) - case "go-e", "goe": - charger, err = NewGoEFromConfig(other) - case "evsewifi": - charger, err = NewEVSEWifiFromConfig(other) - case "simpleevse", "evse": - charger, err = NewSimpleEVSEFromConfig(other) - case "porsche", "audi", "bentley", "mcc": - charger, err = NewMobileConnectFromConfig(other) - case "keba", "bmw": - charger, err = NewKebaFromConfig(other) - default: - return nil, fmt.Errorf("invalid charger type: %s", typ) +type chargerRegistry map[string]func(map[string]interface{}) (api.Charger, error) + +func (r chargerRegistry) Add(name string, factory func(map[string]interface{}) (api.Charger, error)) { + if _, exists := r[name]; exists { + panic(fmt.Sprintf("cannot register duplicate charger type: %s", name)) + } + r[name] = factory +} + +func (r chargerRegistry) Get(name string) (func(map[string]interface{}) (api.Charger, error), error) { + factory, exists := r[name] + if !exists { + return nil, fmt.Errorf("charger type not registered: %s", name) } + return factory, nil +} - if err != nil { - err = fmt.Errorf("cannot create %s charger: %v", typ, err) +var registry chargerRegistry = make(map[string]func(map[string]interface{}) (api.Charger, error)) + +// NewFromConfig creates charger from configuration +func NewFromConfig(typ string, other map[string]interface{}) (v api.Charger, err error) { + factory, err := registry.Get(strings.ToLower(typ)) + if err == nil { + if v, err = factory(other); err != nil { + err = fmt.Errorf("cannot create %s charger: %w", typ, err) + } + } else { + err = fmt.Errorf("invalid charger type: %s", typ) } - return charger, err + return } diff --git a/charger/config_test.go b/charger/config_test.go index 87c979f7ac..bf39531ff4 100644 --- a/charger/config_test.go +++ b/charger/config_test.go @@ -10,7 +10,7 @@ func TestChargers(t *testing.T) { acceptable := []string{ "invalid plugin type: ...", "mqtt not configured", - "NRGKick bluetooth is only supported on linux", + "invalid charger type: nrgkick-bluetooth", "invalid pin:", "connect: no route to host", "connect: connection refused", diff --git a/charger/evsewifi.go b/charger/evsewifi.go index c25f42a124..01523f30e7 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -49,6 +49,10 @@ type EVSEWifi struct { uri string } +func init() { + registry.Add("evsewifi", NewEVSEWifiFromConfig) +} + // NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI string }{} diff --git a/charger/go-e.go b/charger/go-e.go index 5cf9456710..3dd8bfb4cc 100644 --- a/charger/go-e.go +++ b/charger/go-e.go @@ -44,6 +44,10 @@ type GoE struct { status goeStatusResponse } +func init() { + registry.Add("go-e", NewGoEFromConfig) +} + // NewGoEFromConfig creates a go-e charger from generic config func NewGoEFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { diff --git a/charger/keba.go b/charger/keba.go index aaf265ebfc..b671c6b467 100644 --- a/charger/keba.go +++ b/charger/keba.go @@ -36,6 +36,10 @@ type Keba struct { recv chan keba.UDPMsg } +func init() { + registry.Add("keba", NewKebaFromConfig) +} + // NewKebaFromConfig creates a new configurable charger func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { diff --git a/charger/mcc.go b/charger/mcc.go index 61b6001af9..aeb601fde6 100644 --- a/charger/mcc.go +++ b/charger/mcc.go @@ -67,6 +67,10 @@ type MobileConnect struct { cableInformation MCCCurrentCableInformation } +func init() { + registry.Add("mcc", NewMobileConnectFromConfig) +} + // NewMobileConnectFromConfig creates a MCC charger from generic config func NewMobileConnectFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI, Password string }{} diff --git a/charger/nrgble.go b/charger/nrgble.go index 87df8b0cb8..418dbdce6e 100644 --- a/charger/nrgble.go +++ b/charger/nrgble.go @@ -8,6 +8,10 @@ import ( "github.com/andig/evcc/api" ) +func init() { + registry.Add("ngrkick-bluetooth", NewNRGKickBLEFromConfig) +} + // NewNRGKickBLEFromConfig creates a NRGKickBLE charger from generic config func NewNRGKickBLEFromConfig(other map[string]interface{}) (api.Charger, error) { return nil, errors.New("NRGKick bluetooth is only supported on linux") diff --git a/charger/nrgconnect.go b/charger/nrgconnect.go index 1f6320d595..f41ba3a6aa 100644 --- a/charger/nrgconnect.go +++ b/charger/nrgconnect.go @@ -69,6 +69,10 @@ type NRGKickConnect struct { password string } +func init() { + registry.Add("nrgkick-connect", NewNRGKickConnectFromConfig) +} + // NewNRGKickConnectFromConfig creates a NRGKickConnect charger from generic config func NewNRGKickConnectFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI, Mac, Password string }{} diff --git a/charger/phoenix-emcp.go b/charger/phoenix-emcp.go index 3b339a48f0..2467ef37a7 100644 --- a/charger/phoenix-emcp.go +++ b/charger/phoenix-emcp.go @@ -25,6 +25,10 @@ type PhoenixEMCP struct { handler *modbus.TCPClientHandler } +func init() { + registry.Add("phoenix-emcp", NewPhoenixEMCPFromConfig) +} + // NewPhoenixEMCPFromConfig creates a Phoenix charger from generic config func NewPhoenixEMCPFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { diff --git a/charger/phoenix-evcc.go b/charger/phoenix-evcc.go index 99657ad987..7e7679db4c 100644 --- a/charger/phoenix-evcc.go +++ b/charger/phoenix-evcc.go @@ -24,6 +24,10 @@ type PhoenixEVCC struct { client gridx.Client } +func init() { + registry.Add("phoenix-evcc", NewPhoenixEVCCFromConfig) +} + // NewPhoenixEVCCFromConfig creates a Phoenix charger from generic config func NewPhoenixEVCCFromConfig(other map[string]interface{}) (api.Charger, error) { cc := modbus.Connection{ID: 255} diff --git a/charger/simpleevse.go b/charger/simpleevse.go index f5ac72abe5..79ebfd6d06 100644 --- a/charger/simpleevse.go +++ b/charger/simpleevse.go @@ -22,6 +22,10 @@ const ( evseRegTurnOff = 1004 ) +func init() { + registry.Add("simpleevse", NewSimpleEVSEFromConfig) +} + // NewSimpleEVSEFromConfig creates a SimpleEVSE charger from generic config func NewSimpleEVSEFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI, Device string }{} diff --git a/charger/wallbe.go b/charger/wallbe.go index 42688ea1f9..e52e4daa9f 100644 --- a/charger/wallbe.go +++ b/charger/wallbe.go @@ -33,8 +33,12 @@ type Wallbe struct { factor int64 } +func init() { + registry.Add("wallbe", NewWallbeFromConfig) +} + // NewWallbeFromConfig creates a Wallbe charger from generic config -func NewWallbeFromConfig(other map[string]interface{}) (*Wallbe, error) { +func NewWallbeFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { URI string Legacy bool diff --git a/charger/wallbe_test.go b/charger/wallbe_test.go index 757a730269..482755691f 100644 --- a/charger/wallbe_test.go +++ b/charger/wallbe_test.go @@ -5,20 +5,30 @@ import ( ) func TestWallbe(t *testing.T) { - wb, err := NewWallbeFromConfig(nil) + wbc, err := NewWallbeFromConfig(nil) if err != nil { t.Error(err) } + wb, ok := wbc.(*Wallbe) + if !ok { + t.Error("unexpected type") + } + if wb.factor != 10 { t.Errorf("invalid factor: %d", wb.factor) } - wb, err = NewWallbeFromConfig(map[string]interface{}{"legacy": true}) + wbc, err = NewWallbeFromConfig(map[string]interface{}{"legacy": true}) if err != nil { t.Error(err) } + wb, ok = wbc.(*Wallbe) + if !ok { + t.Error("unexpected type") + } + if wb.factor != 1 { t.Errorf("invalid factor: %d", wb.factor) } diff --git a/go.mod b/go.mod index 47ab646557..068ac1d1cf 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/andig/evcc go 1.13 require ( - github.com/andig/evcc-config v0.0.0-20200814164749-a3589fa3b244 + github.com/andig/evcc-config v0.0.0-20200822120120-f129cbffa9aa github.com/asaskevich/EventBus v0.0.0-20200428142821-4fc0642a29f3 github.com/avast/retry-go v2.6.0+incompatible github.com/benbjohnson/clock v1.0.3 diff --git a/go.sum b/go.sum index 4f262b05f9..84af995669 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alvaroloes/enumer v1.1.2 h1:5khqHB33TZy1GWCO/lZwcroBFh7u+0j40T83VUbfAMY= github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= github.com/andig/evcc v0.0.0-20200727161511-d58eb15f2dc9/go.mod h1:8HONEC6cC2s4k0u3QL7GIjrYOZYTOKiiXybw0FIJL0A= -github.com/andig/evcc-config v0.0.0-20200814164749-a3589fa3b244 h1:T1BN+JT/s49O3PsD22xep5RogK1xHBfERY+o7NljGo0= -github.com/andig/evcc-config v0.0.0-20200814164749-a3589fa3b244/go.mod h1:N0hIjIy+5E2AR1fF7Tg2IzBlblBrnFvCCaDGAaHzbWk= +github.com/andig/evcc-config v0.0.0-20200822120120-f129cbffa9aa h1:+t8n7jFHvd/QITFCYHu4D2kUFgo9Mc5hu9lJSJPuMCQ= +github.com/andig/evcc-config v0.0.0-20200822120120-f129cbffa9aa/go.mod h1:N0hIjIy+5E2AR1fF7Tg2IzBlblBrnFvCCaDGAaHzbWk= github.com/andig/gosunspec v0.0.0-20200429133549-3cf6a82fed9c h1:AMtX56iHlNYVxMID7fe9efuVtaxgtdjyMeolg7q87IE= github.com/andig/gosunspec v0.0.0-20200429133549-3cf6a82fed9c/go.mod h1:YkshK8WMzYn1iXAZzHUO75gIqhMSan2ctgBVtBkRIyA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= diff --git a/meter/config.go b/meter/config.go index e22875d6bb..986378bbb9 100644 --- a/meter/config.go +++ b/meter/config.go @@ -7,24 +7,35 @@ import ( "github.com/andig/evcc/api" ) -// NewFromConfig creates meter from configuration -func NewFromConfig(typ string, other map[string]interface{}) (meter api.Meter, err error) { - switch strings.ToLower(typ) { - case "default", "configurable": - meter, err = NewConfigurableFromConfig(other) - case "modbus": - meter, err = NewModbusFromConfig(other) - case "sma": - meter, err = NewSMAFromConfig(other) - case "tesla", "powerwall": - meter, err = NewTeslaFromConfig(other) - default: - err = fmt.Errorf("invalid meter type: %s", typ) +type meterRegistry map[string]func(map[string]interface{}) (api.Meter, error) + +func (r meterRegistry) Add(name string, factory func(map[string]interface{}) (api.Meter, error)) { + if _, exists := r[name]; exists { + panic(fmt.Sprintf("cannot register duplicate meter type: %s", name)) + } + r[name] = factory +} + +func (r meterRegistry) Get(name string) (func(map[string]interface{}) (api.Meter, error), error) { + factory, exists := r[name] + if !exists { + return nil, fmt.Errorf("meter type not registered: %s", name) } + return factory, nil +} + +var registry meterRegistry = make(map[string]func(map[string]interface{}) (api.Meter, error)) - if err != nil { - err = fmt.Errorf("cannot create %s meter: %v", typ, err) +// NewFromConfig creates meter from configuration +func NewFromConfig(typ string, other map[string]interface{}) (v api.Meter, err error) { + factory, err := registry.Get(strings.ToLower(typ)) + if err == nil { + if v, err = factory(other); err != nil { + err = fmt.Errorf("cannot create %s meter: %w", typ, err) + } + } else { + err = fmt.Errorf("invalid meter type: %s", typ) } - return meter, err + return } diff --git a/meter/meter.go b/meter/meter.go index b90943c9dc..8e324599e1 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -9,6 +9,10 @@ import ( "github.com/andig/evcc/util" ) +func init() { + registry.Add("default", NewConfigurableFromConfig) +} + // NewConfigurableFromConfig creates api.Meter from config func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { diff --git a/meter/modbus.go b/meter/modbus.go index cdedcf123f..1e512bf3a1 100644 --- a/meter/modbus.go +++ b/meter/modbus.go @@ -21,6 +21,10 @@ type Modbus struct { opEnergy modbus.Operation } +func init() { + registry.Add("modbus", NewModbusFromConfig) +} + // NewModbusFromConfig creates api.Meter from config func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { diff --git a/meter/sma.go b/meter/sma.go index 31038e04e7..f2dddc371a 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -33,6 +33,10 @@ type SMA struct { recv chan sma.Telegram } +func init() { + registry.Add("sma", NewSMAFromConfig) +} + // NewSMAFromConfig creates a SMA Meter from generic config func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { diff --git a/meter/tesla.go b/meter/tesla.go index 94a563e316..64104e2283 100644 --- a/meter/tesla.go +++ b/meter/tesla.go @@ -35,6 +35,10 @@ type Tesla struct { uri, usage string } +func init() { + registry.Add("tesla", NewTeslaFromConfig) +} + // NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { diff --git a/vehicle/audi.go b/vehicle/audi.go index eca0558d6f..07ffef8c81 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -53,6 +53,10 @@ type Audi struct { chargeStateG func() (float64, error) } +func init() { + registry.Add("audi", NewAudiFromConfig) +} + // NewAudiFromConfig creates a new vehicle func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { diff --git a/vehicle/bmw.go b/vehicle/bmw.go index 83a713f74b..e889290e76 100644 --- a/vehicle/bmw.go +++ b/vehicle/bmw.go @@ -39,6 +39,10 @@ type BMW struct { chargeStateG func() (float64, error) } +func init() { + registry.Add("bmw", NewBMWFromConfig) +} + // NewBMWFromConfig creates a new vehicle func NewBMWFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { diff --git a/vehicle/config.go b/vehicle/config.go index 15c6a12fc1..3f0d27ad0d 100644 --- a/vehicle/config.go +++ b/vehicle/config.go @@ -7,30 +7,35 @@ import ( "github.com/andig/evcc/api" ) +type vehicleRegistry map[string]func(map[string]interface{}) (api.Vehicle, error) + +func (r vehicleRegistry) Add(name string, factory func(map[string]interface{}) (api.Vehicle, error)) { + if _, exists := r[name]; exists { + panic(fmt.Sprintf("cannot register duplicate vehicle type: %s", name)) + } + r[name] = factory +} + +func (r vehicleRegistry) Get(name string) (func(map[string]interface{}) (api.Vehicle, error), error) { + factory, exists := r[name] + if !exists { + return nil, fmt.Errorf("vehicle type not registered: %s", name) + } + return factory, nil +} + +var registry vehicleRegistry = make(map[string]func(map[string]interface{}) (api.Vehicle, error)) + // NewFromConfig creates vehicle from configuration func NewFromConfig(typ string, other map[string]interface{}) (v api.Vehicle, err error) { - switch strings.ToLower(typ) { - case "default", "configurable": - v, err = NewConfigurableFromConfig(other) - case "audi", "etron": - v, err = NewAudiFromConfig(other) - case "bmw", "i3": - v, err = NewBMWFromConfig(other) - case "tesla", "model3", "model 3", "models", "model s": - v, err = NewTeslaFromConfig(other) - case "nissan", "leaf": - v, err = NewNissanFromConfig(other) - case "renault", "zoe": - v, err = NewRenaultFromConfig(other) - case "porsche", "taycan": - v, err = NewPorscheFromConfig(other) - default: + factory, err := registry.Get(strings.ToLower(typ)) + if err == nil { + if v, err = factory(other); err != nil { + err = fmt.Errorf("cannot create %s vehicle: %w", typ, err) + } + } else { err = fmt.Errorf("invalid vehicle type: %s", typ) } - if err != nil { - err = fmt.Errorf("cannot create %s vehicle: %v", typ, err) - } - return } diff --git a/vehicle/nissan.go b/vehicle/nissan.go index cd3c1cb519..93297f89bd 100644 --- a/vehicle/nissan.go +++ b/vehicle/nissan.go @@ -17,6 +17,10 @@ type Nissan struct { chargeStateG func() (float64, error) } +func init() { + registry.Add("nissan", NewNissanFromConfig) +} + // NewNissanFromConfig creates a new vehicle func NewNissanFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { diff --git a/vehicle/porsche.go b/vehicle/porsche.go index ec91dde412..fda315c329 100644 --- a/vehicle/porsche.go +++ b/vehicle/porsche.go @@ -58,6 +58,10 @@ type Porsche struct { chargeStateG func() (float64, error) } +func init() { + registry.Add("porsche", NewPorscheFromConfig) +} + // NewPorscheFromConfig creates a new vehicle func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { diff --git a/vehicle/renault.go b/vehicle/renault.go index bb7113332d..f6b40a16d0 100644 --- a/vehicle/renault.go +++ b/vehicle/renault.go @@ -93,6 +93,10 @@ type Renault struct { chargeStateG func() (float64, error) } +func init() { + registry.Add("renault", NewRenaultFromConfig) +} + // NewRenaultFromConfig creates a new vehicle func NewRenaultFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { diff --git a/vehicle/tesla.go b/vehicle/tesla.go index 533e83ee1c..9bb5c9d1d3 100644 --- a/vehicle/tesla.go +++ b/vehicle/tesla.go @@ -18,6 +18,10 @@ type Tesla struct { chargedEnergyG func() (float64, error) } +func init() { + registry.Add("tesla", NewTeslaFromConfig) +} + // NewTeslaFromConfig creates a new Tesla vehicle func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { diff --git a/vehicle/vehicle.go b/vehicle/vehicle.go index a1f743b535..990ea323c4 100644 --- a/vehicle/vehicle.go +++ b/vehicle/vehicle.go @@ -30,6 +30,10 @@ type Vehicle struct { chargeG func() (float64, error) } +func init() { + registry.Add("default", NewConfigurableFromConfig) +} + // NewConfigurableFromConfig creates a new Vehicle func NewConfigurableFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct {