Add Tasmota pseudo charger (#829)
This commit is contained in:
parent
6df4d777b9
commit
711ec8357d
3 changed files with 208 additions and 1 deletions
|
|
@ -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
152
internal/charger/tasmota.go
Normal 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())
|
||||
}
|
||||
54
internal/charger/tasmota/types.go
Normal file
54
internal/charger/tasmota/types.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue