From b1de0950c2c82b729360a32607f38cae4c434c96 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 29 Jul 2020 13:42:09 +0200 Subject: [PATCH] Return errors instead of failing --- charger/charger.go | 44 +++-- charger/config.go | 32 ++-- charger/evsewifi.go | 10 +- charger/go-e.go | 14 +- charger/{go-e-test.go => go-e_test.go} | 6 +- charger/keba.go | 10 +- charger/keba_test.go | 6 +- charger/mcc.go | 12 +- charger/nrgble.go | 8 +- charger/nrgble_linux.go | 21 ++- charger/nrgconnect.go | 13 +- charger/nrgconnect_test.go | 6 +- charger/phoenix-emcp.go | 20 +-- charger/phoenix-evcc.go | 22 +-- charger/simpleevse.go | 12 +- charger/wallbe.go | 62 +------ charger/wallbe_test.go | 12 +- cmd/config.go | 50 ++++-- cmd/setup.go | 9 +- core/loadpoint.go | 4 +- core/site.go | 4 +- core/wrapper/chargerater_test.go | 2 +- go.mod | 2 +- go.sum | 8 + meter/config.go | 18 +- meter/meter.go | 38 ++-- meter/modbus.go | 24 ++- meter/sma.go | 16 +- meter/sma/listener_test.go | 2 +- meter/sma_test.go | 2 +- meter/tesla.go | 18 +- provider/cache.go | 4 +- provider/cache_test.go | 2 +- provider/calc.go | 15 +- provider/config.go | 231 +++++++++++++++++-------- provider/http.go | 15 +- provider/modbus.go | 36 ++-- provider/openwb.go | 23 ++- provider/script.go | 5 +- provider/socket.go | 22 ++- push/config.go | 18 +- util/decoder.go | 10 +- util/modbus/modbus.go | 12 +- vehicle/audi.go | 10 +- vehicle/bmw.go | 17 +- vehicle/config.go | 24 ++- vehicle/nissan.go | 17 +- vehicle/porsche.go | 10 +- vehicle/renault.go | 21 +-- vehicle/tesla.go | 20 ++- vehicle/vehicle.go | 22 ++- 51 files changed, 629 insertions(+), 412 deletions(-) rename charger/{go-e-test.go => go-e_test.go} (77%) diff --git a/charger/charger.go b/charger/charger.go index b969fbfa94..2f2c4b5474 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -1,6 +1,8 @@ package charger import ( + "fmt" + "github.com/andig/evcc/api" "github.com/andig/evcc/provider" "github.com/andig/evcc/util" @@ -15,9 +17,11 @@ type Charger struct { } // NewConfigurableFromConfig creates a new configurable charger -func NewConfigurableFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ Status, Enable, Enabled, MaxCurrent provider.Config }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } for k, v := range map[string]string{ "status": cc.Status.Type, @@ -26,18 +30,32 @@ func NewConfigurableFromConfig(log *util.Logger, other map[string]interface{}) a "maxcurrent": cc.MaxCurrent.Type, } { if v == "" { - log.FATAL.Fatalf("default charger config: %s required", k) + return nil, fmt.Errorf("default charger config: %s required", k) } } - charger := NewConfigurable( - provider.NewStringGetterFromConfig(log, cc.Status), - provider.NewBoolGetterFromConfig(log, cc.Enabled), - provider.NewBoolSetterFromConfig(log, "enable", cc.Enable), - provider.NewIntSetterFromConfig(log, "maxcurrent", cc.MaxCurrent), - ) + status, err := provider.NewStringGetterFromConfig(cc.Status) + + var enabled func() (bool, error) + if err == nil { + enabled, err = provider.NewBoolGetterFromConfig(cc.Enabled) + } + + var enable func(bool) error + if err == nil { + enable, err = provider.NewBoolSetterFromConfig("enable", cc.Enable) + } + + var maxcurrent func(int64) error + if err == nil { + maxcurrent, err = provider.NewIntSetterFromConfig("maxcurrent", cc.MaxCurrent) + } + + if err != nil { + return nil, err + } - return charger + return NewConfigurable(status, enabled, enable, maxcurrent) } // NewConfigurable creates a new charger @@ -46,13 +64,15 @@ func NewConfigurable( enabledG func() (bool, error), enableS func(bool) error, maxCurrentS func(int64) error, -) api.Charger { - return &Charger{ +) (api.Charger, error) { + c := &Charger{ statusG: statusG, enabledG: enabledG, enableS: enableS, maxCurrentS: maxCurrentS, } + + return c, nil } // Status implements the Charger.Status interface diff --git a/charger/config.go b/charger/config.go index cfb49b51c0..cf8e442ff4 100644 --- a/charger/config.go +++ b/charger/config.go @@ -1,44 +1,42 @@ package charger import ( + "fmt" "strings" "github.com/andig/evcc/api" - "github.com/andig/evcc/util" ) type apiFunction string // NewFromConfig creates charger from configuration -func NewFromConfig(log *util.Logger, typ string, other map[string]interface{}) api.Charger { - var c api.Charger - +func NewFromConfig(typ string, other map[string]interface{}) (charger api.Charger, err error) { switch strings.ToLower(typ) { case "default", "configurable": - c = NewConfigurableFromConfig(log, other) + charger, err = NewConfigurableFromConfig(other) case "wallbe": - c = NewWallbeFromConfig(log, other) + charger, err = NewWallbeFromConfig(other) case "phoenix-emcp": - c = NewPhoenixEMCPFromConfig(log, other) + charger, err = NewPhoenixEMCPFromConfig(other) case "phoenix-evcc": - c = NewPhoenixEVCCFromConfig(log, other) + charger, err = NewPhoenixEVCCFromConfig(other) case "nrgkick-bluetooth", "nrgkick-bt", "nrgble": - c = NewNRGKickBLEFromConfig(log, other) + charger, err = NewNRGKickBLEFromConfig(other) case "nrgkick-connect", "nrgconnect": - c = NewNRGKickConnectFromConfig(log, other) + charger, err = NewNRGKickConnectFromConfig(other) case "go-e", "goe": - c = NewGoEFromConfig(log, other) + charger, err = NewGoEFromConfig(other) case "evsewifi": - c = NewEVSEWifiFromConfig(log, other) + charger, err = NewEVSEWifiFromConfig(other) case "simpleevse", "evse": - c = NewSimpleEVSEFromConfig(log, other) + charger, err = NewSimpleEVSEFromConfig(other) case "porsche", "audi", "bentley", "mcc": - c = NewMobileConnectFromConfig(log, other) + charger, err = NewMobileConnectFromConfig(other) case "keba", "bmw": - c = NewKebaFromConfig(log, other) + charger, err = NewKebaFromConfig(other) default: - log.FATAL.Fatalf("invalid charger type '%s'", typ) + return nil, fmt.Errorf("invalid charger type: %s", typ) } - return c + return charger, err } diff --git a/charger/evsewifi.go b/charger/evsewifi.go index 9f7bf6f1da..c25f42a124 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -50,21 +50,23 @@ type EVSEWifi struct { } // NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config -func NewEVSEWifiFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI string }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } return NewEVSEWifi(cc.URI) } // NewEVSEWifi creates EVSEWifi charger -func NewEVSEWifi(uri string) api.Charger { +func NewEVSEWifi(uri string) (api.Charger, error) { evse := &EVSEWifi{ HTTPHelper: util.NewHTTPHelper(util.NewLogger("wifi")), uri: strings.TrimRight(uri, "/"), } - return evse + return evse, nil } func (evse *EVSEWifi) apiURL(service apiFunction) string { diff --git a/charger/go-e.go b/charger/go-e.go index 14ed2ff37e..5cf9456710 100644 --- a/charger/go-e.go +++ b/charger/go-e.go @@ -45,33 +45,35 @@ type GoE struct { } // NewGoEFromConfig creates a go-e charger from generic config -func NewGoEFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewGoEFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { Token string URI string Cache time.Duration }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } if cc.URI != "" && cc.Token != "" { - log.FATAL.Fatal("config: should only have one of uri/token") + return nil, errors.New("go-e config: should only have one of uri/token") } if cc.URI == "" && cc.Token == "" { - log.FATAL.Fatal("config: must have one of uri/token") + return nil, errors.New("go-e config: must have one of uri/token") } return NewGoE(cc.URI, cc.Token, cc.Cache) } // NewGoE creates GoE charger -func NewGoE(uri, token string, cache time.Duration) *GoE { +func NewGoE(uri, token string, cache time.Duration) (*GoE, error) { c := &GoE{ HTTPHelper: util.NewHTTPHelper(util.NewLogger("go-e")), uri: strings.TrimRight(uri, "/"), token: strings.TrimSpace(token), } - return c + return c, nil } func (c *GoE) localResponse(function, payload string) (goeStatusResponse, error) { diff --git a/charger/go-e-test.go b/charger/go-e_test.go similarity index 77% rename from charger/go-e-test.go rename to charger/go-e_test.go index 33bf5c71a2..db577aba09 100644 --- a/charger/go-e-test.go +++ b/charger/go-e_test.go @@ -8,7 +8,11 @@ import ( // TestGoE tests interfaces func TestGoE(t *testing.T) { - var wb api.Charger = NewGoE("foo", "bar", 0) + var wb api.Charger + wb, err := NewGoE("foo", "bar", 0) + if err != nil { + t.Error(err) + } if _, ok := wb.(api.MeterCurrent); !ok { t.Error("missing MeterCurrent interface") diff --git a/charger/keba.go b/charger/keba.go index 8a9e963c11..f886c8d28d 100644 --- a/charger/keba.go +++ b/charger/keba.go @@ -37,19 +37,21 @@ type Keba struct { } // NewKebaFromConfig creates a new configurable charger -func NewKebaFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { URI string Timeout time.Duration RFID RFID }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } return NewKeba(cc.URI, cc.RFID, cc.Timeout) } // NewKeba creates a new charger -func NewKeba(conn string, rfid RFID, timeout time.Duration) api.Charger { +func NewKeba(conn string, rfid RFID, timeout time.Duration) (api.Charger, error) { log := util.NewLogger("keba") if keba.Instance == nil { @@ -75,7 +77,7 @@ func NewKeba(conn string, rfid RFID, timeout time.Duration) api.Charger { keba.Instance.Subscribe(conn, c.recv) - return c + return c, nil } func (c *Keba) send(msg string) error { diff --git a/charger/keba_test.go b/charger/keba_test.go index 19f8910653..9816e5e4dc 100644 --- a/charger/keba_test.go +++ b/charger/keba_test.go @@ -7,7 +7,11 @@ import ( ) func TestKeba(t *testing.T) { - var wb api.Charger = NewKeba("foo", RFID{}, 0) + var wb api.Charger + wb, err := NewKeba("foo", RFID{}, 0) + if err != nil { + t.Error(err) + } if _, ok := wb.(api.MeterEnergy); !ok { t.Error("missing MeterEnergy interface") diff --git a/charger/mcc.go b/charger/mcc.go index 944ca4a465..61b6001af9 100644 --- a/charger/mcc.go +++ b/charger/mcc.go @@ -68,17 +68,19 @@ type MobileConnect struct { } // NewMobileConnectFromConfig creates a MCC charger from generic config -func NewMobileConnectFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewMobileConnectFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI, Password string }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } return NewMobileConnect(cc.URI, cc.Password) } // NewMobileConnect creates MCC charger -func NewMobileConnect(uri string, password string) *MobileConnect { +func NewMobileConnect(uri string, password string) (*MobileConnect, error) { mcc := &MobileConnect{ - HTTPHelper: util.NewHTTPHelper(util.NewLogger("mcc ")), + HTTPHelper: util.NewHTTPHelper(util.NewLogger("mcc")), uri: strings.TrimRight(uri, "/"), password: password, } @@ -89,7 +91,7 @@ func NewMobileConnect(uri string, password string) *MobileConnect { mcc.HTTPHelper.Client.Transport = customTransport - return mcc + return mcc, nil } // construct the URL for a given apiFunction diff --git a/charger/nrgble.go b/charger/nrgble.go index ae9fa064a2..87df8b0cb8 100644 --- a/charger/nrgble.go +++ b/charger/nrgble.go @@ -3,12 +3,12 @@ package charger import ( + "errors" + "github.com/andig/evcc/api" - "github.com/andig/evcc/util" ) // NewNRGKickBLEFromConfig creates a NRGKickBLE charger from generic config -func NewNRGKickBLEFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { - log.FATAL.Fatal("config: NRGKick bluetooth is only supported on linux") - return nil +func NewNRGKickBLEFromConfig(other map[string]interface{}) (api.Charger, error) { + return nil, errors.New("NRGKick bluetooth is only supported on linux") } diff --git a/charger/nrgble_linux.go b/charger/nrgble_linux.go index b9ad2b1ce5..127b57e12f 100644 --- a/charger/nrgble_linux.go +++ b/charger/nrgble_linux.go @@ -2,6 +2,7 @@ package charger import ( "bytes" + "errors" "fmt" "os" "strconv" @@ -35,23 +36,25 @@ type NRGKickBLE struct { } // NewNRGKickBLEFromConfig creates a NRGKickBLE charger from generic config -func NewNRGKickBLEFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewNRGKickBLEFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ Device, MacAddress, PIN string }{ Device: "hci0", } - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } // decode PIN with leading zero pin, err := strconv.Atoi(cc.PIN) if err != nil { - log.FATAL.Fatalf("config: invalid pin '%s'", cc.PIN) + return nil, errors.New("config: invalid pin '%s'", cc.PIN) } return NewNRGKickBLE(cc.Device, cc.MacAddress, pin) } // NewNRGKickBLE creates NRGKickBLE charger -func NewNRGKickBLE(device, macaddress string, pin int) *NRGKickBLE { +func NewNRGKickBLE(device, macaddress string, pin int) (*NRGKickBLE, error) { logger := util.NewLogger("nrg-bt") // set LE mode @@ -73,18 +76,18 @@ func NewNRGKickBLE(device, macaddress string, pin int) *NRGKickBLE { } if err != nil { - logger.FATAL.Fatal(err) + return nil, err } adapt, err := adapter.NewAdapter1FromAdapterID(device) if err != nil { - logger.FATAL.Fatal(err) + return nil, err } //Connect DBus System bus conn, err := dbus.SystemBus() if err != nil { - logger.FATAL.Fatal(err) + return nil, err } // do not reuse agent0 from service @@ -93,7 +96,7 @@ func NewNRGKickBLE(device, macaddress string, pin int) *NRGKickBLE { ag := agent.NewSimpleAgent() err = agent.ExposeAgent(conn, ag, agent.CapNoInputNoOutput, true) if err != nil { - logger.FATAL.Fatal(err) + return nil, err } nrg := &NRGKickBLE{ @@ -106,7 +109,7 @@ func NewNRGKickBLE(device, macaddress string, pin int) *NRGKickBLE { agent: ag, } - return nrg + return nrg, nil } func (nrg *NRGKickBLE) connect() (*device.Device1, error) { diff --git a/charger/nrgconnect.go b/charger/nrgconnect.go index 7aca1098b6..3114c48944 100644 --- a/charger/nrgconnect.go +++ b/charger/nrgconnect.go @@ -70,17 +70,20 @@ type NRGKickConnect struct { } // NewNRGKickConnectFromConfig creates a NRGKickConnect charger from generic config -func NewNRGKickConnectFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewNRGKickConnectFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ IP, MacAddress, Password string }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } return NewNRGKickConnect(cc.IP, cc.MacAddress, cc.Password) } // NewNRGKickConnect creates NRGKickConnect charger -func NewNRGKickConnect(IP, MacAddress, Password string) *NRGKickConnect { +func NewNRGKickConnect(IP, MacAddress, Password string) (*NRGKickConnect, error) { + log := util.NewLogger("nrgconn") nrg := &NRGKickConnect{ - HTTPHelper: util.NewHTTPHelper(util.NewLogger("nrgc")), + HTTPHelper: util.NewHTTPHelper(log), IP: IP, MacAddress: MacAddress, Password: Password, @@ -88,7 +91,7 @@ func NewNRGKickConnect(IP, MacAddress, Password string) *NRGKickConnect { nrg.HTTPHelper.Log.WARN.Println("-- experimental --") - return nrg + return nrg, nil } func (nrg *NRGKickConnect) apiURL(api apiFunction) string { diff --git a/charger/nrgconnect_test.go b/charger/nrgconnect_test.go index 04dd6f509b..5e06dbafbe 100644 --- a/charger/nrgconnect_test.go +++ b/charger/nrgconnect_test.go @@ -7,7 +7,11 @@ import ( ) func TestNRGKickConnect(t *testing.T) { - var wb api.Charger = NewNRGKickConnect("foo", "bar", "baz") + var wb api.Charger + wb, err := NewNRGKickConnect("foo", "bar", "baz") + if err != nil { + t.Error(err) + } if _, ok := wb.(api.MeterEnergy); !ok { t.Error("missing MeterEnergy interface") diff --git a/charger/phoenix-emcp.go b/charger/phoenix-emcp.go index 60fa9ce646..174478f6ae 100644 --- a/charger/phoenix-emcp.go +++ b/charger/phoenix-emcp.go @@ -26,27 +26,27 @@ type PhoenixEMCP struct { } // NewPhoenixEMCPFromConfig creates a Phoenix charger from generic config -func NewPhoenixEMCPFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewPhoenixEMCPFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct { URI string ID uint8 - }{} - util.DecodeOther(log, other, &cc) + }{ + ID: 180, // default + } - if _, _, err := net.SplitHostPort(cc.URI); err != nil { - log.FATAL.Printf("config: missing or invalid phoenix EM-CP uri: %s", cc.URI) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err } - if cc.ID == 0 { - cc.ID = 180 - log.WARN.Printf("config: missing phoenix EM-CP slave id, assuming default %d", cc.ID) + if _, _, err := net.SplitHostPort(cc.URI); err != nil { + return nil, fmt.Errorf("config: missing or invalid phoenix EM-CP uri: %s", cc.URI) } return NewPhoenixEMCP(cc.URI, cc.ID) } // NewPhoenixEMCP creates a Phoenix charger -func NewPhoenixEMCP(uri string, id uint8) api.Charger { +func NewPhoenixEMCP(uri string, id uint8) (api.Charger, error) { log := util.NewLogger("emcp") handler := modbus.NewTCPClientHandler(uri) @@ -62,7 +62,7 @@ func NewPhoenixEMCP(uri string, id uint8) api.Charger { handler: handler, } - return wb + return wb, nil } // Status implements the Charger.Status interface diff --git a/charger/phoenix-evcc.go b/charger/phoenix-evcc.go index fd40be2cc8..99657ad987 100644 --- a/charger/phoenix-evcc.go +++ b/charger/phoenix-evcc.go @@ -25,22 +25,24 @@ type PhoenixEVCC struct { } // NewPhoenixEVCCFromConfig creates a Phoenix charger from generic config -func NewPhoenixEVCCFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { - var cc modbus.Connection - util.DecodeOther(log, other, &cc) - - if cc.ID == 0 { - cc.ID = 255 - log.WARN.Printf("config: missing phoenix EV-CC slave id, assuming default %d", cc.ID) +func NewPhoenixEVCCFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := modbus.Connection{ID: 255} + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err } return NewPhoenixEVCC(cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.ID) } // NewPhoenixEVCC creates a Phoenix charger -func NewPhoenixEVCC(uri, device, comset string, baudrate int, id uint8) api.Charger { +func NewPhoenixEVCC(uri, device, comset string, baudrate int, id uint8) (api.Charger, error) { log := util.NewLogger("evcc") - conn := modbus.NewConnection(log, uri, device, comset, baudrate, true) + + conn, err := modbus.NewConnection(uri, device, comset, baudrate, true) + if err != nil { + return nil, err + } + conn.Slave(id) wb := &PhoenixEVCC{ @@ -49,7 +51,7 @@ func NewPhoenixEVCC(uri, device, comset string, baudrate int, id uint8) api.Char handler: conn, } - return wb + return wb, nil } // Status implements the Charger.Status interface diff --git a/charger/simpleevse.go b/charger/simpleevse.go index c506427981..d18c216a42 100644 --- a/charger/simpleevse.go +++ b/charger/simpleevse.go @@ -23,15 +23,17 @@ const ( ) // NewSimpleEVSEFromConfig creates a SimpleEVSE charger from generic config -func NewSimpleEVSEFromConfig(log *util.Logger, other map[string]interface{}) api.Charger { +func NewSimpleEVSEFromConfig(other map[string]interface{}) (api.Charger, error) { cc := struct{ URI, Device string }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } return NewSimpleEVSE(cc.URI, cc.Device) } // NewSimpleEVSE creates SimpleEVSE charger -func NewSimpleEVSE(conn, device string) api.Charger { +func NewSimpleEVSE(conn, device string) (api.Charger, error) { log := util.NewLogger("evse") var handler modbus.ClientHandler @@ -53,7 +55,7 @@ func NewSimpleEVSE(conn, device string) api.Charger { handler.(*modbus.RTUClientHandler).SlaveID = 1 } if handler == nil { - log.FATAL.Fatal("must define either uri or device") + return nil, errors.New("must define either uri or device") } evse := &SimpleEVSE{ @@ -64,7 +66,7 @@ func NewSimpleEVSE(conn, device string) api.Charger { evse.log.WARN.Println("-- experimental --") - return evse + return evse, nil } // Status implements the Charger.Status interface diff --git a/charger/wallbe.go b/charger/wallbe.go index 94931d5dc4..42688ea1f9 100644 --- a/charger/wallbe.go +++ b/charger/wallbe.go @@ -1,9 +1,7 @@ package charger import ( - "encoding/binary" "fmt" - "net" "time" "github.com/andig/evcc/api" @@ -36,16 +34,16 @@ type Wallbe struct { } // NewWallbeFromConfig creates a Wallbe charger from generic config -func NewWallbeFromConfig(log *util.Logger, other map[string]interface{}) *Wallbe { +func NewWallbeFromConfig(other map[string]interface{}) (*Wallbe, error) { cc := struct { URI string Legacy bool - }{} - util.DecodeOther(log, other, &cc) + }{ + URI: "192.168.0.8:502", + } - if _, _, err := net.SplitHostPort(cc.URI); err != nil { - cc.URI = "192.168.0.8:502" - log.WARN.Printf("config: missing or invalid wallbe uri, using default %s", cc.URI) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err } wb := NewWallbe(cc.URI) @@ -54,7 +52,7 @@ func NewWallbeFromConfig(log *util.Logger, other map[string]interface{}) *Wallbe wb.factor = 1 } - return wb + return wb, nil } // NewWallbe creates a Wallbe charger @@ -73,55 +71,9 @@ func NewWallbe(conn string) *Wallbe { factor: 10, } - // wb.showIOs() - return wb } -// showIOs logs all input/output register values and their configurations -func (wb *Wallbe) showIOs() { - // inputs - wb.showIO("LD", 520, 200) // 200 = EN? - wb.showIO("EN", 521, 201) // 201 = XR? - wb.showIO("ML", 522, 202) // 202 = LD? - wb.showIO("XR", 523, 203) // 203 = ML? - wb.showIO("IN", 524, 208) - - // outputs - wb.showIO("ER", 327, 204) - wb.showIO("LR", 328, 205) - wb.showIO("VR", 329, 206) - wb.showIO("CR", 330, 207) - - if b, err := wb.client.ReadHoldingRegisters(390, 1); err != nil { - wb.log.FATAL.Printf("%s definition %v", "Schütz", err) - } else { - wb.log.DEBUG.Printf("%s (%d)", "Schütz", binary.BigEndian.Uint16(b)) - } -} - -// showIOs logs a single input/output register's values and their configurations -func (wb *Wallbe) showIO(input string, definition uint16, status uint16) { - var def uint16 - var val byte - - b, err := wb.client.ReadHoldingRegisters(definition, 1) - if err != nil { - wb.log.FATAL.Printf("%s definition %v", input, err) - return - } - def = binary.BigEndian.Uint16(b) - - b, err = wb.client.ReadDiscreteInputs(status, 1) - if err != nil { - wb.log.FATAL.Printf("%s status %v", input, err) - return - } - val = b[0] - - wb.log.DEBUG.Printf("%s = %d (%d)", input, val, def) -} - // Status implements the Charger.Status interface func (wb *Wallbe) Status() (api.ChargeStatus, error) { b, err := wb.client.ReadInputRegisters(wbRegStatus, 1) diff --git a/charger/wallbe_test.go b/charger/wallbe_test.go index d3a31a08af..757a730269 100644 --- a/charger/wallbe_test.go +++ b/charger/wallbe_test.go @@ -2,18 +2,22 @@ package charger import ( "testing" - - "github.com/andig/evcc/util" ) func TestWallbe(t *testing.T) { - wb := NewWallbeFromConfig(util.NewLogger(""), nil) + wb, err := NewWallbeFromConfig(nil) + if err != nil { + t.Error(err) + } if wb.factor != 10 { t.Errorf("invalid factor: %d", wb.factor) } - wb = NewWallbeFromConfig(util.NewLogger(""), map[string]interface{}{"legacy": true}) + wb, err = NewWallbeFromConfig(map[string]interface{}{"legacy": true}) + if err != nil { + t.Error(err) + } if wb.factor != 1 { t.Errorf("invalid factor: %d", wb.factor) diff --git a/cmd/config.go b/cmd/config.go index 9e6b9c4fe9..c429d16d99 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -51,8 +51,8 @@ type ConfigProvider struct { } // Meter provides meters by name -func (c *ConfigProvider) Meter(name string) api.Meter { - if meter, ok := c.meters[name]; ok { +func (cp *ConfigProvider) Meter(name string) api.Meter { + if meter, ok := cp.meters[name]; ok { return meter } log.FATAL.Fatalf("config: invalid meter %s", name) @@ -60,8 +60,8 @@ func (c *ConfigProvider) Meter(name string) api.Meter { } // Charger provides chargers by name -func (c *ConfigProvider) Charger(name string) api.Charger { - if charger, ok := c.chargers[name]; ok { +func (cp *ConfigProvider) Charger(name string) api.Charger { + if charger, ok := cp.chargers[name]; ok { return charger } log.FATAL.Fatalf("config: invalid charger %s", name) @@ -69,37 +69,49 @@ func (c *ConfigProvider) Charger(name string) api.Charger { } // Vehicle provides vehicles by name -func (c *ConfigProvider) Vehicle(name string) api.Vehicle { - if vehicle, ok := c.vehicles[name]; ok { +func (cp *ConfigProvider) Vehicle(name string) api.Vehicle { + if vehicle, ok := cp.vehicles[name]; ok { return vehicle } log.FATAL.Fatalf("config: invalid vehicle %s", name) return nil } -func (c *ConfigProvider) configure(conf config) { - c.configureMeters(conf) - c.configureChargers(conf) - c.configureVehicles(conf) +func (cp *ConfigProvider) configure(conf config) { + cp.configureMeters(conf) + cp.configureChargers(conf) + cp.configureVehicles(conf) } -func (c *ConfigProvider) configureMeters(conf config) { - c.meters = make(map[string]api.Meter) +func (cp *ConfigProvider) configureMeters(conf config) { + cp.meters = make(map[string]api.Meter) for _, cc := range conf.Meters { - c.meters[cc.Name] = meter.NewFromConfig(log, cc.Type, cc.Other) + m, err := meter.NewFromConfig(cc.Type, cc.Other) + if err != nil { + log.FATAL.Fatal(err) + } + cp.meters[cc.Name] = m } } -func (c *ConfigProvider) configureChargers(conf config) { - c.chargers = make(map[string]api.Charger) +func (cp *ConfigProvider) configureChargers(conf config) { + cp.chargers = make(map[string]api.Charger) for _, cc := range conf.Chargers { - c.chargers[cc.Name] = charger.NewFromConfig(log, cc.Type, cc.Other) + c, err := charger.NewFromConfig(cc.Type, cc.Other) + if err != nil { + log.FATAL.Fatal(err) + } + cp.chargers[cc.Name] = c } } -func (c *ConfigProvider) configureVehicles(conf config) { - c.vehicles = make(map[string]api.Vehicle) +func (cp *ConfigProvider) configureVehicles(conf config) { + cp.vehicles = make(map[string]api.Vehicle) for _, cc := range conf.Vehicles { - c.vehicles[cc.Name] = vehicle.NewFromConfig(log, cc.Type, cc.Other) + v, err := vehicle.NewFromConfig(cc.Type, cc.Other) + if err != nil { + log.FATAL.Fatal(err) + } + cp.vehicles[cc.Name] = v } } diff --git a/cmd/setup.go b/cmd/setup.go index 9a7bc88bb3..b4e688a88b 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -55,7 +55,10 @@ func configureMessengers(conf messagingConfig, cache *util.Cache) chan push.Even notificationHub := push.NewHub(conf.Events, cache) for _, service := range conf.Services { - impl := push.NewMessengerFromConfig(service.Type, service.Other) + impl, err := push.NewMessengerFromConfig(service.Type, service.Other) + if err != nil { + log.FATAL.Fatal(err) + } notificationHub.Add(impl) } @@ -87,7 +90,9 @@ func configureLoadPoints(conf config, cp *ConfigProvider) (loadPoints []*core.Lo // decode slice into slice of maps var lpc []map[string]interface{} - util.DecodeOther(log, lps, &lpc) + if err := util.DecodeOther(lps, &lpc); err != nil { + log.FATAL.Fatal(err) + } for id, lpc := range lpc { log := util.NewLogger("lp-" + strconv.Itoa(id+1)) diff --git a/core/loadpoint.go b/core/loadpoint.go index 8526fbd919..3531bbea93 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -89,7 +89,9 @@ type LoadPoint struct { // NewLoadPointFromConfig creates a new loadpoint func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[string]interface{}) *LoadPoint { lp := NewLoadPoint(log) - util.DecodeOther(log, other, &lp) + if err := util.DecodeOther(other, &lp); err != nil { + log.FATAL.Fatal(err) + } // workaround mapstructure if lp.Mode == "0" { diff --git a/core/site.go b/core/site.go index 66436848a5..a15c3a25eb 100644 --- a/core/site.go +++ b/core/site.go @@ -59,7 +59,9 @@ func NewSiteFromConfig( loadpoints []*LoadPoint, ) *Site { site := NewSite() - util.DecodeOther(log, other, &site) + if err := util.DecodeOther(other, &site); err != nil { + log.FATAL.Fatal(err) + } Voltage = site.Voltage site.loadpoints = loadpoints diff --git a/core/wrapper/chargerater_test.go b/core/wrapper/chargerater_test.go index e55ba0e051..0f75809827 100644 --- a/core/wrapper/chargerater_test.go +++ b/core/wrapper/chargerater_test.go @@ -45,7 +45,7 @@ func TestWrappedMeter(t *testing.T) { mm := mock.NewMockMeter(ctrl) me := mock.NewMockMeterEnergy(ctrl) - cm := &meter.MeterEnergyDecorator{Meter: mm, MeterEnergy: me} + cm := &meter.EnergyDecorator{Meter: mm, MeterEnergy: me} me.EXPECT(). TotalEnergy(). diff --git a/go.mod b/go.mod index 2c3cd5376d..80fa724e30 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-20200728142959-b798a8b6b384 + github.com/andig/evcc-config v0.0.0-20200729105416-7fbb6e0605cc 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 cdeb2613c3..74b8b4f1f5 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,14 @@ github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT github.com/andig/evcc v0.0.0-20200727161511-d58eb15f2dc9/go.mod h1:8HONEC6cC2s4k0u3QL7GIjrYOZYTOKiiXybw0FIJL0A= github.com/andig/evcc-config v0.0.0-20200728142959-b798a8b6b384 h1:ol6wEK2NJCIKjyVk3cyQsVKsi1QaVm77XlMNKTLidQA= github.com/andig/evcc-config v0.0.0-20200728142959-b798a8b6b384/go.mod h1:N0hIjIy+5E2AR1fF7Tg2IzBlblBrnFvCCaDGAaHzbWk= +github.com/andig/evcc-config v0.0.0-20200729061908-dff8e0408168 h1:8UjWHD6HTTcTpWDD0pYlCUXaOUUe5R582EtqLy3fPig= +github.com/andig/evcc-config v0.0.0-20200729061908-dff8e0408168/go.mod h1:N0hIjIy+5E2AR1fF7Tg2IzBlblBrnFvCCaDGAaHzbWk= +github.com/andig/evcc-config v0.0.0-20200729092413-106691a8c13d h1:0bcJtdJmdhUfuOkEIsa7bP+eFVXXAN6W0C/AyKZhIAI= +github.com/andig/evcc-config v0.0.0-20200729092413-106691a8c13d/go.mod h1:N0hIjIy+5E2AR1fF7Tg2IzBlblBrnFvCCaDGAaHzbWk= +github.com/andig/evcc-config v0.0.0-20200729093325-899f5b7c2247 h1:Xv3LS07jQriFZTSZnqkvVj5QS82oy8Io1+cGF49DNl4= +github.com/andig/evcc-config v0.0.0-20200729093325-899f5b7c2247/go.mod h1:N0hIjIy+5E2AR1fF7Tg2IzBlblBrnFvCCaDGAaHzbWk= +github.com/andig/evcc-config v0.0.0-20200729105416-7fbb6e0605cc h1:G+Zpx2yBwqGVZPrUMm8CXpb9sOavQlH9d1CS++cV4SY= +github.com/andig/evcc-config v0.0.0-20200729105416-7fbb6e0605cc/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 2337a124cc..a8f3c9e3b6 100644 --- a/meter/config.go +++ b/meter/config.go @@ -1,28 +1,26 @@ package meter import ( + "fmt" "strings" "github.com/andig/evcc/api" - "github.com/andig/evcc/util" ) // NewFromConfig creates meter from configuration -func NewFromConfig(log *util.Logger, typ string, other map[string]interface{}) api.Meter { - var c api.Meter - +func NewFromConfig(typ string, other map[string]interface{}) (meter api.Meter, err error) { switch strings.ToLower(typ) { case "default", "configurable": - c = NewConfigurableFromConfig(log, other) + meter, err = NewConfigurableFromConfig(other) case "modbus": - c = NewModbusFromConfig(log, other) + meter, err = NewModbusFromConfig(other) case "sma": - c = NewSMAFromConfig(log, other) + meter, err = NewSMAFromConfig(other) case "tesla", "powerwall": - c = NewTeslaFromConfig(log, other) + meter, err = NewTeslaFromConfig(other) default: - log.FATAL.Fatalf("invalid meter type '%s'", typ) + err = fmt.Errorf("invalid meter type: %s", typ) } - return c + return meter, err } diff --git a/meter/meter.go b/meter/meter.go index 182d4ad61b..f6ee63ac63 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -1,49 +1,65 @@ package meter import ( + "fmt" + "github.com/andig/evcc/api" "github.com/andig/evcc/provider" "github.com/andig/evcc/util" ) -// MeterEnergyDecorator decorates an api.Meter with api.MeterEnergy -type MeterEnergyDecorator struct { +// EnergyDecorator decorates an api.Meter with api.MeterEnergy +type EnergyDecorator struct { api.Meter api.MeterEnergy } // NewConfigurableFromConfig creates api.Meter from config -func NewConfigurableFromConfig(log *util.Logger, other map[string]interface{}) api.Meter { +func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { Power provider.Config Energy *provider.Config // optional }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } for k, v := range map[string]string{"power": cc.Power.Type} { if v == "" { - log.FATAL.Fatalf("default meter config: %s required", k) + return nil, fmt.Errorf("default meter config: %s required", k) } } - m := NewConfigurable(provider.NewFloatGetterFromConfig(log, cc.Power)) + power, err := provider.NewFloatGetterFromConfig(cc.Power) + if err != nil { + return nil, err + } + + m, _ := NewConfigurable(power) // decorate Meter with MeterEnergy if cc.Energy != nil { - m = &MeterEnergyDecorator{ + energy, err := provider.NewFloatGetterFromConfig(*cc.Energy) + if err != nil { + return nil, err + } + + m = &EnergyDecorator{ Meter: m, - MeterEnergy: NewMeterEnergy(provider.NewFloatGetterFromConfig(log, *cc.Energy)), + MeterEnergy: NewMeterEnergy(energy), } } - return m + return m, nil } // NewConfigurable creates a new charger -func NewConfigurable(currentPowerG func() (float64, error)) api.Meter { - return &Meter{ +func NewConfigurable(currentPowerG func() (float64, error)) (api.Meter, error) { + m := &Meter{ currentPowerG: currentPowerG, } + return m, nil } // Meter is an api.Meter implementation with configurable getters and setters. diff --git a/meter/modbus.go b/meter/modbus.go index 07b60e3e93..f63c0620be 100644 --- a/meter/modbus.go +++ b/meter/modbus.go @@ -20,12 +20,15 @@ type Modbus struct { } // NewModbusFromConfig creates api.Meter from config -func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) api.Meter { +func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { modbus.Settings `mapstructure:",squash"` Power, Energy string }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } // assume RTU if not set and this is a known RS485 meter model if cc.RTU == nil { @@ -33,13 +36,17 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) api.Met cc.RTU = &b } - conn := modbus.NewConnection(log, cc.URI, cc.Device, cc.Comset, cc.Baudrate, *cc.RTU) - device, err := modbus.NewDevice(log, cc.Model, *cc.RTU) + log := util.NewLogger("modbus") - log = util.NewLogger("modbus") + conn, err := modbus.NewConnection(cc.URI, cc.Device, cc.Comset, cc.Baudrate, *cc.RTU) conn.Logger(log.TRACE) // prepare device + var device meters.Device + if err == nil { + device, err = modbus.NewDevice(cc.Model, *cc.RTU) + } + if err == nil { conn.Slave(cc.ID) err = device.Initialize(conn.ModbusClient()) @@ -49,8 +56,9 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) api.Met err = nil } } + if err != nil { - log.FATAL.Fatal(err) + return nil, err } m := &Modbus{ @@ -75,10 +83,10 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) api.Met log.FATAL.Fatalf("invalid measurement for energy: %s", cc.Power) } - return &ModbusEnergy{m} + return &ModbusEnergy{m}, nil } - return m + return m, nil } // floatGetter executes configured modbus read operation and implements func() (float64, error) diff --git a/meter/sma.go b/meter/sma.go index 8ca93cad10..83fbe41cfb 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -1,6 +1,7 @@ package meter import ( + "errors" "fmt" "time" @@ -33,17 +34,20 @@ type SMA struct { } // NewSMAFromConfig creates a SMA Meter from generic config -func NewSMAFromConfig(log *util.Logger, other map[string]interface{}) api.Meter { +func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { URI, Serial, Power, Energy string }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } return NewSMA(cc.URI, cc.Serial, cc.Power, cc.Energy) } // NewSMA creates a SMA Meter -func NewSMA(uri, serial, power, energy string) api.Meter { +func NewSMA(uri, serial, power, energy string) (api.Meter, error) { log := util.NewLogger("sma") sm := &SMA{ @@ -66,17 +70,17 @@ func NewSMA(uri, serial, power, energy string) api.Meter { } else if serial != "" { sma.Instance.Subscribe(serial, sm.recv) } else { - log.FATAL.Fatalf("config: missing uri or serial") + return nil, errors.New("config: missing uri or serial") } go sm.receive() // decorate api.MeterEnergy if energy != "" { - return &SMAEnergy{SMA: sm} + return &SMAEnergy{SMA: sm}, nil } - return sm + return sm, nil } // update the actual meter data diff --git a/meter/sma/listener_test.go b/meter/sma/listener_test.go index 56b0552ab3..06dc071499 100644 --- a/meter/sma/listener_test.go +++ b/meter/sma/listener_test.go @@ -189,7 +189,7 @@ func TestListenerProcessMessage(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { l := &Listener{ - log: util.NewLogger("sma "), + log: util.NewLogger("foo"), } buffer := tc.response diff --git a/meter/sma_test.go b/meter/sma_test.go index 30595c16d3..11c4301e74 100644 --- a/meter/sma_test.go +++ b/meter/sma_test.go @@ -52,7 +52,7 @@ func TestSMAUpdateMeterValues(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sm := &SMA{ - log: util.NewLogger("sma "), + log: util.NewLogger("foo"), mux: util.NewWaiter(udpTimeout, func() {}), } diff --git a/meter/tesla.go b/meter/tesla.go index 8bfb2bdb82..a25db275b7 100644 --- a/meter/tesla.go +++ b/meter/tesla.go @@ -2,6 +2,7 @@ package meter import ( "crypto/tls" + "errors" "fmt" "net/http" "net/url" @@ -35,19 +36,22 @@ type Tesla struct { } // NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config -func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Meter { +func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { URI, Usage string }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } if cc.Usage == "" { - log.FATAL.Fatalf("config: missing usage") + return nil, errors.New("config: missing usage") } url, err := url.ParseRequestURI(cc.URI) if err != nil { - log.FATAL.Fatalf("config: invalid uri %s", cc.URI) + return nil, fmt.Errorf("config: invalid uri %s", cc.URI) } if url.Path == "" { @@ -59,7 +63,7 @@ func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Mete } // NewTesla creates a Tesla Meter -func NewTesla(uri, usage string) api.Meter { +func NewTesla(uri, usage string) (api.Meter, error) { m := &Tesla{ HTTPHelper: util.NewHTTPHelper(util.NewLogger("tesla")), uri: uri, @@ -73,10 +77,10 @@ func NewTesla(uri, usage string) api.Meter { // decorate api.MeterEnergy if m.usage == "load" || m.usage == "solar" { - return &TeslaEnergy{Tesla: m} + return &TeslaEnergy{Tesla: m}, nil } - return m + return m, nil } // CurrentPower implements the Meter.CurrentPower interface diff --git a/provider/cache.go b/provider/cache.go index cebd528f7d..a35988725f 100644 --- a/provider/cache.go +++ b/provider/cache.go @@ -19,9 +19,9 @@ type Cached struct { } // NewCached wraps a getter with a cache -func NewCached(log *util.Logger, getter interface{}, cache time.Duration) *Cached { +func NewCached(getter interface{}, cache time.Duration) *Cached { return &Cached{ - log: log, + log: util.NewLogger("cache"), clock: clock.New(), getter: getter, cache: cache, diff --git a/provider/cache_test.go b/provider/cache_test.go index 4eab785bc6..c0683eb8f9 100644 --- a/provider/cache_test.go +++ b/provider/cache_test.go @@ -27,7 +27,7 @@ func TestCachedGetter(t *testing.T) { } duration := time.Second - c := NewCached(nil, g, duration) + c := NewCached(g, duration) clck := clock.NewMock() c.clock = clck getter := c.FloatGetter() diff --git a/provider/calc.go b/provider/calc.go index eec4e9649a..ea4dc50f09 100644 --- a/provider/calc.go +++ b/provider/calc.go @@ -9,19 +9,26 @@ type calcProvider struct { } // NewCalcFromConfig creates calc provider -func NewCalcFromConfig(log *util.Logger, other map[string]interface{}) func() (float64, error) { +func NewCalcFromConfig(other map[string]interface{}) (func() (float64, error), error) { cc := struct { Add []Config }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } o := &calcProvider{} for _, cc := range cc.Add { - o.add = append(o.add, NewFloatGetterFromConfig(log, cc)) + f, err := NewFloatGetterFromConfig(cc) + if err != nil { + return nil, err + } + o.add = append(o.add, f) } - return o.floatGetter + return o.floatGetter, nil } func (o *calcProvider) floatGetter() (float64, error) { diff --git a/provider/config.go b/provider/config.go index 2dfa40e9df..74a5908c60 100644 --- a/provider/config.go +++ b/provider/config.go @@ -1,10 +1,12 @@ package provider import ( + "fmt" "strings" "time" "github.com/andig/evcc/util" + "github.com/pkg/errors" ) const ( @@ -34,164 +36,257 @@ type scriptConfig struct { // MQTT singleton var MQTT *MqttClient -func mqttFromConfig(log *util.Logger, other map[string]interface{}) mqttConfig { - if MQTT == nil { - log.FATAL.Fatal("mqtt not configured") +func mqttFromConfig(other map[string]interface{}) (mqttConfig, error) { + pc := mqttConfig{Scale: 1} + if err := util.DecodeOther(other, &pc); err != nil { + return pc, err } - var pc mqttConfig - util.DecodeOther(log, other, &pc) - - if pc.Scale == 0 { - pc.Scale = 1 + if MQTT == nil { + return pc, errors.New("mqtt not configured") } - return pc + return pc, nil } -func scriptFromConfig(log *util.Logger, other map[string]interface{}) scriptConfig { +func scriptFromConfig(other map[string]interface{}) (scriptConfig, error) { var pc scriptConfig - util.DecodeOther(log, other, &pc) + if err := util.DecodeOther(other, &pc); err != nil { + return pc, err + } if pc.Timeout == 0 { pc.Timeout = execTimeout } - return pc + return pc, nil } // NewFloatGetterFromConfig creates a FloatGetter from config -func NewFloatGetterFromConfig(log *util.Logger, config Config) (res func() (float64, error)) { +func NewFloatGetterFromConfig(config Config) (res func() (float64, error), err error) { switch strings.ToLower(config.Type) { case "calc": - res = NewCalcFromConfig(log, config.Other) + res, err = NewCalcFromConfig(config.Other) case "http": - res = NewHTTPProviderFromConfig(log, config.Other).FloatGetter + var prov *HTTP + if prov, err = NewHTTPProviderFromConfig(config.Other); err == nil { + res = prov.FloatGetter + } case "websocket", "ws": - res = NewSocketProviderFromConfig(log, config.Other).FloatGetter + var prov *Socket + if prov, err = NewSocketProviderFromConfig(config.Other); err == nil { + res = prov.FloatGetter + } case "mqtt": - pc := mqttFromConfig(log, config.Other) - res = MQTT.FloatGetter(pc.Topic, pc.Scale, pc.Timeout) + if pc, err := mqttFromConfig(config.Other); err == nil { + res = MQTT.FloatGetter(pc.Topic, pc.Scale, pc.Timeout) + } case "script": - pc := scriptFromConfig(log, config.Other) - res = NewScriptProvider(pc.Timeout).FloatGetter(pc.Cmd) + var pc scriptConfig + if pc, err = scriptFromConfig(config.Other); err != nil { + break + } + + var prov *Script + if prov, err = NewScriptProvider(pc.Timeout); err == nil { + res = prov.FloatGetter(pc.Cmd) + } + if pc.Cache > 0 { - res = NewCached(log, res, pc.Cache).FloatGetter() + res = NewCached(res, pc.Cache).FloatGetter() } case "modbus": - res = NewModbusFromConfig(log, config.Other).FloatGetter + var prov *Modbus + if prov, err = NewModbusFromConfig(config.Other); err == nil { + res = prov.FloatGetter + } default: - log.FATAL.Fatal("invalid plugin type:", config.Type) + return nil, fmt.Errorf("invalid plugin type: %s", config.Type) } return } // NewIntGetterFromConfig creates a IntGetter from config -func NewIntGetterFromConfig(log *util.Logger, config Config) (res func() (int64, error)) { +func NewIntGetterFromConfig(config Config) (res func() (int64, error), err error) { switch strings.ToLower(config.Type) { case "http": - res = NewHTTPProviderFromConfig(log, config.Other).IntGetter + var prov *HTTP + if prov, err = NewHTTPProviderFromConfig(config.Other); err == nil { + res = prov.IntGetter + } case "websocket", "ws": - res = NewSocketProviderFromConfig(log, config.Other).IntGetter + var prov *Socket + if prov, err = NewSocketProviderFromConfig(config.Other); err == nil { + res = prov.IntGetter + } case "mqtt": - pc := mqttFromConfig(log, config.Other) - res = MQTT.IntGetter(pc.Topic, int64(pc.Scale), pc.Timeout) + var pc mqttConfig + if pc, err = mqttFromConfig(config.Other); err == nil { + res = MQTT.IntGetter(pc.Topic, int64(pc.Scale), pc.Timeout) + } case "script": - pc := scriptFromConfig(log, config.Other) - res = NewScriptProvider(pc.Timeout).IntGetter(pc.Cmd) + var pc scriptConfig + if pc, err = scriptFromConfig(config.Other); err != nil { + break + } + + var prov *Script + if prov, err = NewScriptProvider(pc.Timeout); err == nil { + res = prov.IntGetter(pc.Cmd) + } + if pc.Cache > 0 { - res = NewCached(log, res, pc.Cache).IntGetter() + res = NewCached(res, pc.Cache).IntGetter() } case "modbus": - res = NewModbusFromConfig(log, config.Other).IntGetter + var prov *Modbus + if prov, err = NewModbusFromConfig(config.Other); err == nil { + res = prov.IntGetter + } default: - log.FATAL.Fatal("invalid plugin type:", config.Type) + err = fmt.Errorf("invalid plugin type: %s", config.Type) } return } // NewStringGetterFromConfig creates a StringGetter from config -func NewStringGetterFromConfig(log *util.Logger, config Config) (res func() (string, error)) { +func NewStringGetterFromConfig(config Config) (res func() (string, error), err error) { switch strings.ToLower(config.Type) { case "http": - res = NewHTTPProviderFromConfig(log, config.Other).StringGetter + var prov *HTTP + if prov, err = NewHTTPProviderFromConfig(config.Other); err == nil { + res = prov.StringGetter + } case "websocket", "ws": - res = NewSocketProviderFromConfig(log, config.Other).StringGetter + var prov *Socket + if prov, err = NewSocketProviderFromConfig(config.Other); err == nil { + res = prov.StringGetter + } case "mqtt": - pc := mqttFromConfig(log, config.Other) - res = MQTT.StringGetter(pc.Topic, pc.Timeout) + var pc mqttConfig + if pc, err = mqttFromConfig(config.Other); err == nil { + res = MQTT.StringGetter(pc.Topic, pc.Timeout) + } case "script": - pc := scriptFromConfig(log, config.Other) - res = NewScriptProvider(pc.Timeout).StringGetter(pc.Cmd) + var pc scriptConfig + if pc, err = scriptFromConfig(config.Other); err != nil { + break + } + + var prov *Script + if prov, err = NewScriptProvider(pc.Timeout); err == nil { + res = prov.StringGetter(pc.Cmd) + } + if pc.Cache > 0 { - res = NewCached(log, res, pc.Cache).StringGetter() + res = NewCached(res, pc.Cache).StringGetter() } case "combined", "openwb": - res = openWBStatusFromConfig(log, config.Other) + res, err = openWBStatusFromConfig(config.Other) default: - log.FATAL.Fatal("invalid plugin type:", config.Type) + err = fmt.Errorf("invalid plugin type: %s", config.Type) } return } // NewBoolGetterFromConfig creates a BoolGetter from config -func NewBoolGetterFromConfig(log *util.Logger, config Config) (res func() (bool, error)) { +func NewBoolGetterFromConfig(config Config) (res func() (bool, error), err error) { switch strings.ToLower(config.Type) { case "http": - res = NewHTTPProviderFromConfig(log, config.Other).BoolGetter + var prov *HTTP + if prov, err = NewHTTPProviderFromConfig(config.Other); err == nil { + res = prov.BoolGetter + } case "websocket", "ws": - res = NewSocketProviderFromConfig(log, config.Other).BoolGetter + var prov *Socket + if prov, err = NewSocketProviderFromConfig(config.Other); err == nil { + res = prov.BoolGetter + } case "mqtt": - pc := mqttFromConfig(log, config.Other) - res = MQTT.BoolGetter(pc.Topic, pc.Timeout) + var pc mqttConfig + if pc, err = mqttFromConfig(config.Other); err == nil { + res = MQTT.BoolGetter(pc.Topic, pc.Timeout) + } case "script": - pc := scriptFromConfig(log, config.Other) - res = NewScriptProvider(pc.Timeout).BoolGetter(pc.Cmd) + var pc scriptConfig + if pc, err = scriptFromConfig(config.Other); err != nil { + break + } + + var prov *Script + if prov, err = NewScriptProvider(pc.Timeout); err == nil { + res = prov.BoolGetter(pc.Cmd) + } + if pc.Cache > 0 { - res = NewCached(log, res, pc.Cache).BoolGetter() + res = NewCached(res, pc.Cache).BoolGetter() } default: - log.FATAL.Fatal("invalid plugin type:", config.Type) + err = fmt.Errorf("invalid plugin type: %s", config.Type) } return } // NewIntSetterFromConfig creates a IntSetter from config -func NewIntSetterFromConfig(log *util.Logger, param string, config Config) (res func(int64) error) { +func NewIntSetterFromConfig(param string, config Config) (res func(int64) error, err error) { switch strings.ToLower(config.Type) { case "http": - res = NewHTTPProviderFromConfig(log, config.Other).IntSetter + var prov *HTTP + if prov, err = NewHTTPProviderFromConfig(config.Other); err == nil { + res = prov.IntSetter + } case "mqtt": - pc := mqttFromConfig(log, config.Other) - res = MQTT.IntSetter(param, pc.Topic, pc.Payload) + var pc mqttConfig + if pc, err = mqttFromConfig(config.Other); err == nil { + res = MQTT.IntSetter(param, pc.Topic, pc.Payload) + } case "script": - pc := scriptFromConfig(log, config.Other) - script := NewScriptProvider(pc.Timeout) - res = script.IntSetter(param, pc.Cmd) + var pc scriptConfig + if pc, err = scriptFromConfig(config.Other); err != nil { + break + } + + var prov *Script + if prov, err = NewScriptProvider(pc.Timeout); err == nil { + res = prov.IntSetter(param, pc.Cmd) + } default: - log.FATAL.Fatalf("invalid setter type %s", config.Type) + err = fmt.Errorf("invalid setter type %s", config.Type) } + return } // NewBoolSetterFromConfig creates a BoolSetter from config -func NewBoolSetterFromConfig(log *util.Logger, param string, config Config) (res func(bool) error) { +func NewBoolSetterFromConfig(param string, config Config) (res func(bool) error, err error) { switch strings.ToLower(config.Type) { case "http": - res = NewHTTPProviderFromConfig(log, config.Other).BoolSetter + var prov *HTTP + if prov, err = NewHTTPProviderFromConfig(config.Other); err == nil { + res = prov.BoolSetter + } case "mqtt": - pc := mqttFromConfig(log, config.Other) - res = MQTT.BoolSetter(param, pc.Topic, pc.Payload) + var pc mqttConfig + if pc, err = mqttFromConfig(config.Other); err == nil { + res = MQTT.BoolSetter(param, pc.Topic, pc.Payload) + } case "script": - pc := scriptFromConfig(log, config.Other) - script := NewScriptProvider(pc.Timeout) - res = script.BoolSetter(param, pc.Cmd) + var pc scriptConfig + if pc, err = scriptFromConfig(config.Other); err != nil { + break + } + + var prov *Script + if prov, err = NewScriptProvider(pc.Timeout); err == nil { + res = prov.BoolSetter(param, pc.Cmd) + } default: - log.FATAL.Fatalf("invalid setter type %s", config.Type) + err = fmt.Errorf("invalid setter type %s", config.Type) } + return } diff --git a/provider/http.go b/provider/http.go index 0c6c833482..6ba869b4a3 100644 --- a/provider/http.go +++ b/provider/http.go @@ -41,7 +41,7 @@ func NewAuth(log *util.Logger, auth Auth, headers map[string]string) { } // NewHTTPProviderFromConfig creates a HTTP provider -func NewHTTPProviderFromConfig(log *util.Logger, other map[string]interface{}) *HTTP { +func NewHTTPProviderFromConfig(other map[string]interface{}) (*HTTP, error) { cc := struct { URI, Method string Headers map[string]string @@ -51,12 +51,15 @@ func NewHTTPProviderFromConfig(log *util.Logger, other map[string]interface{}) * Insecure bool Auth Auth }{} - util.DecodeOther(log, other, &cc) - logger := util.NewLogger("http") + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + log := util.NewLogger("http") p := &HTTP{ - HTTPHelper: util.NewHTTPHelper(logger), + HTTPHelper: util.NewHTTPHelper(log), url: cc.URI, method: cc.Method, headers: cc.Headers, @@ -82,13 +85,13 @@ func NewHTTPProviderFromConfig(log *util.Logger, other map[string]interface{}) * if cc.Jq != "" { op, err := gojq.Parse(cc.Jq) if err != nil { - log.FATAL.Fatalf("config: invalid jq query: %s", p.jq) + return nil, fmt.Errorf("config: invalid jq query: %s", p.jq) } p.jq = op } - return p + return p, nil } // request executed the configured request diff --git a/provider/modbus.go b/provider/modbus.go index cbfa626426..c06c56370e 100644 --- a/provider/modbus.go +++ b/provider/modbus.go @@ -23,14 +23,19 @@ type Modbus struct { } // NewModbusFromConfig creates Modbus plugin -func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) *Modbus { +func NewModbusFromConfig(other map[string]interface{}) (*Modbus, error) { cc := struct { modbus.Settings `mapstructure:",squash"` Register modbus.Register Value string Scale float64 - }{} - util.DecodeOther(log, other, &cc) + }{ + Scale: 1, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } // assume RTU if not set and this is a known RS485 meter model if cc.RTU == nil { @@ -38,16 +43,19 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) *Modbus cc.RTU = &b } - log = util.NewLogger("modb") - conn := modbus.NewConnection(log, cc.URI, cc.Device, cc.Comset, cc.Baudrate, *cc.RTU) + conn, err := modbus.NewConnection(cc.URI, cc.Device, cc.Comset, cc.Baudrate, *cc.RTU) + if err != nil { + return nil, err + } + + log := util.NewLogger("modbus") conn.Logger(log.TRACE) - var err error var device meters.Device var op modbus.Operation if cc.Value != "" && cc.Register.Decode != "" { - log.FATAL.Fatalf("config: modbus cannot have value and register both") + return nil, errors.New("config: modbus cannot have value and register both") } if cc.Value == "" && cc.Register.Decode == "" { @@ -58,7 +66,7 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) *Modbus // model + value configured if cc.Value != "" { if err := modbus.ParseOperation(device, cc.Value, &op); err != nil { - log.FATAL.Fatalf("config: invalid value %s", cc.Value) + return nil, fmt.Errorf("config: invalid value %s", cc.Value) } // if sunspec reading configured make sure model is defined or device won't be initalized @@ -76,7 +84,7 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) *Modbus // model configured if cc.Model != "" { - device, err = modbus.NewDevice(log, cc.Model, *cc.RTU) + device, err = modbus.NewDevice(cc.Model, *cc.RTU) // prepare device if err == nil { @@ -89,15 +97,12 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) *Modbus } } } - if err != nil { - log.FATAL.Fatal(err) - } - if cc.Scale == 0 { - cc.Scale = 1 + if err != nil { + return nil, err } - return &Modbus{ + mb := &Modbus{ log: log, conn: conn, device: device, @@ -105,6 +110,7 @@ func NewModbusFromConfig(log *util.Logger, other map[string]interface{}) *Modbus scale: cc.Scale, slaveID: cc.ID, } + return mb, nil } // FloatGetter executes configured modbus read operation and implements func() (float64, error) diff --git a/provider/openwb.go b/provider/openwb.go index de833a4be4..c70f1bcd5e 100644 --- a/provider/openwb.go +++ b/provider/openwb.go @@ -9,18 +9,31 @@ type openWBStatusProvider struct { plugged, charging func() (bool, error) } -func openWBStatusFromConfig(log *util.Logger, other map[string]interface{}) func() (string, error) { +func openWBStatusFromConfig(other map[string]interface{}) (func() (string, error), error) { cc := struct { Plugged, Charging Config }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + plugged, err := NewBoolGetterFromConfig(cc.Plugged) + + var charging func() (bool, error) + if err == nil { + charging, err = NewBoolGetterFromConfig(cc.Charging) + } + + if err != nil { + return nil, err + } o := &openWBStatusProvider{ - plugged: NewBoolGetterFromConfig(log, cc.Plugged), - charging: NewBoolGetterFromConfig(log, cc.Charging), + plugged: plugged, + charging: charging, } - return o.stringGetter + return o.stringGetter, nil } func (o *openWBStatusProvider) stringGetter() (string, error) { diff --git a/provider/script.go b/provider/script.go index 1d6acef807..54a1a22299 100644 --- a/provider/script.go +++ b/provider/script.go @@ -20,11 +20,12 @@ type Script struct { // NewScriptProvider creates a script provider. // Script execution is aborted after given timeout. -func NewScriptProvider(timeout time.Duration) *Script { - return &Script{ +func NewScriptProvider(timeout time.Duration) (*Script, error) { + s := &Script{ log: util.NewLogger("exec"), timeout: timeout, } + return s, nil } // StringGetter returns string from exec result. Only STDOUT is considered. diff --git a/provider/socket.go b/provider/socket.go index 2262cda7c3..26e46beb5a 100644 --- a/provider/socket.go +++ b/provider/socket.go @@ -14,6 +14,8 @@ import ( "github.com/itchyny/gojq" ) +const retryDelay = 5 * time.Second + // Socket implements websocket request provider type Socket struct { *util.HTTPHelper @@ -26,7 +28,7 @@ type Socket struct { } // NewSocketProviderFromConfig creates a HTTP provider -func NewSocketProviderFromConfig(log *util.Logger, other map[string]interface{}) *Socket { +func NewSocketProviderFromConfig(other map[string]interface{}) (*Socket, error) { cc := struct { URI string Headers map[string]string @@ -36,13 +38,15 @@ func NewSocketProviderFromConfig(log *util.Logger, other map[string]interface{}) Auth Auth Timeout time.Duration }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } - logger := util.NewLogger("ws") + log := util.NewLogger("ws") p := &Socket{ - HTTPHelper: util.NewHTTPHelper(logger), - mux: util.NewWaiter(cc.Timeout, func() { logger.TRACE.Println("wait for initial value") }), + HTTPHelper: util.NewHTTPHelper(log), + mux: util.NewWaiter(cc.Timeout, func() { log.TRACE.Println("wait for initial value") }), url: cc.URI, headers: cc.Headers, scale: cc.Scale, @@ -66,7 +70,7 @@ func NewSocketProviderFromConfig(log *util.Logger, other map[string]interface{}) if cc.Jq != "" { op, err := gojq.Parse(cc.Jq) if err != nil { - log.FATAL.Fatalf("config: invalid jq query: %s", p.jq) + return nil, fmt.Errorf("config: invalid jq query: %s", p.jq) } p.jq = op @@ -74,7 +78,7 @@ func NewSocketProviderFromConfig(log *util.Logger, other map[string]interface{}) go p.listen() - return p + return p, nil } func (p *Socket) listen() { @@ -88,7 +92,9 @@ func (p *Socket) listen() { for { client, _, err := websocket.DefaultDialer.Dial(p.url, headers) if err != nil { - log.ERROR.Println("dial:", err) + log.ERROR.Println(err) + time.Sleep(retryDelay) + continue } for { diff --git a/push/config.go b/push/config.go index c555cdd170..d3bb15bc47 100644 --- a/push/config.go +++ b/push/config.go @@ -1,6 +1,7 @@ package push import ( + "fmt" "strings" "github.com/andig/evcc/util" @@ -19,18 +20,21 @@ type EventTemplate struct { var log = util.NewLogger("push") // NewMessengerFromConfig creates a new messenger -func NewMessengerFromConfig(typ string, other map[string]interface{}) Sender { +func NewMessengerFromConfig(typ string, other map[string]interface{}) (res Sender, err error) { switch strings.ToLower(typ) { case "pushover": var cc pushOverConfig - util.DecodeOther(log, other, &cc) - return NewPushOverMessenger(cc.App, cc.Recipients) + if err = util.DecodeOther(other, &cc); err == nil { + res = NewPushOverMessenger(cc.App, cc.Recipients) + } case "telegram": var cc telegramConfig - util.DecodeOther(log, other, &cc) - return NewTelegramMessenger(cc.Token, cc.Chats) + if err = util.DecodeOther(other, &cc); err == nil { + res = NewTelegramMessenger(cc.Token, cc.Chats) + } + default: + err = fmt.Errorf("unknown messenger type: %s", typ) } - log.FATAL.Fatalf("unknown messenger type: %s", typ) - return nil + return res, err } diff --git a/util/decoder.go b/util/decoder.go index ad30e9271e..259b86741b 100644 --- a/util/decoder.go +++ b/util/decoder.go @@ -5,7 +5,7 @@ import ( ) // DecodeOther uses mapstructure to decode into target structure. Unused keys cause errors. -func DecodeOther(log *Logger, other interface{}, cc interface{}) { +func DecodeOther(other interface{}, cc interface{}) error { decoderConfig := &mapstructure.DecoderConfig{ Result: cc, ErrorUnused: true, @@ -14,11 +14,9 @@ func DecodeOther(log *Logger, other interface{}, cc interface{}) { } decoder, err := mapstructure.NewDecoder(decoderConfig) - if err != nil { - log.FATAL.Fatal(err) + if err == nil { + err = decoder.Decode(other) } - if err := decoder.Decode(other); err != nil { - log.FATAL.Fatal(err) - } + return err } diff --git a/util/modbus/modbus.go b/util/modbus/modbus.go index f4af5ad5e9..26f57b4fc0 100644 --- a/util/modbus/modbus.go +++ b/util/modbus/modbus.go @@ -1,11 +1,11 @@ package modbus import ( + "errors" "fmt" "strconv" "strings" - "github.com/andig/evcc/util" "github.com/volkszaehler/mbmd/meters" "github.com/volkszaehler/mbmd/meters/rs485" "github.com/volkszaehler/mbmd/meters/sunspec" @@ -41,7 +41,7 @@ func registeredConnection(key string, newConn meters.Connection) meters.Connecti } // NewConnection creates physical modbus device from config -func NewConnection(log *util.Logger, uri, device, comset string, baudrate int, rtu bool) (conn meters.Connection) { +func NewConnection(uri, device, comset string, baudrate int, rtu bool) (conn meters.Connection, err error) { if device != "" { conn = registeredConnection(device, meters.NewRTU(device, baudrate, comset)) } @@ -55,14 +55,14 @@ func NewConnection(log *util.Logger, uri, device, comset string, baudrate int, r } if conn == nil { - log.FATAL.Fatalf("config: invalid modbus configuration: need either uri or device") + return nil, errors.New("config: invalid modbus configuration: need either uri or device") } - return conn + return conn, nil } // NewDevice creates physical modbus device from config -func NewDevice(log *util.Logger, model string, isRS485 bool) (device meters.Device, err error) { +func NewDevice(model string, isRS485 bool) (device meters.Device, err error) { if isRS485 { device, err = rs485.NewDevice(strings.ToUpper(model)) } else { @@ -70,7 +70,7 @@ func NewDevice(log *util.Logger, model string, isRS485 bool) (device meters.Devi } if device == nil { - log.FATAL.Fatalf("config: invalid modbus configuration: need either uri or device") + err = errors.New("config: invalid modbus configuration: need either uri or device") } return device, err diff --git a/vehicle/audi.go b/vehicle/audi.go index 42162d9d02..eca0558d6f 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -54,14 +54,16 @@ type Audi struct { } // NewAudiFromConfig creates a new vehicle -func NewAudiFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 User, Password, VIN string Cache time.Duration }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } v := &Audi{ embed: &embed{cc.Title, cc.Capacity}, @@ -71,9 +73,9 @@ func NewAudiFromConfig(log *util.Logger, other map[string]interface{}) api.Vehic vin: cc.VIN, } - v.chargeStateG = provider.NewCached(log, v.chargeState, cc.Cache).FloatGetter() + v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - return v + return v, nil } func (v *Audi) apiURL(service, part string) string { diff --git a/vehicle/bmw.go b/vehicle/bmw.go index 32c5ab0113..83a713f74b 100644 --- a/vehicle/bmw.go +++ b/vehicle/bmw.go @@ -40,16 +40,19 @@ type BMW struct { } // NewBMWFromConfig creates a new vehicle -func NewBMWFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewBMWFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 User, Password, VIN string Cache time.Duration }{} - util.DecodeOther(log, other, &cc) - log = util.NewLogger("bmw ") + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + log := util.NewLogger("bmw") v := &BMW{ embed: &embed{cc.Title, cc.Capacity}, @@ -62,20 +65,20 @@ func NewBMWFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicl if cc.VIN == "" { vehicles, err := v.vehicles() if err != nil { - log.FATAL.Fatalf("cannot get vehicles: %v", err) + return nil, fmt.Errorf("cannot get vehicles: %v", err) } if len(vehicles) != 1 { - log.FATAL.Fatalf("cannot find vehicle: %v", vehicles) + return nil, fmt.Errorf("cannot find vehicle: %v", vehicles) } v.vin = vehicles[0].Vin log.DEBUG.Printf("found vehicle: %v", v.vin) } - v.chargeStateG = provider.NewCached(log, v.chargeState, cc.Cache).FloatGetter() + v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - return v + return v, nil } func (v *BMW) login(user, password string) error { diff --git a/vehicle/config.go b/vehicle/config.go index 050e4d33e2..674255dcbb 100644 --- a/vehicle/config.go +++ b/vehicle/config.go @@ -1,34 +1,32 @@ package vehicle import ( + "fmt" "strings" "github.com/andig/evcc/api" - "github.com/andig/evcc/util" ) // NewFromConfig creates vehicle from configuration -func NewFromConfig(log *util.Logger, typ string, other map[string]interface{}) api.Vehicle { - var c api.Vehicle - +func NewFromConfig(typ string, other map[string]interface{}) (v api.Vehicle, err error) { switch strings.ToLower(typ) { case "default", "configurable": - c = NewConfigurableFromConfig(log, other) + v, err = NewConfigurableFromConfig(other) case "audi", "etron": - c = NewAudiFromConfig(log, other) + v, err = NewAudiFromConfig(other) case "bmw", "i3": - c = NewBMWFromConfig(log, other) + v, err = NewBMWFromConfig(other) case "tesla", "model3", "model 3", "models", "model s": - c = NewTeslaFromConfig(log, other) + v, err = NewTeslaFromConfig(other) case "nissan", "leaf": - c = NewNissanFromConfig(log, other) + v, err = NewNissanFromConfig(other) case "renault", "zoe": - c = NewRenaultFromConfig(log, other) + v, err = NewRenaultFromConfig(other) case "porsche", "taycan": - c = NewPorscheFromConfig(log, other) + v, err = NewPorscheFromConfig(other) default: - log.FATAL.Fatalf("invalid vehicle type '%s'", typ) + err = fmt.Errorf("invalid vehicle type: %s", typ) } - return c + return } diff --git a/vehicle/nissan.go b/vehicle/nissan.go index c4b35caa84..cd3c1cb519 100644 --- a/vehicle/nissan.go +++ b/vehicle/nissan.go @@ -18,17 +18,18 @@ type Nissan struct { } // NewNissanFromConfig creates a new vehicle -func NewNissanFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewNissanFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 User, Password, Region string Cache time.Duration - }{} - util.DecodeOther(log, other, &cc) + }{ + Region: carwings.RegionEurope, + } - if cc.Region == "" { - cc.Region = carwings.RegionEurope + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err } session := &carwings.Session{ @@ -36,7 +37,7 @@ func NewNissanFromConfig(log *util.Logger, other map[string]interface{}) api.Veh } if err := session.Connect(cc.User, cc.Password); err != nil { - log.FATAL.Fatalf("cannot create nissan: %v", err) + return nil, err } v := &Nissan{ @@ -44,9 +45,9 @@ func NewNissanFromConfig(log *util.Logger, other map[string]interface{}) api.Veh session: session, } - v.chargeStateG = provider.NewCached(log, v.chargeState, cc.Cache).FloatGetter() + v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - return v + return v, nil } // chargeState implements the Vehicle.ChargeState interface diff --git a/vehicle/porsche.go b/vehicle/porsche.go index 9f12f9006a..ec91dde412 100644 --- a/vehicle/porsche.go +++ b/vehicle/porsche.go @@ -59,14 +59,16 @@ type Porsche struct { } // NewPorscheFromConfig creates a new vehicle -func NewPorscheFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 User, Password, VIN string Cache time.Duration }{} - util.DecodeOther(log, other, &cc) + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } v := &Porsche{ embed: &embed{cc.Title, cc.Capacity}, @@ -76,9 +78,9 @@ func NewPorscheFromConfig(log *util.Logger, other map[string]interface{}) api.Ve vin: cc.VIN, } - v.chargeStateG = provider.NewCached(log, v.chargeState, cc.Cache).FloatGetter() + v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - return v + return v, nil } // login with a my Porsche account diff --git a/vehicle/renault.go b/vehicle/renault.go index 27e9600f76..2588f5c5e3 100644 --- a/vehicle/renault.go +++ b/vehicle/renault.go @@ -94,24 +94,25 @@ type Renault struct { } // NewRenaultFromConfig creates a new vehicle -func NewRenaultFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewRenaultFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 User, Password, Region, VIN string Cache time.Duration - }{} - util.DecodeOther(log, other, &cc) + }{ + Region: "de_DE", + } - if cc.Region == "" { - cc.Region = "de_DE" + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err } - logger := util.NewLogger("zoe") + log := util.NewLogger("renault") v := &Renault{ embed: &embed{cc.Title, cc.Capacity}, - HTTPHelper: util.NewHTTPHelper(logger), + HTTPHelper: util.NewHTTPHelper(log), user: cc.User, password: cc.Password, vin: cc.VIN, @@ -122,12 +123,12 @@ func NewRenaultFromConfig(log *util.Logger, other map[string]interface{}) api.Ve err = v.authFlow() } if err != nil { - v.HTTPHelper.Log.FATAL.Fatalf("cannot create renault: %v", err) + return nil, err } - v.chargeStateG = provider.NewCached(logger, v.chargeState, cc.Cache).FloatGetter() + v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - return v + return v, nil } func (v *Renault) apiKeys(region string) error { diff --git a/vehicle/tesla.go b/vehicle/tesla.go index a706f0e240..533e83ee1c 100644 --- a/vehicle/tesla.go +++ b/vehicle/tesla.go @@ -1,6 +1,7 @@ package vehicle import ( + "errors" "time" "github.com/andig/evcc/api" @@ -18,7 +19,7 @@ type Tesla struct { } // NewTeslaFromConfig creates a new Tesla vehicle -func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 @@ -27,7 +28,10 @@ func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Vehi VIN string Cache time.Duration }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } client, err := tesla.NewClient(&tesla.Auth{ ClientID: cc.ClientID, @@ -36,12 +40,12 @@ func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Vehi Password: cc.Password, }) if err != nil { - log.FATAL.Fatalf("cannot create tesla: %v", err) + return nil, err } vehicles, err := client.Vehicles() if err != nil { - log.FATAL.Fatalf("cannot create tesla: %v", err) + return nil, err } v := &Tesla{ @@ -59,13 +63,13 @@ func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Vehi } if v.vehicle == nil { - log.FATAL.Fatal("cannot create tesla: vin not found") + return nil, errors.New("vin not found") } - v.chargeStateG = provider.NewCached(log, v.chargeState, cc.Cache).FloatGetter() - v.chargedEnergyG = provider.NewCached(log, v.chargedEnergy, cc.Cache).FloatGetter() + v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() + v.chargedEnergyG = provider.NewCached(v.chargedEnergy, cc.Cache).FloatGetter() - return v + return v, nil } // chargeState implements the Vehicle.ChargeState interface diff --git a/vehicle/vehicle.go b/vehicle/vehicle.go index 84b265563c..a1f743b535 100644 --- a/vehicle/vehicle.go +++ b/vehicle/vehicle.go @@ -1,6 +1,7 @@ package vehicle import ( + "fmt" "time" "github.com/andig/evcc/api" @@ -30,30 +31,39 @@ type Vehicle struct { } // NewConfigurableFromConfig creates a new Vehicle -func NewConfigurableFromConfig(log *util.Logger, other map[string]interface{}) api.Vehicle { +func NewConfigurableFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { Title string Capacity int64 Charge provider.Config Cache time.Duration }{} - util.DecodeOther(log, other, &cc) + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } for k, v := range map[string]string{"charge": cc.Charge.Type} { if v == "" { - log.FATAL.Fatalf("default vehicle config: %s required", k) + return nil, fmt.Errorf("default vehicle config: %s required", k) } } - getter := provider.NewFloatGetterFromConfig(log, cc.Charge) + getter, err := provider.NewFloatGetterFromConfig(cc.Charge) + if err != nil { + return nil, err + } + if cc.Cache > 0 { - getter = provider.NewCached(log, getter, cc.Cache).FloatGetter() + getter = provider.NewCached(getter, cc.Cache).FloatGetter() } - return &Vehicle{ + v := &Vehicle{ embed: &embed{cc.Title, cc.Capacity}, chargeG: getter, } + + return v, nil } // ChargeState implements the Vehicle.ChargeState interface