Skip to content

Commit

Permalink
Add TP-Link Tapo Charger (P100,P110) (#3005)
Browse files Browse the repository at this point in the history
  • Loading branch information
thierolm authored Mar 27, 2022
1 parent d8a6efc commit 4460ed5
Show file tree
Hide file tree
Showing 7 changed files with 612 additions and 0 deletions.
170 changes: 170 additions & 0 deletions charger/tapo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package charger

import (
"errors"
"fmt"
"strings"
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/tapo"
"github.com/evcc-io/evcc/util"
)

// TP-Link Tapo charger implementation
type Tapo struct {
conn *tapo.Connection
standbypower float64
updated time.Time
lasttodayenergy int64
energy int64
}

func init() {
registry.Add("tapo", NewTapoFromConfig)
}

// NewTapoFromConfig creates a Tapo charger from generic config
func NewTapoFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
URI string
User string
Password string
StandbyPower float64
}{}

if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}

if cc.URI == "" {
return nil, errors.New("missing uri")
}

return NewTapo(cc.URI, cc.User, cc.Password, cc.StandbyPower)
}

// NewTapo creates Tapo charger
func NewTapo(uri, user, password string, standbypower float64) (*Tapo, error) {
for _, suffix := range []string{"/", "/app"} {
uri = strings.TrimSuffix(uri, suffix)
}

conn := tapo.NewConnection(uri, user, password)

tapo := &Tapo{
conn: conn,
standbypower: standbypower,
}

if user == "" || password == "" {
return tapo, fmt.Errorf("missing user/password")
}

return tapo, nil
}

// Enabled implements the api.Charger interface
func (c *Tapo) Enabled() (bool, error) {
resp, err := c.execTapoCmd("get_device_info", false)
if err != nil {
return false, err
}
return resp.Result.DeviceON, nil
}

// Enable implements the api.Charger interface
func (c *Tapo) Enable(enable bool) error {
_, err := c.execTapoCmd("set_device_info", enable)
return err
}

// MaxCurrent implements the api.Charger interface
func (c *Tapo) MaxCurrent(current int64) error {
return nil
}

// Status implements the api.Charger interface
func (c *Tapo) Status() (api.ChargeStatus, error) {
res := api.StatusB
on, err := c.Enabled()
if err != nil {
return res, err
}

power, err := c.CurrentPower()
if err != nil {
return res, err
}

// static mode || standby power mode condition
if on && (c.standbypower < 0 || power > c.standbypower) {
res = api.StatusC
}

return res, nil
}

var _ api.Meter = (*Tapo)(nil)

// CurrentPower implements the api.Meter interface
func (c *Tapo) CurrentPower() (float64, error) {
resp, err := c.execTapoCmd("get_energy_usage", false)
if err != nil {
return 0, err
}

power := float64(resp.Result.Current_Power) / 1000

// ignore power in standby mode
if c.standbypower >= 0 && power <= c.standbypower {
power = 0
}

// set fix static power in static mode
if c.standbypower < 0 {
on, err := c.Enabled()
if err != nil {
return 0, err
}
if on {
power = c.standbypower * -1
} else {
power = 0
}
}

return power, nil
}

var _ api.ChargeRater = (*Vestel)(nil)

// ChargedEnergy implements the api.ChargeRater interface
func (c *Tapo) ChargedEnergy() (float64, error) {
resp, err := c.execTapoCmd("get_energy_usage", false)
if err != nil {
return 0, err
}

if resp.Result.Today_Energy > c.lasttodayenergy {
c.energy = c.energy + (resp.Result.Today_Energy - c.lasttodayenergy)
}
c.lasttodayenergy = resp.Result.Today_Energy

return float64(c.energy) / 1000, nil
}

// execTapoCmd executes a Tapo api command and provides the response
func (c *Tapo) execTapoCmd(method string, enable bool) (*tapo.DeviceResponse, error) {
// refresh Tapo session id
if time.Since(c.updated) >= 600*time.Minute {
err := c.conn.Login()
if err != nil {
return nil, err
}
// update session timestamp
c.updated = time.Now()
}

return c.conn.ExecMethod(method, enable)
}
Loading

0 comments on commit 4460ed5

Please sign in to comment.