Skip to content

Commit

Permalink
Add Tesla Powerwall (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed May 2, 2020
1 parent eeef3f1 commit 500f6fe
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 2 deletions.
15 changes: 13 additions & 2 deletions README.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions meter/config.go
Expand Up @@ -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)
}
Expand Down
118 changes: 118 additions & 0 deletions meter/tesla.go
@@ -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)
}

0 comments on commit 500f6fe

Please sign in to comment.