Skip to content

Commit

Permalink
Implement factory patterns for device configuration (evcc-io#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Aug 22, 2020
1 parent abeded1 commit 81b72e2
Show file tree
Hide file tree
Showing 30 changed files with 185 additions and 74 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
4 changes: 4 additions & 0 deletions charger/charger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }{}
Expand Down
57 changes: 27 additions & 30 deletions charger/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion charger/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions charger/evsewifi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }{}
Expand Down
4 changes: 4 additions & 0 deletions charger/go-e.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions charger/keba.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions charger/mcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }{}
Expand Down
4 changes: 4 additions & 0 deletions charger/nrgble.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions charger/nrgconnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }{}
Expand Down
4 changes: 4 additions & 0 deletions charger/phoenix-emcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions charger/phoenix-evcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
4 changes: 4 additions & 0 deletions charger/simpleevse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }{}
Expand Down
6 changes: 5 additions & 1 deletion charger/wallbe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions charger/wallbe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
43 changes: 27 additions & 16 deletions meter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions meter/meter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions meter/modbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions meter/sma.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions meter/tesla.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions vehicle/audi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions vehicle/bmw.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 81b72e2

Please sign in to comment.