Tasmota: add cache (#9422)
This commit is contained in:
parent
eb08e6d933
commit
abb79e4b75
3 changed files with 159 additions and 126 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue