evcc-io/meter/tesla.go

122 lines
3 KiB
Go

package meter
import (
"crypto/tls"
"errors"
"fmt"
"net/url"
"strings"
"github.com/andig/evcc/api"
"github.com/andig/evcc/util"
"github.com/andig/evcc/util/request"
)
// 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 {
*request.Helper
uri, usage string
}
func init() {
registry.Add("tesla", NewTeslaFromConfig)
}
//go:generate go run ../cmd/tools/decorate.go -p meter -f decorateTesla -b api.Meter -o tesla_decorators -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
// NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config
func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
URI, Usage string
}{}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
if cc.Usage == "" {
return nil, errors.New("missing usage setting")
}
url, err := url.ParseRequestURI(cc.URI)
if err != nil {
return nil, fmt.Errorf("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, error) {
m := &Tesla{
Helper: request.NewHelper(util.NewLogger("tesla")),
uri: uri,
usage: strings.ToLower(usage),
}
// ignore the self signed certificate
m.Helper.Transport(request.NewTransport().WithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
// decorate api.MeterEnergy
var totalEnergy func() (float64, error)
if m.usage == "load" || m.usage == "solar" {
totalEnergy = m.totalEnergy
}
return decorateTesla(m, totalEnergy), nil
}
// 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)
}
// totalEnergy implements the api.MeterEnergy interface
func (m *Tesla) 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)
}