172 lines
3.7 KiB
Go
172 lines
3.7 KiB
Go
package vehicle
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/util"
|
|
"github.com/evcc-io/evcc/vehicle/tibber"
|
|
)
|
|
|
|
// Tibber is an api.Vehicle implementation for the Tibber Data API
|
|
type Tibber struct {
|
|
*embed
|
|
*tibber.API
|
|
vin string
|
|
homeID string
|
|
devID string
|
|
dataG func() (tibber.DeviceDetail, error)
|
|
}
|
|
|
|
func init() {
|
|
registry.AddCtx("tibber", NewTibberFromConfig)
|
|
}
|
|
|
|
// NewTibberFromConfig creates a new vehicle
|
|
func NewTibberFromConfig(ctx context.Context, other map[string]any) (api.Vehicle, error) {
|
|
cc := struct {
|
|
embed `mapstructure:",squash"`
|
|
ClientID, ClientSecret, RedirectURI string
|
|
VIN string
|
|
Cache time.Duration
|
|
}{
|
|
Cache: interval,
|
|
}
|
|
|
|
if err := util.DecodeOther(other, &cc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cc.ClientID == "" || cc.ClientSecret == "" {
|
|
return nil, api.ErrMissingCredentials
|
|
}
|
|
|
|
log := util.NewLogger("tibber").Redact(cc.ClientID, cc.ClientSecret)
|
|
|
|
authCtx := util.WithLogger(context.Background(), log)
|
|
ts, err := tibber.NewOAuth(authCtx, cc.ClientID, cc.ClientSecret, cc.RedirectURI, cc.embed.GetTitle())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := &Tibber{
|
|
embed: &cc.embed,
|
|
API: tibber.NewAPI(log, ts),
|
|
vin: strings.ToUpper(cc.VIN),
|
|
}
|
|
|
|
v.dataG = util.Cached(v.status, cc.Cache)
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// resolve discovers the home and device id of the configured vehicle. It is
|
|
// deferred until first use because authorization happens interactively. With
|
|
// only the vehicles scope granted, the devices endpoint returns vehicles only.
|
|
func (v *Tibber) resolve() error {
|
|
homes, err := v.Homes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, home := range homes {
|
|
devices, err := v.Devices(home.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, d := range devices {
|
|
// match the bare vin or the full external id (e.g. tesla:5YJ...)
|
|
if v.vin == "" || strings.EqualFold(d.VIN(), v.vin) || strings.EqualFold(d.ExternalID, v.vin) {
|
|
v.homeID = home.ID
|
|
v.devID = d.ID
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("vehicle not found: %s", v.vin)
|
|
}
|
|
|
|
func (v *Tibber) status() (tibber.DeviceDetail, error) {
|
|
if v.devID == "" {
|
|
if err := v.resolve(); err != nil {
|
|
return tibber.DeviceDetail{}, err
|
|
}
|
|
}
|
|
|
|
return v.Device(v.homeID, v.devID)
|
|
}
|
|
|
|
// Soc implements the api.Vehicle interface
|
|
func (v *Tibber) Soc() (float64, error) {
|
|
res, err := v.dataG()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
soc, ok := res.Soc()
|
|
if !ok {
|
|
return 0, api.ErrNotAvailable
|
|
}
|
|
|
|
return soc, nil
|
|
}
|
|
|
|
var _ api.ChargeState = (*Tibber)(nil)
|
|
|
|
// Status implements the api.ChargeState interface
|
|
func (v *Tibber) Status() (api.ChargeStatus, error) {
|
|
res, err := v.dataG()
|
|
if err != nil {
|
|
return api.StatusNone, err
|
|
}
|
|
|
|
status := api.StatusA // disconnected
|
|
|
|
if plug, ok := res.PlugStatus(); ok && plug == tibber.StatusConnected {
|
|
status = api.StatusB // connected, not charging
|
|
}
|
|
if charging, ok := res.ChargingStatus(); ok && charging == tibber.StatusCharging {
|
|
status = api.StatusC // charging
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
var _ api.SocLimiter = (*Tibber)(nil)
|
|
|
|
// GetLimitSoc implements the api.SocLimiter interface
|
|
func (v *Tibber) GetLimitSoc() (int64, error) {
|
|
res, err := v.dataG()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
soc, ok := res.TargetSoc()
|
|
if !ok {
|
|
return 0, api.ErrNotAvailable
|
|
}
|
|
|
|
return int64(soc), nil
|
|
}
|
|
|
|
var _ api.VehicleRange = (*Tibber)(nil)
|
|
|
|
// Range implements the api.VehicleRange interface
|
|
func (v *Tibber) Range() (int64, error) {
|
|
res, err := v.dataG()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
rng, ok := res.Range()
|
|
if !ok {
|
|
return 0, api.ErrNotAvailable
|
|
}
|
|
|
|
return int64(rng), nil
|
|
}
|