Document smart home outlets and simplify TPLink (#919)
This commit is contained in:
parent
3284444279
commit
ef06e9b489
2 changed files with 42 additions and 48 deletions
14
README.md
14
README.md
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue