-
-
Notifications
You must be signed in to change notification settings - Fork 620
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TP-Link Tapo Charger (P100,P110) (#3005)
- Loading branch information
Showing
7 changed files
with
612 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.