From ee8d3db356cce319139f41c6ba9eddbf3bcd4787 Mon Sep 17 00:00:00 2001 From: Markus Thierolf <77847348+thierolm@users.noreply.github.com> Date: Tue, 17 May 2022 11:39:54 +0200 Subject: [PATCH] Tasmota: enable multi-relay devices (#3368) --- charger/tasmota.go | 66 +++++++++++++++++++---- meter/tasmota.go | 3 +- meter/tasmota/connection.go | 46 +++++++++++++++- meter/tasmota/types.go | 38 ++++++++++--- templates/definition/charger/tasmota.yaml | 18 ++++++- templates/definition/defaults.yaml | 2 +- templates/definition/meter/tasmota.yaml | 14 ++++- templates/docs/charger/tasmota_0.yaml | 3 +- templates/docs/meter/tasmota_0.yaml | 3 +- 9 files changed, 168 insertions(+), 25 deletions(-) diff --git a/charger/tasmota.go b/charger/tasmota.go index e34d545d5..cad8b8ba4 100644 --- a/charger/tasmota.go +++ b/charger/tasmota.go @@ -2,6 +2,7 @@ package charger import ( "errors" + "fmt" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/meter/tasmota" @@ -30,17 +31,18 @@ func NewTasmotaFromConfig(other map[string]interface{}) (api.Charger, error) { User string Password string StandbyPower float64 + Channel int }{} if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - return NewTasmota(cc.URI, cc.User, cc.Password, cc.StandbyPower) + return NewTasmota(cc.URI, cc.User, cc.Password, cc.Channel, cc.StandbyPower) } // NewTasmota creates Tasmota charger -func NewTasmota(uri, user, password string, standbypower float64) (*Tasmota, error) { - conn, err := tasmota.NewConnection(uri, user, password) +func NewTasmota(uri, user, password string, channel int, standbypower float64) (*Tasmota, error) { + conn, err := tasmota.NewConnection(uri, user, password, channel) if err != nil { return nil, err } @@ -55,27 +57,69 @@ func NewTasmota(uri, user, password string, standbypower float64) (*Tasmota, err // Enabled implements the api.Charger interface func (c *Tasmota) Enabled() (bool, error) { - var res tasmota.StatusResponse + var res tasmota.StatusSTSResponse err := c.conn.ExecCmd("Status 0", &res) + if err != nil { + return false, err + } - return res.Status.Power == 1, err + switch c.conn.Channel { + case 2: + return res.StatusSTS.Power2 == "ON", err + case 3: + return res.StatusSTS.Power3 == "ON", err + case 4: + return res.StatusSTS.Power4 == "ON", err + case 5: + return res.StatusSTS.Power5 == "ON", err + case 6: + return res.StatusSTS.Power6 == "ON", err + case 7: + return res.StatusSTS.Power7 == "ON", err + case 8: + return res.StatusSTS.Power8 == "ON", err + default: + return res.StatusSTS.Power == "ON" || res.StatusSTS.Power1 == "ON", err + } } // Enable implements the api.Charger interface func (c *Tasmota) Enable(enable bool) error { var res tasmota.PowerResponse - cmd := "Power off" + on := false + cmd := fmt.Sprintf("Power%d off", c.conn.Channel) if enable { - cmd = "Power on" + cmd = fmt.Sprintf("Power%d on", c.conn.Channel) } + err := c.conn.ExecCmd(cmd, &res) + if err != nil { + return err + } + + switch c.conn.Channel { + case 2: + on = res.Power2 == "ON" + case 3: + on = res.Power3 == "ON" + case 4: + on = res.Power4 == "ON" + case 5: + on = res.Power5 == "ON" + case 6: + on = res.Power6 == "ON" + case 7: + on = res.Power7 == "ON" + case 8: + on = res.Power8 == "ON" + default: + on = res.Power == "ON" || res.Power1 == "ON" + } switch { - case err != nil: - return err - case enable && res.Power != "ON": + case enable && !on: return errors.New("switchOn failed") - case !enable && res.Power != "OFF": + case !enable && on: return errors.New("switchOff failed") default: return nil diff --git a/meter/tasmota.go b/meter/tasmota.go index 5cc52c891..658c7c42e 100644 --- a/meter/tasmota.go +++ b/meter/tasmota.go @@ -17,11 +17,12 @@ func NewTasmotaFromConfig(other map[string]interface{}) (api.Meter, error) { URI string User string Password string + Channel int }{} if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - return tasmota.NewConnection(cc.URI, cc.User, cc.Password) + return tasmota.NewConnection(cc.URI, cc.User, cc.Password, cc.Channel) } diff --git a/meter/tasmota/connection.go b/meter/tasmota/connection.go index 53b2693ee..acd0d4ea2 100644 --- a/meter/tasmota/connection.go +++ b/meter/tasmota/connection.go @@ -15,10 +15,11 @@ import ( type Connection struct { *request.Helper uri, user, password string + Channel int } // NewConnection creates Tasmota charger -func NewConnection(uri, user, password string) (*Connection, error) { +func NewConnection(uri, user, password string, channel int) (*Connection, error) { if uri == "" { return nil, errors.New("missing uri") } @@ -29,10 +30,16 @@ func NewConnection(uri, user, password string) (*Connection, error) { uri: util.DefaultScheme(strings.TrimRight(uri, "/"), "http"), user: user, password: password, + Channel: channel, } c.Client.Transport = request.NewTripper(log, transport.Insecure()) + err := c.ChannelExists() + if err != nil { + return nil, err + } + return c, nil } @@ -73,3 +80,40 @@ func (d *Connection) TotalEnergy() (float64, error) { return float64(res.StatusSNS.Energy.Total), nil } + +// ChannelExists checks the existence of the configured relay channel interface +func (d *Connection) ChannelExists() error { + var res *StatusSTSResponse + err := d.ExecCmd("Status 0", &res) + if err != nil { + return err + } + + channelexists := false + switch d.Channel { + case 1: + channelexists = res.StatusSTS.Power != "" || res.StatusSTS.Power1 != "" + case 2: + channelexists = res.StatusSTS.Power2 != "" + case 3: + channelexists = res.StatusSTS.Power3 != "" + case 4: + channelexists = res.StatusSTS.Power4 != "" + case 5: + channelexists = res.StatusSTS.Power5 != "" + case 6: + channelexists = res.StatusSTS.Power6 != "" + case 7: + channelexists = res.StatusSTS.Power7 != "" + case 8: + channelexists = res.StatusSTS.Power8 != "" + default: + channelexists = false + } + + if !channelexists { + return errors.New("configured relay channel doesn't exist on device") + } + + return nil +} diff --git a/meter/tasmota/types.go b/meter/tasmota/types.go index 0459de7f5..a742a0237 100644 --- a/meter/tasmota/types.go +++ b/meter/tasmota/types.go @@ -1,6 +1,6 @@ package tasmota -// StatusResponse is a part of the Tasmota Status 0 command Status response +// StatusResponse is the Status part of the Tasmota Status 0 command response // https://tasmota.github.io/docs/JSON-Status-Responses/ type StatusResponse struct { Status struct { @@ -26,6 +26,36 @@ type StatusResponse struct { } } +// StatusSTSResponse is the StatusSTS part of the Tasmota Status 0 command response +// https://tasmota.github.io/docs/JSON-Status-Responses/ +type StatusSTSResponse struct { + StatusSTS struct { + Power string // ON, OFF, Error + Power1 string // ON, OFF, Error + Power2 string // ON, OFF, Error + Power3 string // ON, OFF, Error + Power4 string // ON, OFF, Error + Power5 string // ON, OFF, Error + Power6 string // ON, OFF, Error + Power7 string // ON, OFF, Error + Power8 string // ON, OFF, Error + } +} + +// PowerResponse is the Tasmota Power command Status response +// https://tasmota.github.io/docs/Commands/#with-web-requests +type PowerResponse struct { + Power string // ON, OFF, Error + Power1 string // ON, OFF, Error + Power2 string // ON, OFF, Error + Power3 string // ON, OFF, Error + Power4 string // ON, OFF, Error + Power5 string // ON, OFF, Error + Power6 string // ON, OFF, Error + Power7 string // ON, OFF, Error + Power8 string // ON, OFF, Error +} + // StatusSNSResponse is the Tasmota Status 8 command Status response // https://tasmota.github.io/docs/JSON-Status-Responses/ type StatusSNSResponse struct { @@ -45,9 +75,3 @@ type StatusSNSResponse struct { } } } - -// PowerResponse is the Tasmota Power command Status response -// https://tasmota.github.io/docs/Commands/#with-web-requests -type PowerResponse struct { - Power string // ON, OFF, Error -} diff --git a/templates/definition/charger/tasmota.yaml b/templates/definition/charger/tasmota.yaml index 582ca4b50..eb17fd834 100644 --- a/templates/definition/charger/tasmota.yaml +++ b/templates/definition/charger/tasmota.yaml @@ -3,19 +3,35 @@ products: - brand: Tasmota group: switchsockets params: + - name: host - name: host - name: user + required: false + help: + de: Standard-User ist admin + en: admin is default - name: password + required: false mask: true + - name: channel + default: 1 + required: true + description: + de: Nummer des Schalt-Kanals + en: Device relay channel number + help: + de: Nummer des Schalt-Kanals (1-8), bei Geräten mit mehr als einem Schalter + en: Device relay channel number (allowed range 1-8) - name: standbypower default: 15 render: | type: tasmota - uri: http://{{ .host }} # shelly device ip address (local) + uri: http://{{ .host }} {{ if ne .user "" }} user: {{ .user }} {{ end }} {{ if ne .password "" }} password: {{ .password }} {{ end }} + channel: {{ .channel }} # relay channel (allowed range: 1-8) standbypower: {{ .standbypower }} diff --git a/templates/definition/defaults.yaml b/templates/definition/defaults.yaml index 1b08d103d..ec52338bb 100644 --- a/templates/definition/defaults.yaml +++ b/templates/definition/defaults.yaml @@ -196,7 +196,7 @@ params: valuetype: stringlist - name: standbypower description: - de: Standby-Leistung in W" + de: Standby-Leistung in W en: Standby power in W help: de: Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet diff --git a/templates/definition/meter/tasmota.yaml b/templates/definition/meter/tasmota.yaml index d0423988e..802af2254 100644 --- a/templates/definition/meter/tasmota.yaml +++ b/templates/definition/meter/tasmota.yaml @@ -9,12 +9,24 @@ params: - name: host - name: user required: false - default: admin + help: + de: Standard-User ist admin + en: admin is default - name: password required: false mask: true + - name: channel + default: 1 + required: true + description: + de: Nummer des Schalt-Kanals + en: Device relay channel number + help: + de: Nummer des Schalt-Kanals (1-8), bei Geräten mit mehr als einem Schalter + en: Device relay channel number (allowed range 1-8) render: | type: tasmota uri: http://{{ .host }} user: {{ .user }} password: {{ .password }} + channel: {{ .channel }} # # relay channel (allowed range: 1-8) diff --git a/templates/docs/charger/tasmota_0.yaml b/templates/docs/charger/tasmota_0.yaml index 208d8e4d3..43ef994bd 100644 --- a/templates/docs/charger/tasmota_0.yaml +++ b/templates/docs/charger/tasmota_0.yaml @@ -6,6 +6,7 @@ render: type: template template: tasmota host: 192.0.2.2 # IP-Adresse oder Hostname - user: # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.) # Optional + user: # Standard-User ist admin # Optional password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen) # Optional + channel: 1 # Nummer des Schalt-Kanals (1-8), bei Geräten mit mehr als einem Schalter standbypower: 15 # Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet # Optional diff --git a/templates/docs/meter/tasmota_0.yaml b/templates/docs/meter/tasmota_0.yaml index d75c2fdec..5cdcbc3a5 100644 --- a/templates/docs/meter/tasmota_0.yaml +++ b/templates/docs/meter/tasmota_0.yaml @@ -8,5 +8,6 @@ render: template: tasmota usage: pv host: 192.0.2.2 # IP-Adresse oder Hostname - user: admin # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.) # Optional + user: # Standard-User ist admin # Optional password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen) # Optional + channel: 1 # Nummer des Schalt-Kanals (1-8), bei Geräten mit mehr als einem Schalter