From ca75de87c16a59b9e3e8866768986bee658a4777 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 13 Apr 2020 18:53:56 +0200 Subject: [PATCH] Implement int/string/bool getters over mqtt (#30) --- charger/charger.go | 2 +- evcc.dist.yaml | 14 +++++ provider/config.go | 134 +++++++++++++++++++++++++++------------- provider/exec.go | 4 -- provider/helper.go | 5 ++ provider/helper_test.go | 22 +++++++ provider/mqtt.go | 53 +++++++++++++++- 7 files changed, 183 insertions(+), 51 deletions(-) diff --git a/charger/charger.go b/charger/charger.go index 59181bbfd..727cc789f 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -22,7 +22,7 @@ func NewConfigurableFromConfig(log *api.Logger, other map[string]interface{}) ap provider.NewStringGetterFromConfig(cc.Status), provider.NewBoolGetterFromConfig(cc.Enabled), provider.NewBoolSetterFromConfig("enable", cc.Enable), - provider.NewIntSetterFromConfig("current", cc.MaxCurrent), + provider.NewIntSetterFromConfig("maxcurrent", cc.MaxCurrent), ) return charger diff --git a/evcc.dist.yaml b/evcc.dist.yaml index 280f9319b..54a248f3f 100644 --- a/evcc.dist.yaml +++ b/evcc.dist.yaml @@ -83,6 +83,20 @@ chargers: - name: go-e type: go-e # go-e charger uri: http://192.168.1.4 # go-e address +- name: configurable + type: default # Configurable charger + status: # charger status A..F + type: mqtt + topic: some/topic1 + enabled: # charger enabled state (true/false or 0/1) + type: mqtt + topic: some/topic2 + enable: # set charger enabled state + type: script + cmd: /bin/sh -c "echo ${enable}" + maxcurrent: # set charger max current + type: script + cmd: /bin/sh -c "echo ${maxcurrent}" vehicles: - name: default diff --git a/provider/config.go b/provider/config.go index 721aef3a1..087b5bee7 100644 --- a/provider/config.go +++ b/provider/config.go @@ -1,6 +1,7 @@ package provider import ( + "strings" "time" ) @@ -22,49 +23,9 @@ type Config struct { // MQTT singleton var MQTT *MqttClient -// NewStringGetterFromConfig creates a StringGetter from config -func NewStringGetterFromConfig(pc *Config) (res StringGetter) { - switch pc.Type { - case "script": - if pc.Timeout == 0 { - pc.Timeout = execTimeout - } - exec := NewScriptProvider(pc.Timeout) - res = exec.StringGetter(pc.Cmd) - default: - log.FATAL.Fatalf("invalid provider type %s", pc.Type) - } - - if pc.Cache > 0 { - res = NewCached(res, pc.Cache).StringGetter() - } - - return -} - -// NewBoolGetterFromConfig creates a BoolGetter from config -func NewBoolGetterFromConfig(pc *Config) (res BoolGetter) { - switch pc.Type { - case "script": - if pc.Timeout == 0 { - pc.Timeout = execTimeout - } - exec := NewScriptProvider(pc.Timeout) - res = exec.BoolGetter(pc.Cmd) - default: - log.FATAL.Fatalf("invalid provider type %s", pc.Type) - } - - if pc.Cache > 0 { - res = NewCached(res, pc.Cache).BoolGetter() - } - - return -} - // NewFloatGetterFromConfig creates a FloatGetter from config func NewFloatGetterFromConfig(pc *Config) (res FloatGetter) { - switch pc.Type { + switch strings.ToLower(pc.Type) { case "mqtt": if MQTT == nil { log.FATAL.Fatal("mqtt not configured") @@ -93,9 +54,96 @@ func NewFloatGetterFromConfig(pc *Config) (res FloatGetter) { return } +// NewIntGetterFromConfig creates a IntGetter from config +func NewIntGetterFromConfig(pc *Config) (res IntGetter) { + switch strings.ToLower(pc.Type) { + case "mqtt": + if MQTT == nil { + log.FATAL.Fatal("mqtt not configured") + } + if pc.Timeout == 0 { + pc.Timeout = mqttTimeout + } + if pc.Multiplier == 0 { + pc.Multiplier = 1 + } + res = MQTT.IntGetter(pc.Topic, int64(pc.Multiplier), pc.Timeout) + case "script": + if pc.Timeout == 0 { + pc.Timeout = execTimeout + } + exec := NewScriptProvider(pc.Timeout) + res = exec.IntGetter(pc.Cmd) + default: + log.FATAL.Fatalf("invalid provider type %s", pc.Type) + } + + if pc.Cache > 0 { + res = NewCached(res, pc.Cache).IntGetter() + } + + return +} + +// NewStringGetterFromConfig creates a StringGetter from config +func NewStringGetterFromConfig(pc *Config) (res StringGetter) { + switch strings.ToLower(pc.Type) { + case "mqtt": + if MQTT == nil { + log.FATAL.Fatal("mqtt not configured") + } + if pc.Timeout == 0 { + pc.Timeout = mqttTimeout + } + res = MQTT.StringGetter(pc.Topic, pc.Timeout) + case "script": + if pc.Timeout == 0 { + pc.Timeout = execTimeout + } + exec := NewScriptProvider(pc.Timeout) + res = exec.StringGetter(pc.Cmd) + default: + log.FATAL.Fatalf("invalid provider type %s", pc.Type) + } + + if pc.Cache > 0 { + res = NewCached(res, pc.Cache).StringGetter() + } + + return +} + +// NewBoolGetterFromConfig creates a BoolGetter from config +func NewBoolGetterFromConfig(pc *Config) (res BoolGetter) { + switch strings.ToLower(pc.Type) { + case "mqtt": + if MQTT == nil { + log.FATAL.Fatal("mqtt not configured") + } + if pc.Timeout == 0 { + pc.Timeout = mqttTimeout + } + res = MQTT.BoolGetter(pc.Topic, pc.Timeout) + case "script": + if pc.Timeout == 0 { + pc.Timeout = execTimeout + } + exec := NewScriptProvider(pc.Timeout) + res = exec.BoolGetter(pc.Cmd) + default: + log.FATAL.Fatalf("invalid provider type %s", pc.Type) + } + + if pc.Cache > 0 { + res = NewCached(res, pc.Cache).BoolGetter() + } + + return +} + // NewBoolSetterFromConfig creates a BoolSetter from config func NewBoolSetterFromConfig(param string, pc *Config) (res BoolSetter) { - switch pc.Type { + switch strings.ToLower(pc.Type) { case "script": if pc.Timeout == 0 { pc.Timeout = execTimeout @@ -110,7 +158,7 @@ func NewBoolSetterFromConfig(param string, pc *Config) (res BoolSetter) { // NewIntSetterFromConfig creates a IntSetter from config func NewIntSetterFromConfig(param string, pc *Config) (res IntSetter) { - switch pc.Type { + switch strings.ToLower(pc.Type) { case "script": if pc.Timeout == 0 { pc.Timeout = execTimeout diff --git a/provider/exec.go b/provider/exec.go index afe02d8e0..311688ce9 100644 --- a/provider/exec.go +++ b/provider/exec.go @@ -14,10 +14,6 @@ import ( var log = api.NewLogger("exec") -func truish(s string) bool { - return s == "1" || strings.ToLower(s) == "true" || strings.ToLower(s) == "on" -} - // Script implements shell script-based providers and setters type Script struct { timeout time.Duration diff --git a/provider/helper.go b/provider/helper.go index 77397ab43..7358cce55 100644 --- a/provider/helper.go +++ b/provider/helper.go @@ -9,6 +9,11 @@ import ( var re = regexp.MustCompile(`\${(\w+)(:([a-zA-Z0-9%.]+))?}`) +// truish returns true if value is truish (true/1/on) +func truish(s string) bool { + return s == "1" || strings.ToLower(s) == "true" || strings.ToLower(s) == "on" +} + // replaceFormatted replaces all occurrances of ${key} with val from the kv map. // All keys of kv must exist inside the string to apply replacements to func replaceFormatted(s string, kv map[string]interface{}) (string, error) { diff --git a/provider/helper_test.go b/provider/helper_test.go index 627a82f22..baa387bf9 100644 --- a/provider/helper_test.go +++ b/provider/helper_test.go @@ -5,6 +5,28 @@ import ( "testing" ) +func TestTruish(t *testing.T) { + cases := []struct { + k string + v bool + }{ + {"", false}, + {"false", false}, + {"0", false}, + {"off", false}, + {"true", true}, + {"1", true}, + {"on", true}, + } + + for _, c := range cases { + b := truish(c.k) + if b != c.v { + t.Errorf("expected %v got %v", c.v, b) + } + } +} + func TestReplace(t *testing.T) { cases := []struct { k string diff --git a/provider/mqtt.go b/provider/mqtt.go index 57fbc551d..96c749ea4 100644 --- a/provider/mqtt.go +++ b/provider/mqtt.go @@ -115,14 +115,37 @@ func (m *MqttClient) FloatGetter(topic string, multiplier float64, timeout time. } // IntGetter creates handler for int64 from MQTT topic that returns cached value -func (m *MqttClient) IntGetter(topic string, timeout time.Duration) IntGetter { +func (m *MqttClient) IntGetter(topic string, multiplier int64, timeout time.Duration) IntGetter { + h := &msgHandler{ + topic: topic, + multiplier: float64(multiplier), + timeout: timeout, + } + + m.Listen(topic, h.Receive) + return h.intGetter +} + +// StringGetter creates handler for string from MQTT topic that returns cached value +func (m *MqttClient) StringGetter(topic string, timeout time.Duration) StringGetter { h := &msgHandler{ topic: topic, timeout: timeout, } m.Listen(topic, h.Receive) - return h.intGetter + return h.stringGetter +} + +// BoolGetter creates handler for string from MQTT topic that returns cached value +func (m *MqttClient) BoolGetter(topic string, timeout time.Duration) BoolGetter { + h := &msgHandler{ + topic: topic, + timeout: timeout, + } + + m.Listen(topic, h.Receive) + return h.boolGetter } // WaitForToken synchronously waits until token operation completed @@ -209,5 +232,29 @@ func (h *msgHandler) intGetter() (int64, error) { return 0, fmt.Errorf("%s invalid: '%s'", h.topic, h.payload) } - return val, nil + return int64(h.multiplier) * val, nil +} + +func (h *msgHandler) stringGetter() (string, error) { + h.once.Do(h.waitForInitialValue) + h.mux.Lock() + defer h.mux.Unlock() + + if elapsed := time.Since(h.updated); elapsed > h.timeout { + return "", fmt.Errorf("%s outdated: %v", h.topic, elapsed.Truncate(time.Second)) + } + + return string(h.payload), nil +} + +func (h *msgHandler) boolGetter() (bool, error) { + h.once.Do(h.waitForInitialValue) + h.mux.Lock() + defer h.mux.Unlock() + + if elapsed := time.Since(h.updated); elapsed > h.timeout { + return false, fmt.Errorf("%s outdated: %v", h.topic, elapsed.Truncate(time.Second)) + } + + return truish(string(h.payload)), nil }