Add Tasmota pseudo charger (#829)

This commit is contained in:
Markus Thierolf 2021-04-03 00:01:50 +02:00 • committed by GitHub
parent 6df4d777b9
commit 711ec8357d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 208 additions and 1 deletions

View file

@ -11,7 +11,7 @@ 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 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, Fritz!DECT outlets, Tasmota outlets and any other charger using scripting
- 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, 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
@ -182,6 +182,7 @@ Charger is responsible for handling EV state and adjusting charge current. Avail
- `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
Configuration examples are documented at [andig/evcc-config#chargers](https://github.com/andig/evcc-config#chargers)

152
internal/charger/tasmota.go Normal file
View file

@ -0,0 +1,152 @@
package charger
import (
"errors"
"fmt"
"math"
"net/url"
"strings"
"github.com/andig/evcc/api"
"github.com/andig/evcc/internal/charger/tasmota"
"github.com/andig/evcc/util"
"github.com/andig/evcc/util/request"
)
// Tasmota project homepage
// https://tasmota.github.io/docs/
// Supported devices:
// https://templates.blakadder.com/
// Tasmota charger implementation
type Tasmota struct {
*request.Helper
uri, user, password string
standbypower float64
}
func init() {
registry.Add("tasmota", NewTasmotaFromConfig)
}
// NewTasmotaFromConfig creates a Tasmota charger from generic config
func NewTasmotaFromConfig(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 NewTasmota(cc.URI, cc.User, cc.Password, cc.StandbyPower)
}
// NewTasmota creates Tasmota charger
func NewTasmota(uri, user, password string, standbypower float64) (*Tasmota, error) {
log := util.NewLogger("tasmota")
c := &Tasmota{
Helper: request.NewHelper(log),
uri: strings.TrimRight(uri, "/"),
user: user,
password: password,
standbypower: standbypower,
}
c.Client.Transport = request.NewTripper(log, request.InsecureTransport())
return c, nil
}
// Enabled implements the Charger.Enabled interface
func (c *Tasmota) Enabled() (bool, error) {
var tStatus tasmota.StatusResponse
// Execute Tasmota Status 0 command
err := c.GetJSON(c.cmdUri("Status 0"), &tStatus)
return int(1) == tStatus.Status.Power, err
}
// Enable implements the Charger.Enable interface
func (c *Tasmota) Enable(enable bool) error {
var tPower tasmota.PowerResponse
cmd := "Power off"
if enable {
cmd = "Power on"
}
// Execute Tasmota Power on/off command
err := c.GetJSON(c.cmdUri(cmd), &tPower)
switch {
case err != nil:
return err
case enable && tPower.POWER != "ON":
return errors.New("switchOn failed")
case !enable && tPower.POWER != "OFF":
return errors.New("switchOff failed")
default:
return nil
}
}
// MaxCurrent implements the Charger.MaxCurrent interface
func (c *Tasmota) MaxCurrent(current int64) error {
return nil
}
// Status implements the Charger.Status interface
func (c *Tasmota) Status() (api.ChargeStatus, error) {
power, err := c.CurrentPower()
switch {
case power > 0:
return api.StatusC, err
default:
return api.StatusB, err
}
}
var _ api.Meter = (*Tasmota)(nil)
// CurrentPower implements the api.Meter interface
func (c *Tasmota) CurrentPower() (float64, error) {
var tStatusSNS tasmota.StatusSNSResponse
// Execute Tasmota Status 8 command
err := c.GetJSON(c.cmdUri("Status 8"), &tStatusSNS)
if err != nil {
return math.NaN(), err
}
power := float64(tStatusSNS.StatusSNS.Energy.Power)
// ignore standby power
if power < c.standbypower {
power = 0
}
return power, err
}
// cmdUri creates the Tasmota command web request
// https://tasmota.github.io/docs/Commands/#with-web-requests
func (c *Tasmota) cmdUri(cmd string) string {
parameters := url.Values{
"user": []string{c.user},
"password": []string{c.password},
"cmnd": []string{cmd},
}
return fmt.Sprintf("%s/cm?%s", c.uri, parameters.Encode())
}

View file

@ -0,0 +1,54 @@
package tasmota
// StatusResponse is a part of the Tasmota Status 0 command Status response
// https://tasmota.github.io/docs/JSON-Status-Responses/
type StatusResponse struct {
Status struct {
Module int
DeviceName string
FriendlyName []string
Topic string
ButtonTopic string
Power int
PowerOnState int
LedState int
LedMask string
SaveData int
SaveState int
SwitchTopic string
SwitchMode []int
ButtonRetain int
SwitchRetain int
SensorRetain int
PowerRetain int
InfoRetain int
StateRetain int
}
}
// StatusSNSResponse is the Tasmota Status 8 command Status response
// https://tasmota.github.io/docs/JSON-Status-Responses/
type StatusSNSResponse struct {
StatusSNS struct {
Time string
Energy struct {
TotalStartTime string
Total float64
Yesterday float64
Today float64
Power int
ApparentPower int
ReactivePower int
Factor float64
Voltage int
Current float64
}
}
}
// PowerResponse is the Tasmota Power command Status response
// ON, OFF, Error
// https://tasmota.github.io/docs/Commands/#with-web-requests
type PowerResponse struct {
POWER string
}