Tasmota: add cache (#9422)

This commit is contained in:
Markus Thierolf 2023-08-18 08:12:35 +02:00 • committed by GitHub
parent eb08e6d933
commit abb79e4b75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 159 additions and 126 deletions

View file

@ -1,9 +1,7 @@
package charger
import (
"errors"
"fmt"
"strings"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/tasmota"
@ -35,20 +33,22 @@ func NewTasmotaFromConfig(other map[string]interface{}) (api.Charger, error) {
Password string
StandbyPower float64
Channel int
Cache time.Duration
}{
Channel: 1,
Cache: time.Second,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewTasmota(cc.embed, cc.URI, cc.User, cc.Password, cc.Channel, cc.StandbyPower)
return NewTasmota(cc.embed, cc.URI, cc.User, cc.Password, cc.Channel, cc.StandbyPower, cc.Cache)
}
// NewTasmota creates Tasmota charger
func NewTasmota(embed embed, uri, user, password string, channel int, standbypower float64) (*Tasmota, error) {
conn, err := tasmota.NewConnection(uri, user, password, channel)
func NewTasmota(embed embed, uri, user, password string, channel int, standbypower float64, cache time.Duration) (*Tasmota, error) {
conn, err := tasmota.NewConnection(uri, user, password, channel, cache)
if err != nil {
return nil, err
}
@ -60,112 +60,17 @@ func NewTasmota(embed embed, uri, user, password string, channel int, standbypow
c.switchSocket = NewSwitchSocket(&embed, c.Enabled, c.conn.CurrentPower, standbypower)
return c, c.channelExists(channel)
}
// channelExists checks the existence of the configured relay channel interface
func (c *Tasmota) channelExists(channel int) error {
var res *tasmota.StatusSTSResponse
if err := c.conn.ExecCmd("Status 0", &res); err != nil {
return err
}
var ok bool
switch channel {
case 1:
ok = res.StatusSTS.Power != "" || res.StatusSTS.Power1 != ""
case 2:
ok = res.StatusSTS.Power2 != ""
case 3:
ok = res.StatusSTS.Power3 != ""
case 4:
ok = res.StatusSTS.Power4 != ""
case 5:
ok = res.StatusSTS.Power5 != ""
case 6:
ok = res.StatusSTS.Power6 != ""
case 7:
ok = res.StatusSTS.Power7 != ""
case 8:
ok = res.StatusSTS.Power8 != ""
}
if !ok {
return fmt.Errorf("invalid relay channel: %d", channel)
}
return nil
return c, c.conn.ChannelExists(channel)
}
// Enabled implements the api.Charger interface
func (c *Tasmota) Enabled() (bool, error) {
var res tasmota.StatusSTSResponse
err := c.conn.ExecCmd("Status 0", &res)
if err != nil {
return false, err
}
switch c.channel {
case 2:
return strings.ToUpper(res.StatusSTS.Power2) == "ON", err
case 3:
return strings.ToUpper(res.StatusSTS.Power3) == "ON", err
case 4:
return strings.ToUpper(res.StatusSTS.Power4) == "ON", err
case 5:
return strings.ToUpper(res.StatusSTS.Power5) == "ON", err
case 6:
return strings.ToUpper(res.StatusSTS.Power6) == "ON", err
case 7:
return strings.ToUpper(res.StatusSTS.Power7) == "ON", err
case 8:
return strings.ToUpper(res.StatusSTS.Power8) == "ON", err
default:
return strings.ToUpper(res.StatusSTS.Power) == "ON" || strings.ToUpper(res.StatusSTS.Power1) == "ON", err
}
return c.conn.Enabled()
}
// Enable implements the api.Charger interface
func (c *Tasmota) Enable(enable bool) error {
var res tasmota.PowerResponse
cmd := fmt.Sprintf("Power%d off", c.channel)
if enable {
cmd = fmt.Sprintf("Power%d on", c.channel)
}
if err := c.conn.ExecCmd(cmd, &res); err != nil {
return err
}
var on bool
switch c.channel {
case 2:
on = strings.ToUpper(res.Power2) == "ON"
case 3:
on = strings.ToUpper(res.Power3) == "ON"
case 4:
on = strings.ToUpper(res.Power4) == "ON"
case 5:
on = strings.ToUpper(res.Power5) == "ON"
case 6:
on = strings.ToUpper(res.Power6) == "ON"
case 7:
on = strings.ToUpper(res.Power7) == "ON"
case 8:
on = strings.ToUpper(res.Power8) == "ON"
default:
on = strings.ToUpper(res.Power) == "ON" || strings.ToUpper(res.Power1) == "ON"
}
switch {
case enable && !on:
return errors.New("switchOn failed")
case !enable && on:
return errors.New("switchOff failed")
default:
return nil
}
return c.conn.Enable(enable)
}
var _ api.MeterEnergy = (*Tasmota)(nil)

View file

@ -1,6 +1,8 @@
package meter
import (
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/tasmota"
"github.com/evcc-io/evcc/util"
@ -25,20 +27,22 @@ func NewTasmotaFromConfig(other map[string]interface{}) (api.Meter, error) {
Password string
Channel int
Usage string
Cache time.Duration
}{
Channel: 1,
Cache: time.Second,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewTasmota(cc.URI, cc.User, cc.Password, cc.Usage, cc.Channel)
return NewTasmota(cc.URI, cc.User, cc.Password, cc.Usage, cc.Channel, cc.Cache)
}
// NewTasmota creates Tasmota meter
func NewTasmota(uri, user, password, usage string, channel int) (*Tasmota, error) {
conn, err := tasmota.NewConnection(uri, user, password, channel)
func NewTasmota(uri, user, password, usage string, channel int, cache time.Duration) (*Tasmota, error) {
conn, err := tasmota.NewConnection(uri, user, password, channel, cache)
if err != nil {
return nil, err
}

View file

@ -5,7 +5,9 @@ import (
"fmt"
"net/url"
"strings"
"time"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
@ -16,10 +18,12 @@ type Connection struct {
*request.Helper
uri, user, password string
channel int
statusSNSCache provider.Cacheable[StatusSNSResponse]
statusSTSCache provider.Cacheable[StatusSTSResponse]
}
// NewConnection creates a Tasmota connection
func NewConnection(uri, user, password string, channel int) (*Connection, error) {
func NewConnection(uri, user, password string, channel int, cache time.Duration) (*Connection, error) {
if uri == "" {
return nil, errors.New("missing uri")
}
@ -35,46 +39,166 @@ func NewConnection(uri, user, password string, channel int) (*Connection, error)
c.Client.Transport = request.NewTripper(log, transport.Insecure())
c.statusSNSCache = provider.ResettableCached(func() (StatusSNSResponse, error) {
parameters := url.Values{
"user": []string{c.user},
"password": []string{c.password},
"cmnd": []string{"Status 8"},
}
var res StatusSNSResponse
err := c.GetJSON(fmt.Sprintf("%s/cm?%s", c.uri, parameters.Encode()), &res)
return res, err
}, cache)
c.statusSTSCache = provider.ResettableCached(func() (StatusSTSResponse, error) {
parameters := url.Values{
"user": []string{c.user},
"password": []string{c.password},
"cmnd": []string{"Status 0"},
}
var res StatusSTSResponse
err := c.GetJSON(fmt.Sprintf("%s/cm?%s", c.uri, parameters.Encode()), &res)
return res, err
}, cache)
return c, nil
}
// ExecCmd executes an api command and provides the response
func (d *Connection) ExecCmd(cmd string, res interface{}) error {
// channelExists checks the existence of the configured relay channel interface
func (c *Connection) ChannelExists(channel int) error {
res, err := c.statusSTSCache.Get()
if err != nil {
return err
}
var ok bool
switch channel {
case 1:
ok = res.StatusSTS.Power != "" || res.StatusSTS.Power1 != ""
case 2:
ok = res.StatusSTS.Power2 != ""
case 3:
ok = res.StatusSTS.Power3 != ""
case 4:
ok = res.StatusSTS.Power4 != ""
case 5:
ok = res.StatusSTS.Power5 != ""
case 6:
ok = res.StatusSTS.Power6 != ""
case 7:
ok = res.StatusSTS.Power7 != ""
case 8:
ok = res.StatusSTS.Power8 != ""
}
if !ok {
return fmt.Errorf("invalid relay channel: %d", channel)
}
return nil
}
// Enable implements the api.Charger interface
func (c *Connection) Enable(enable bool) error {
cmd := fmt.Sprintf("Power%d off", c.channel)
if enable {
cmd = fmt.Sprintf("Power%d on", c.channel)
}
parameters := url.Values{
"user": []string{d.user},
"password": []string{d.password},
"user": []string{c.user},
"password": []string{c.password},
"cmnd": []string{cmd},
}
return d.GetJSON(fmt.Sprintf("%s/cm?%s", d.uri, parameters.Encode()), res)
var res PowerResponse
if err := c.GetJSON(fmt.Sprintf("%s/cm?%s", c.uri, parameters.Encode()), &res); err != nil {
return err
}
var on bool
switch c.channel {
case 2:
on = strings.ToUpper(res.Power2) == "ON"
case 3:
on = strings.ToUpper(res.Power3) == "ON"
case 4:
on = strings.ToUpper(res.Power4) == "ON"
case 5:
on = strings.ToUpper(res.Power5) == "ON"
case 6:
on = strings.ToUpper(res.Power6) == "ON"
case 7:
on = strings.ToUpper(res.Power7) == "ON"
case 8:
on = strings.ToUpper(res.Power8) == "ON"
default:
on = strings.ToUpper(res.Power) == "ON" || strings.ToUpper(res.Power1) == "ON"
}
c.statusSNSCache.Reset()
c.statusSTSCache.Reset()
switch {
case enable && !on:
return errors.New("switchOn failed")
case !enable && on:
return errors.New("switchOff failed")
default:
return nil
}
}
// Enabled implements the api.Charger interface
func (c *Connection) Enabled() (bool, error) {
res, err := c.statusSTSCache.Get()
if err != nil {
return false, err
}
switch c.channel {
case 2:
return strings.ToUpper(res.StatusSTS.Power2) == "ON", err
case 3:
return strings.ToUpper(res.StatusSTS.Power3) == "ON", err
case 4:
return strings.ToUpper(res.StatusSTS.Power4) == "ON", err
case 5:
return strings.ToUpper(res.StatusSTS.Power5) == "ON", err
case 6:
return strings.ToUpper(res.StatusSTS.Power6) == "ON", err
case 7:
return strings.ToUpper(res.StatusSTS.Power7) == "ON", err
case 8:
return strings.ToUpper(res.StatusSTS.Power8) == "ON", err
default:
return strings.ToUpper(res.StatusSTS.Power) == "ON" || strings.ToUpper(res.StatusSTS.Power1) == "ON", err
}
}
// CurrentPower implements the api.Meter interface
func (d *Connection) CurrentPower() (float64, error) {
var res StatusSNSResponse
if err := d.ExecCmd("Status 8", &res); err != nil {
func (c *Connection) CurrentPower() (float64, error) {
res, err := c.statusSNSCache.Get()
if err != nil {
return 0, err
}
return res.StatusSNS.Energy.Power.Channel(d.channel)
return res.StatusSNS.Energy.Power.Channel(c.channel)
}
// TotalEnergy implements the api.MeterEnergy interface
func (d *Connection) TotalEnergy() (float64, error) {
var res StatusSNSResponse
err := d.ExecCmd("Status 8", &res)
func (c *Connection) TotalEnergy() (float64, error) {
res, err := c.statusSNSCache.Get()
return res.StatusSNS.Energy.Total, err
}
// SmlPower provides the sml sensor power
func (d *Connection) SmlPower() (float64, error) {
var res StatusSNSResponse
err := d.ExecCmd("Status 8", &res)
func (c *Connection) SmlPower() (float64, error) {
res, err := c.statusSNSCache.Get()
return float64(res.StatusSNS.SML.PowerCurr), err
}
// SmlTotalEnergy provides the sml sensor total import energy
func (d *Connection) SmlTotalEnergy() (float64, error) {
var res StatusSNSResponse
err := d.ExecCmd("Status 8", &res)
func (c *Connection) SmlTotalEnergy() (float64, error) {
res, err := c.statusSNSCache.Get()
return res.StatusSNS.SML.TotalIn, err
}