Return errors instead of failing

This commit is contained in:
andig 2020-07-29 13:42:09 +02:00
parent 9e7cf67128
commit b1de0950c2
51 changed files with 635 additions and 418 deletions

View file

@ -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)
return charger
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 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

View file

@ -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
}

View file

@ -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 {

View file

@ -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) {

View file

@ -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")

View file

@ -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 {

View file

@ -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")

View file

@ -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

View file

@ -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")
}

View file

@ -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) {

View file

@ -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 {

View file

@ -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")

View file

@ -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)
if _, _, err := net.SplitHostPort(cc.URI); err != nil {
log.FATAL.Printf("config: missing or invalid phoenix EM-CP uri: %s", cc.URI)
}{
ID: 180, // default
}
if cc.ID == 0 {
cc.ID = 180
log.WARN.Printf("config: missing phoenix EM-CP slave id, assuming default %d", cc.ID)
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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
}
}

View file

@ -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))

View file

@ -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" {

View file

@ -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

View file

@ -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().

2
go.mod
View file

@ -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

8
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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() {}),
}

View file

@ -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

View file

@ -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,

View file

@ -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()

View file

@ -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) {

View file

@ -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 {
func mqttFromConfig(other map[string]interface{}) (mqttConfig, error) {
pc := mqttConfig{Scale: 1}
if err := util.DecodeOther(other, &pc); err != nil {
return pc, err
}
if MQTT == nil {
log.FATAL.Fatal("mqtt not configured")
return pc, errors.New("mqtt not configured")
}
var pc mqttConfig
util.DecodeOther(log, other, &pc)
if pc.Scale == 0 {
pc.Scale = 1
}
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
}

View file

@ -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

View file

@ -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)
return nil, err
}
if cc.Scale == 0 {
cc.Scale = 1
}
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)

View file

@ -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)
o := &openWBStatusProvider{
plugged: NewBoolGetterFromConfig(log, cc.Plugged),
charging: NewBoolGetterFromConfig(log, cc.Charging),
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return o.stringGetter
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: plugged,
charging: charging,
}
return o.stringGetter, nil
}
func (o *openWBStatusProvider) stringGetter() (string, error) {

View file

@ -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.

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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)
if cc.Region == "" {
cc.Region = "de_DE"
}{
Region: "de_DE",
}
logger := util.NewLogger("zoe")
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
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 {

View file

@ -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

View file

@ -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)
if cc.Cache > 0 {
getter = provider.NewCached(log, getter, cc.Cache).FloatGetter()
getter, err := provider.NewFloatGetterFromConfig(cc.Charge)
if err != nil {
return nil, err
}
return &Vehicle{
if cc.Cache > 0 {
getter = provider.NewCached(getter, cc.Cache).FloatGetter()
}
v := &Vehicle{
embed: &embed{cc.Title, cc.Capacity},
chargeG: getter,
}
return v, nil
}
// ChargeState implements the Vehicle.ChargeState interface