122 lines
2.9 KiB
Go
122 lines
2.9 KiB
Go
package meter
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"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(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{
|
|
HTTPHelper: util.NewHTTPHelper(util.NewLogger("tesla")),
|
|
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}, nil
|
|
}
|
|
|
|
return m, 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)
|
|
}
|
|
|
|
// 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)
|
|
}
|