Skip to content

Commit

Permalink
Tasmota: enable multi-relay devices (evcc-io#3368)
Browse files Browse the repository at this point in the history
  • Loading branch information
thierolm authored May 17, 2022
1 parent 31eb3e2 commit ee8d3db
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 25 deletions.
66 changes: 55 additions & 11 deletions charger/tasmota.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package charger

import (
"errors"
"fmt"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/tasmota"
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion meter/tasmota.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
46 changes: 45 additions & 1 deletion meter/tasmota/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
38 changes: 31 additions & 7 deletions meter/tasmota/types.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
18 changes: 17 additions & 1 deletion templates/definition/charger/tasmota.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
2 changes: 1 addition & 1 deletion templates/definition/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion templates/definition/meter/tasmota.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 2 additions & 1 deletion templates/docs/charger/tasmota_0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion templates/docs/meter/tasmota_0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit ee8d3db

Please sign in to comment.