Document smart home outlets and simplify TPLink (#919)

This commit is contained in:
andig 2021-04-25 18:56:56 +02:00 • committed by GitHub
parent 3284444279
commit ef06e9b489
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 48 deletions

View file

@ -11,7 +11,9 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G
## Features <!-- omit in toc -->
- simple and clean user interface
- multiple [chargers](#charger): Wallbe, Phoenix (includes ESL Walli), go-eCharger, NRGkick (direct Bluetooth or via Connect device), SimpleEVSE, EVSEWifi, KEBA/BMW, openWB, Mobile Charger Connect, Fritz!DECT outlets, Tasmota outlets and any other charger using scripting
- multiple [chargers](#charger):
- Wallbe, Phoenix (includes ESL Walli), go-eCharger, NRGkick (direct Bluetooth or via Connect device), SimpleEVSE, EVSEWifi, KEBA/BMW, openWB, Mobile Charger Connect and any other charger using scripting
- Smart-Home outlets: FritzDECT, Tasmota, TP-Link
- multiple [meters](#meter): ModBus (Eastron SDM, MPM3PM, SBC ALE3 and many more), Discovergy (using HTTP plugin), SMA Sunny Home Manager and Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx), any Sunspec-compatible inverter or home battery devices (Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC, ...), Tesla PowerWall
- wide support of vendor-specific [vehicles](#vehicle) interfaces (remote charge, battery and preconditioning status): Audi, BMW, Ford, Hyundai, Kia, Nissan, Niu, Porsche, Renault, Seat, Skoda, Tesla, Volkswagen, Volvo and any other connected vehicle using scripting
- [plugins](#plugins) for integrating with hardware devices and home automation: Modbus (meters and grid inverters), HTTP, MQTT, Javascript, WebSockets and shell scripts
@ -166,7 +168,8 @@ In general, due to the minimum value of 5% for signalling the EV duty cycle, the
### Charger
Charger is responsible for handling EV state and adjusting charge current. Available charger implementations are:
Charger is responsible for handling EV state and adjusting charge current.
Available charger implementations are:
- `evsewifi`: chargers with SimpleEVSE controllers using [EVSE-WiFi](https://www.evse-wifi.de/)
- `go-e`: go-eCharger chargers (both local and cloud API are supported, at least firmware 040.0 required)
@ -181,10 +184,13 @@ Charger is responsible for handling EV state and adjusting charge current. Avail
- `simpleevse`: chargers with SimpleEVSE controllers connected via ModBus (e.g. OpenWB Wallbox, Easy Wallbox B163, ...)
- `wallbe`: Wallbe Eco chargers (see [Preparation](#wallbe-preparation-)). For older Wallbe boxes (pre 2019) with Phoenix EV-CC-AC1-M3-CBC-RCM-ETH controllers make sure to set `legacy: true` to enable correct current configuration.
- `warp`: Tinkerforge Warp/ Warp Pro charger
- `fritzdect`: pseudo charger using Fritz!DECT 200/210 outlets
- `tasmota`: pseudo charger using Tasmota outlets
- `default`: default charger implementation using configurable [plugins](#plugins) for integrating any type of charger
Smart-Home outlet charger implementations:
- `fritzdect`: Fritz!DECT 200/210 outlets
- `tasmota`: Tasmota outlets
- `tplink`: TP-Link HSXXX series outlets
Configuration examples are documented at [andig/evcc-config#chargers](https://github.com/andig/evcc-config#chargers)
#### KEBA preparation <!-- omit in toc -->

View file

@ -17,6 +17,7 @@ import (
// TPLink charger implementation
type TPLink struct {
log *util.Logger
uri string
standbypower float64
}
@ -46,6 +47,7 @@ func NewTPLinkFromConfig(other map[string]interface{}) (api.Charger, error) {
// NewTPLink creates TP-Link charger
func NewTPLink(uri string, standbypower float64) (*TPLink, error) {
c := &TPLink{
log: util.NewLogger("tplink"),
uri: net.JoinHostPort(uri, "9999"),
standbypower: standbypower,
}
@ -54,25 +56,20 @@ func NewTPLink(uri string, standbypower float64) (*TPLink, error) {
// Enabled implements the Charger.Enabled interface
func (c *TPLink) Enabled() (bool, error) {
sysResp, err := c.execCmd(`{"system":{"get_sysinfo":null}}`)
if err != nil {
var resp tplink.SystemResponse
if err := c.execCmd(`{"system":{"get_sysinfo":null}}`, &resp); err != nil {
return false, err
}
var systemResponse tplink.SystemResponse
if err := json.Unmarshal(sysResp, &systemResponse); err != nil {
return false, err
}
if err := systemResponse.System.GetSysinfo.ErrCode; err != 0 {
if err := resp.System.GetSysinfo.ErrCode; err != 0 {
return false, fmt.Errorf("get_sysinfo error %d", err)
}
if !strings.Contains(systemResponse.System.GetSysinfo.Feature, "ENE") {
return false, errors.New(systemResponse.System.GetSysinfo.Model + " not supported, energy meter feature missing")
if !strings.Contains(resp.System.GetSysinfo.Feature, "ENE") {
return false, errors.New(resp.System.GetSysinfo.Model + " not supported, energy meter feature missing")
}
return int(1) == systemResponse.System.GetSysinfo.RelayState, err
return resp.System.GetSysinfo.RelayState == 1, nil
}
// Enable implements the Charger.Enable interface
@ -82,18 +79,12 @@ func (c *TPLink) Enable(enable bool) error {
cmd = `{"system":{"set_relay_state":{"state":1}}}`
}
// Execute TP-Link set_relay_state command
sysResp, err := c.execCmd(cmd)
if err != nil {
var resp tplink.SystemResponse
if err := c.execCmd(cmd, &resp); err != nil {
return err
}
var systemResponse tplink.SystemResponse
if err := json.Unmarshal(sysResp, &systemResponse); err != nil {
return err
}
if err := systemResponse.System.SetRelayState.ErrCode; err != 0 {
if err := resp.System.SetRelayState.ErrCode; err != 0 {
return fmt.Errorf("set_relay_state error %d", err)
}
@ -121,22 +112,18 @@ var _ api.Meter = (*TPLink)(nil)
// CurrentPower implements the api.Meter interface
func (c *TPLink) CurrentPower() (float64, error) {
emeResp, err := c.execCmd(`{"emeter":{"get_realtime":null}}`)
if err != nil {
var resp tplink.EmeterResponse
if err := c.execCmd(`{"emeter":{"get_realtime":null}}`, &resp); err != nil {
return 0, err
}
var emeterResponse tplink.EmeterResponse
if err := json.Unmarshal(emeResp, &emeterResponse); err != nil {
return 0, err
}
if err := emeterResponse.Emeter.GetRealtime.ErrCode; err != 0 {
if err := resp.Emeter.GetRealtime.ErrCode; err != 0 {
return 0, fmt.Errorf("get_realtime error %d", err)
}
power := emeterResponse.Emeter.GetRealtime.PowerMw / 1000
power := resp.Emeter.GetRealtime.PowerMw / 1000
if power == 0 {
power = emeterResponse.Emeter.GetRealtime.Power
power = resp.Emeter.GetRealtime.Power
}
// ignore standby power
@ -144,48 +131,49 @@ func (c *TPLink) CurrentPower() (float64, error) {
power = 0
}
return power, err
return power, nil
}
// execCmd executes an TP-Link Smart Home Protocol command and provides the response
func (c *TPLink) execCmd(cmd string) ([]byte, error) {
func (c *TPLink) execCmd(cmd string, res interface{}) error {
// encode command message
buf := bytes.NewBuffer([]byte{0, 0, 0, 0})
var ekey byte = 171 // initialization vector
var key byte = 171 // initialization vector
for i := 0; i < len(cmd); i++ {
ekey = ekey ^ cmd[i]
_ = buf.WriteByte(ekey)
key = key ^ cmd[i]
_ = buf.WriteByte(key)
}
// write 4 bytes to start of buffer with command length
// write 4 bytes command length to start of buffer
binary.BigEndian.PutUint32(buf.Bytes(), uint32(buf.Len()-4))
// open connection via TP-Link Smart Home Protocol
conn, err := net.DialTimeout("tcp", c.uri, 5*time.Second)
if err != nil {
return nil, err
return err
}
defer conn.Close()
// send command
if _, err = buf.WriteTo(conn); err != nil {
return nil, err
return err
}
// read response
resp := make([]byte, 2048)
n, err := conn.Read(resp)
len, err := conn.Read(resp)
if err != nil {
return nil, err
return err
}
// decode response message
var dkey byte = 171 // initialization vector
for i := 4; i < n; i++ {
dec := dkey ^ resp[i]
dkey = resp[i]
key = 171 // reset initialization vector
for i := 4; i < len; i++ {
dec := key ^ resp[i]
key = resp[i]
_ = buf.WriteByte(dec)
}
c.log.TRACE.Printf("recv: %s", buf.String())
return buf.Bytes(), nil
return json.Unmarshal(buf.Bytes(), res)
}