From c013dc53f88673eada5dec04dacc6eeab1db9f12 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 13 Jul 2022 18:17:54 +0200 Subject: [PATCH] Add myStrom switch (#3832) --- charger/helper.go | 30 +++++++++ charger/mystrom.go | 78 +++++++++++++++++++++++ meter/mystrom.go | 27 ++++++++ meter/mystrom/mystrom.go | 46 +++++++++++++ templates/definition/charger/mystrom.yaml | 16 +++++ templates/definition/meter/mystrom.yaml | 13 ++++ templates/docs/charger/mystrom_0.yaml | 19 ++++++ templates/docs/meter/mystrom_0.yaml | 17 +++++ templates/evcc.io/brands.json | 3 + 9 files changed, 249 insertions(+) create mode 100644 charger/helper.go create mode 100644 charger/mystrom.go create mode 100644 meter/mystrom.go create mode 100644 meter/mystrom/mystrom.go create mode 100644 templates/definition/charger/mystrom.yaml create mode 100644 templates/definition/meter/mystrom.yaml create mode 100644 templates/docs/charger/mystrom_0.yaml create mode 100644 templates/docs/meter/mystrom_0.yaml diff --git a/charger/helper.go b/charger/helper.go new file mode 100644 index 000000000..4b1c808c0 --- /dev/null +++ b/charger/helper.go @@ -0,0 +1,30 @@ +package charger + +import "github.com/evcc-io/evcc/api" + +// switchStatus calculates a generic switches status +func switchStatus( + enabled func() (bool, error), + currentPower func() (float64, error), + standbypower float64, +) (api.ChargeStatus, error) { + res := api.StatusB + + // static mode + if standbypower < 0 { + on, err := enabled() + if on { + res = api.StatusC + } + + return res, err + } + + // standby power mode + power, err := currentPower() + if power > standbypower { + res = api.StatusC + } + + return res, err +} diff --git a/charger/mystrom.go b/charger/mystrom.go new file mode 100644 index 000000000..f25a038f3 --- /dev/null +++ b/charger/mystrom.go @@ -0,0 +1,78 @@ +package charger + +import ( + "fmt" + "time" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/meter/mystrom" + "github.com/evcc-io/evcc/provider" + "github.com/evcc-io/evcc/util" +) + +// myStrom switch: +// https://api.mystrom.ch/#fbb2c698-e37a-4584-9324-3f8b2f615fe2 + +func init() { + registry.Add("mystrom", NewMyStromFromConfig) +} + +// MyStrom charger implementation +type MyStrom struct { + *mystrom.Connection + standbypower float64 + cache time.Duration + reportG func() (mystrom.Report, error) +} + +// NewMyStromFromConfig creates a myStrom charger from generic config +func NewMyStromFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := struct { + URI string + StandbyPower float64 + Cache time.Duration + }{ + Cache: time.Second, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + c := &MyStrom{ + Connection: mystrom.NewConnection(cc.URI), + standbypower: cc.StandbyPower, + cache: cc.Cache, + } + + c.reportG = provider.Cached(c.Report, c.cache) + + return c, nil +} + +var _ api.Meter = (*MyStrom)(nil) + +// Status implements the api.Charger interface +func (c *MyStrom) Status() (api.ChargeStatus, error) { + return switchStatus(c.Enabled, c.CurrentPower, c.standbypower) +} + +// Enabled implements the api.Charger interface +func (c *MyStrom) Enabled() (bool, error) { + res, err := c.reportG() + return res.Relay, err +} + +// Enable implements the api.Charger interface +func (c *MyStrom) Enable(enable bool) error { + // reset cache + c.reportG = provider.Cached(c.Report, c.cache) + + onoff := map[bool]int{false: 0, true: 1} + return c.Request(fmt.Sprintf("relay/state=%d", onoff[enable])) +} + +// MaxCurrent implements the api.Charger interface +func (c *MyStrom) MaxCurrent(current int64) error { + return nil +} diff --git a/meter/mystrom.go b/meter/mystrom.go new file mode 100644 index 000000000..cba5ac44c --- /dev/null +++ b/meter/mystrom.go @@ -0,0 +1,27 @@ +package meter + +import ( + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/meter/mystrom" + "github.com/evcc-io/evcc/util" +) + +// myStrom switch: +// https://api.mystrom.ch/#fbb2c698-e37a-4584-9324-3f8b2f615fe2 + +func init() { + registry.Add("mystrom", NewMyStromFromConfig) +} + +// NewMyStromFromConfig creates a myStrom meter from generic config +func NewMyStromFromConfig(other map[string]interface{}) (api.Meter, error) { + var cc struct { + URI string + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + return mystrom.NewConnection(cc.URI), nil +} diff --git a/meter/mystrom/mystrom.go b/meter/mystrom/mystrom.go new file mode 100644 index 000000000..b4514dac5 --- /dev/null +++ b/meter/mystrom/mystrom.go @@ -0,0 +1,46 @@ +package mystrom + +import ( + "fmt" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" +) + +type Report struct { + Power float64 + Relay bool +} + +type Connection struct { + *request.Helper + uri string +} + +func NewConnection(uri string) *Connection { + return &Connection{ + uri: uri, + Helper: request.NewHelper(util.NewLogger("mystrom")), + } +} + +func (c *Connection) Request(path string) error { + uri := fmt.Sprintf("%s/%s", c.uri, path) + return c.GetJSON(uri, nil) +} + +func (c *Connection) Report() (Report, error) { + var res Report + uri := fmt.Sprintf("%s/report", c.uri) + err := c.GetJSON(uri, &res) + return res, err +} + +var _ api.Meter = (*Connection)(nil) + +// CurrentPower implements the api.Meter interface +func (c *Connection) CurrentPower() (float64, error) { + res, err := c.Report() + return res.Power, err +} diff --git a/templates/definition/charger/mystrom.yaml b/templates/definition/charger/mystrom.yaml new file mode 100644 index 000000000..09f417a72 --- /dev/null +++ b/templates/definition/charger/mystrom.yaml @@ -0,0 +1,16 @@ +template: mystrom +products: + - brand: myStrom + description: + generic: Switch +group: switchsockets +params: + - name: usage + choice: ["pv", "charge"] + - name: host + - name: standbypower + default: 15 +render: | + type: mystrom + uri: http://{{ .host }} + standbypower: {{ .standbypower }} diff --git a/templates/definition/meter/mystrom.yaml b/templates/definition/meter/mystrom.yaml new file mode 100644 index 000000000..3b3dc1369 --- /dev/null +++ b/templates/definition/meter/mystrom.yaml @@ -0,0 +1,13 @@ +template: mystrom +products: + - brand: myStrom + description: + generic: Switch +group: switchsockets +params: + - name: usage + choice: ["pv", "charge"] + - name: host +render: | + type: mystrom + uri: http://{{ .host }} diff --git a/templates/docs/charger/mystrom_0.yaml b/templates/docs/charger/mystrom_0.yaml new file mode 100644 index 000000000..4e48d455f --- /dev/null +++ b/templates/docs/charger/mystrom_0.yaml @@ -0,0 +1,19 @@ +product: + brand: myStrom + description: Switch + group: Schaltbare Steckdosen +render: + - usage: pv + default: | + type: template + template: mystrom + usage: pv + host: 192.0.2.2 # IP-Adresse oder Hostname + standbypower: 15 # Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet # Optional + - usage: charge + default: | + type: template + template: mystrom + usage: charge + host: 192.0.2.2 # IP-Adresse oder Hostname + standbypower: 15 # Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet # Optional diff --git a/templates/docs/meter/mystrom_0.yaml b/templates/docs/meter/mystrom_0.yaml new file mode 100644 index 000000000..4be2b4a63 --- /dev/null +++ b/templates/docs/meter/mystrom_0.yaml @@ -0,0 +1,17 @@ +product: + brand: myStrom + description: Switch + group: Schaltbare Steckdosen +render: + - usage: pv + default: | + type: template + template: mystrom + usage: pv + host: 192.0.2.2 # IP-Adresse oder Hostname + - usage: charge + default: | + type: template + template: mystrom + usage: charge + host: 192.0.2.2 # IP-Adresse oder Hostname diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json index 7a09e5664..d3e5fd40f 100644 --- a/templates/evcc.io/brands.json +++ b/templates/evcc.io/brands.json @@ -44,6 +44,7 @@ "SmartPlugs": [ "AVM", "Homematic IP", + "myStrom", "Shelly", "Tasmota", "TP-Link" @@ -62,6 +63,7 @@ "Janitza", "Kostal", "LG", + "myStrom", "OpenEMS", "Powerfox", "RCT", @@ -92,6 +94,7 @@ "Janitza", "Kostal", "LG", + "myStrom", "OpenEMS", "RCT", "SENEC",