Add Tesla Powerwall (#92)
This commit is contained in:
parent
eeef3f1fe8
commit
500f6fe80e
3 changed files with 133 additions and 2 deletions
15
README.md
15
README.md
|
|
@ -8,7 +8,7 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G
|
|||
|
||||
- simple and clean user interface
|
||||
- multiple [chargers](#charger): Wallbe, Phoenix, go-eCharger, NRGKick, SimpleEVSE, EVSEWifi, KEBA/BMW, openWB, Mobile Charger Connect, any other charger using scripting
|
||||
- multiple [meters](#meter): ModBus (SDM630, MPM3PM, SBC ALE3 and many more), SMA Home Manager and SMA Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx), any Sunspec-compatible inverter or home battery devices (Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC)
|
||||
- multiple [meters](#meter): ModBus (SDM630, MPM3PM, SBC ALE3 and many more), SMA Home Manager and SMA Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx), any Sunspec-compatible inverter or home battery devices (Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC, Tesla PowerWall)
|
||||
- different [vehicles](#vehicle) to show battery status: Audi (eTron), BMW (i3), Tesla, Nissan (Leaf), any other vehicle using scripting
|
||||
- [plugins](#plugins) for integrating with hardware devices and home automation: Modbus (meters and grid inverters), MQTT and shell scripts
|
||||
- status notifications using [Telegram](https://telegram.org) and [PushOver](https://pushover.net)
|
||||
|
|
@ -209,7 +209,7 @@ Meters provide data about power and energy consumption. Available meter implemen
|
|||
energy: Export # optional reading for total energy values, specify for charge meter
|
||||
```
|
||||
|
||||
- `sma`: SMA Home Manager and SMA Energy Meter. Power reading is configured out of the box but can be customizied if necessary. To obtain energy readings define the desired Obis code (Import Energy: "1:1.8.0", Export Energy: "1:2.8.0"):
|
||||
- `sma`: SMA Home Manager and SMA Energy Meter. Power reading is configured out of the box but can be customized if necessary. To obtain energy readings define the desired Obis code (Import Energy: "1:1.8.0", Export Energy: "1:2.8.0"):
|
||||
|
||||
```yaml
|
||||
- name: sma-home-manager
|
||||
|
|
@ -219,6 +219,17 @@ Meters provide data about power and energy consumption. Available meter implemen
|
|||
energy: # leave empty to disable or choose obis 1:1.8.0/1:2.8.0
|
||||
```
|
||||
|
||||
- `tesla`: Tesla PowerWall meter. Use `value` to choose meter (grid meter: `site`, pv: `solar`, battery: `battery`)
|
||||
|
||||
```yaml
|
||||
- name: powerwall
|
||||
type: tesla
|
||||
uri: http://192.168.1.4/api/meters/aggregates
|
||||
meter: site # grid meter: `site`, pv: `solar`, battery: `battery`
|
||||
```
|
||||
|
||||
*Note*: this could also be implemented using a `default` meter with the `http` plugin.
|
||||
|
||||
- `default`: default meter implementation where meter readings- `power` and `energy` are configured using [plugin](#plugins)
|
||||
|
||||
### Vehicle
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ func NewFromConfig(log *util.Logger, typ string, other map[string]interface{}) a
|
|||
c = NewModbusFromConfig(log, other)
|
||||
case "sma":
|
||||
c = NewSMAFromConfig(log, other)
|
||||
case "tesla", "powerwall":
|
||||
c = NewTeslaFromConfig(log, other)
|
||||
default:
|
||||
log.FATAL.Fatalf("invalid meter type '%s'", typ)
|
||||
}
|
||||
|
|
|
|||
118
meter/tesla.go
Normal file
118
meter/tesla.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package meter
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
// credits to https://github.com/vloschiavo/powerwall2
|
||||
|
||||
type teslaResponse map[string]struct {
|
||||
LastCommunicationTime string `json:"last_communication_time"`
|
||||
InstantPower float64 `json:"instant_power"`
|
||||
InstantReactivePower float64 `json:"instant_reactive_power"`
|
||||
InstantApparentPower float64 `json:"instant_apparent_power"`
|
||||
Frequency float64 `json:"frequency"`
|
||||
EnergyExported float64 `json:"energy_exported"`
|
||||
EnergyImported float64 `json:"energy_imported"`
|
||||
InstantAverageVoltage float64 `json:"instant_average_voltage"`
|
||||
InstantTotalCurrent float64 `json:"instant_total_current"`
|
||||
IACurrent float64 `json:"i_a_current"`
|
||||
IBCurrent float64 `json:"i_b_current"`
|
||||
ICCurrent float64 `json:"i_c_current"`
|
||||
}
|
||||
|
||||
// Tesla is the tesla powerwall meter
|
||||
type Tesla struct {
|
||||
*util.HTTPHelper
|
||||
uri, usage string
|
||||
}
|
||||
|
||||
// NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config
|
||||
func NewTeslaFromConfig(log *util.Logger, other map[string]interface{}) api.Meter {
|
||||
cc := struct {
|
||||
URI, Usage string
|
||||
}{}
|
||||
util.DecodeOther(log, other, &cc)
|
||||
|
||||
if cc.Usage == "" {
|
||||
log.FATAL.Fatalf("config: missing usage")
|
||||
}
|
||||
|
||||
url, err := url.ParseRequestURI(cc.URI)
|
||||
if err != nil {
|
||||
log.FATAL.Fatalf("config: invalid uri %s", cc.URI)
|
||||
}
|
||||
|
||||
if url.Path == "" {
|
||||
url.Path = "api/meters/aggregates"
|
||||
cc.URI = url.String()
|
||||
}
|
||||
|
||||
return NewTesla(cc.URI, cc.Usage)
|
||||
}
|
||||
|
||||
// NewTesla creates a Tesla Meter
|
||||
func NewTesla(uri, usage string) api.Meter {
|
||||
m := &Tesla{
|
||||
HTTPHelper: util.NewHTTPHelper(util.NewLogger("tsla")),
|
||||
uri: uri,
|
||||
usage: strings.ToLower(usage),
|
||||
}
|
||||
|
||||
// ignore the self signed certificate
|
||||
customTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
m.HTTPHelper.Client.Transport = customTransport
|
||||
|
||||
// decorate api.MeterEnergy
|
||||
if m.usage == "load" || m.usage == "solar" {
|
||||
return &TeslaEnergy{Tesla: m}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// CurrentPower implements the Meter.CurrentPower interface
|
||||
func (m *Tesla) CurrentPower() (float64, error) {
|
||||
var tr teslaResponse
|
||||
_, err := m.GetJSON(m.uri, &tr)
|
||||
|
||||
if err == nil {
|
||||
if o, ok := tr[m.usage]; ok {
|
||||
return o.InstantPower, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("invalid usage: %s", m.usage)
|
||||
}
|
||||
|
||||
// TeslaEnergy decorates Tesla with api.MeterEnergy interface
|
||||
type TeslaEnergy struct {
|
||||
*Tesla
|
||||
}
|
||||
|
||||
// TotalEnergy implements the api.MeterEnergy interface
|
||||
func (m *TeslaEnergy) TotalEnergy() (float64, error) {
|
||||
var tr teslaResponse
|
||||
_, err := m.GetJSON(m.uri, &tr)
|
||||
|
||||
if err == nil {
|
||||
if o, ok := tr[m.usage]; ok {
|
||||
if m.usage == "load" {
|
||||
return o.EnergyImported, nil
|
||||
}
|
||||
if m.usage == "solar" {
|
||||
return o.EnergyExported, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("invalid usage: %s", m.usage)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue