Carwings: Reduce number of server round-trips (#1157)
This commit is contained in:
parent
5eb5a08681
commit
ece30ae030
4 changed files with 95 additions and 60 deletions
2
go.mod
2
go.mod
|
|
@ -40,7 +40,7 @@ require (
|
|||
github.com/itchyny/gojq v0.12.4
|
||||
github.com/jeremywohl/flatten v1.0.1
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/joeshaw/carwings v0.0.0-20210208214325-dacfdd3d7acc
|
||||
github.com/joeshaw/carwings v0.0.0-20210616193757-7fbc6f7c3c73
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.13.1 // indirect
|
||||
github.com/koron/go-ssdp v0.0.2
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -383,8 +383,8 @@ github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO
|
|||
github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w=
|
||||
github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/joeshaw/carwings v0.0.0-20191118152321-61b46581307a/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
|
||||
github.com/joeshaw/carwings v0.0.0-20210208214325-dacfdd3d7acc h1:lvs05O5riVMhA70SOK53fthEZ5lMDHHy51ZKDldr0aE=
|
||||
github.com/joeshaw/carwings v0.0.0-20210208214325-dacfdd3d7acc/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
|
||||
github.com/joeshaw/carwings v0.0.0-20210616193757-7fbc6f7c3c73 h1:0GC7kKnW8vFHZB1txMdeiaVx1X65V9x8sWi5xoxUCfc=
|
||||
github.com/joeshaw/carwings v0.0.0-20210616193757-7fbc6f7c3c73/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/jsgoecke/tesla v0.0.0-20200530171421-e02ebd220e5a/go.mod h1:GlLJU/C6KA3FEAIZTFDvIDlNGE1+YlKGHHXdNVkt5ow=
|
||||
|
|
|
|||
|
|
@ -2,11 +2,15 @@ package vehicle
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
|
||||
"github.com/joeshaw/carwings"
|
||||
)
|
||||
|
|
@ -19,10 +23,11 @@ const (
|
|||
// CarWings is an api.Vehicle implementation for CarWings cars
|
||||
type CarWings struct {
|
||||
*embed
|
||||
log *util.Logger
|
||||
wg sync.WaitGroup
|
||||
user, password string
|
||||
session *carwings.Session
|
||||
statusG func() (interface{}, error)
|
||||
climateG func() (interface{}, error)
|
||||
refreshKey string
|
||||
refreshTime time.Time
|
||||
}
|
||||
|
|
@ -34,9 +39,9 @@ func init() {
|
|||
// NewCarWingsFromConfig creates a new vehicle
|
||||
func NewCarWingsFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
User, Password, Region string
|
||||
Cache time.Duration
|
||||
embed `mapstructure:",squash"`
|
||||
User, Password, Region, VIN string
|
||||
Cache time.Duration
|
||||
}{
|
||||
Region: carwings.RegionEurope,
|
||||
Cache: interval,
|
||||
|
|
@ -50,18 +55,54 @@ func NewCarWingsFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
return nil, errors.New("missing credentials")
|
||||
}
|
||||
|
||||
log := util.NewLogger("carwin")
|
||||
// http client with high dial/handshake timeout
|
||||
const timeout = 90 * time.Second
|
||||
log := util.NewLogger("carwings")
|
||||
|
||||
transport := request.NewTripper(log, &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment, // default
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: timeout,
|
||||
KeepAlive: 30 * time.Second, // default
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: timeout,
|
||||
ForceAttemptHTTP2: true, // default
|
||||
MaxIdleConns: 100, // default
|
||||
IdleConnTimeout: 90 * time.Second, // default
|
||||
ExpectContinueTimeout: 1 * time.Second, // default
|
||||
})
|
||||
|
||||
carwings.Client = &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
v := &CarWings{
|
||||
embed: &cc.embed,
|
||||
log: log,
|
||||
user: cc.User,
|
||||
password: cc.Password,
|
||||
session: &carwings.Session{Region: cc.Region},
|
||||
session: &carwings.Session{
|
||||
Region: cc.Region,
|
||||
VIN: cc.VIN,
|
||||
},
|
||||
}
|
||||
|
||||
// initial connect
|
||||
v.wg.Add(1)
|
||||
go func() {
|
||||
if err := v.session.Connect(v.user, v.password); err != nil {
|
||||
log.ERROR.Println("login failed:", err)
|
||||
}
|
||||
v.wg.Done()
|
||||
}()
|
||||
|
||||
v.statusG = provider.NewCached(func() (interface{}, error) {
|
||||
return nil, v.status()
|
||||
return v.status()
|
||||
}, cc.Cache).InterfaceGetter()
|
||||
|
||||
v.climateG = provider.NewCached(func() (interface{}, error) {
|
||||
v.wg.Wait() // initial connect
|
||||
return v.session.ClimateControlStatus()
|
||||
}, cc.Cache).InterfaceGetter()
|
||||
|
||||
return v, nil
|
||||
|
|
@ -70,6 +111,7 @@ func NewCarWingsFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
// connectIfRequired will return ErrMustRetry if ErrNotLoggedIn error could be resolved
|
||||
func (v *CarWings) connectIfRequired(err error) error {
|
||||
if err == carwings.ErrNotLoggedIn || err.Error() == "received status code 404" {
|
||||
v.wg.Wait() // initial connect
|
||||
if err = v.session.Connect(v.user, v.password); err == nil {
|
||||
err = api.ErrMustRetry
|
||||
}
|
||||
|
|
@ -77,17 +119,22 @@ func (v *CarWings) connectIfRequired(err error) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (v *CarWings) status() error {
|
||||
func (v *CarWings) status() (interface{}, error) {
|
||||
v.wg.Wait() // initial connect
|
||||
|
||||
// api result is stale
|
||||
if v.refreshKey != "" {
|
||||
if err := v.refreshResult(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bs, err := v.session.BatteryStatus()
|
||||
|
||||
if err == nil {
|
||||
if elapsed := time.Since(bs.Timestamp); elapsed > carwingsStatusExpiry {
|
||||
// api result is stale
|
||||
if v.refreshKey != "" {
|
||||
return v.refreshResult()
|
||||
}
|
||||
|
||||
if err = v.refreshRequest(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.ErrMustRetry
|
||||
|
|
@ -100,7 +147,7 @@ func (v *CarWings) status() error {
|
|||
err = v.connectIfRequired(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return bs, err
|
||||
}
|
||||
|
||||
// refreshResult triggers an update if not already in progress, otherwise gets result
|
||||
|
|
@ -142,34 +189,28 @@ func (v *CarWings) refreshRequest() (err error) {
|
|||
}
|
||||
|
||||
// SoC implements the api.Vehicle interface
|
||||
func (v *CarWings) SoC() (soc float64, err error) {
|
||||
soc = 0
|
||||
|
||||
if _, err = v.statusG(); err == nil {
|
||||
var bs carwings.BatteryStatus
|
||||
if bs, err = v.session.BatteryStatus(); err == nil {
|
||||
soc = float64(bs.StateOfCharge)
|
||||
}
|
||||
func (v *CarWings) SoC() (float64, error) {
|
||||
res, err := v.statusG()
|
||||
if res, ok := res.(carwings.BatteryStatus); err == nil && ok {
|
||||
return float64(res.StateOfCharge), nil
|
||||
}
|
||||
|
||||
return soc, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.ChargeState = (*CarWings)(nil)
|
||||
|
||||
// Status implements the api.ChargeState interface
|
||||
func (v *CarWings) Status() (status api.ChargeStatus, err error) {
|
||||
status = api.StatusA // disconnected
|
||||
func (v *CarWings) Status() (api.ChargeStatus, error) {
|
||||
status := api.StatusA // disconnected
|
||||
|
||||
if _, err = v.statusG(); err == nil {
|
||||
var bs carwings.BatteryStatus
|
||||
if bs, err = v.session.BatteryStatus(); err == nil {
|
||||
if bs.PluginState == carwings.Connected {
|
||||
status = api.StatusB // connected, not charging
|
||||
}
|
||||
if bs.ChargingStatus == carwings.NormalCharging {
|
||||
status = api.StatusC // charging
|
||||
}
|
||||
res, err := v.statusG()
|
||||
if res, ok := res.(carwings.BatteryStatus); err == nil && ok {
|
||||
if res.PluginState == carwings.Connected {
|
||||
status = api.StatusB // connected, not charging
|
||||
}
|
||||
if res.ChargingStatus == carwings.NormalCharging {
|
||||
status = api.StatusC // charging
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,30 +220,25 @@ func (v *CarWings) Status() (status api.ChargeStatus, err error) {
|
|||
var _ api.VehicleRange = (*CarWings)(nil)
|
||||
|
||||
// Range implements the api.VehicleRange interface
|
||||
func (v *CarWings) Range() (rng int64, err error) {
|
||||
rng = 0
|
||||
|
||||
if _, err = v.statusG(); err == nil {
|
||||
var bs carwings.BatteryStatus
|
||||
if bs, err = v.session.BatteryStatus(); err == nil {
|
||||
rng = int64(bs.CruisingRangeACOn) / 1000
|
||||
}
|
||||
func (v *CarWings) Range() (int64, error) {
|
||||
res, err := v.statusG()
|
||||
if res, ok := res.(carwings.BatteryStatus); err == nil && ok {
|
||||
return int64(res.CruisingRangeACOn) / 1000, nil
|
||||
}
|
||||
|
||||
return rng, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.VehicleClimater = (*CarWings)(nil)
|
||||
|
||||
// Climater implements the api.VehicleClimater interface
|
||||
func (v *CarWings) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) {
|
||||
if _, err = v.statusG(); err == nil {
|
||||
var ccs carwings.ClimateStatus
|
||||
if ccs, err = v.session.ClimateControlStatus(); err == nil {
|
||||
active = ccs.Running
|
||||
targetTemp = float64(ccs.Temperature)
|
||||
outsideTemp = targetTemp
|
||||
}
|
||||
res, err := v.climateG()
|
||||
|
||||
if res, ok := res.(carwings.ClimateStatus); err == nil && ok {
|
||||
active = res.Running
|
||||
targetTemp = float64(res.Temperature)
|
||||
outsideTemp = targetTemp
|
||||
|
||||
return active, outsideTemp, targetTemp, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,12 +32,11 @@ func init() {
|
|||
// NewNissanFromConfig creates a new vehicle
|
||||
func NewNissanFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
User, Password, Region, VIN string
|
||||
Cache time.Duration
|
||||
embed `mapstructure:",squash"`
|
||||
User, Password, VIN string
|
||||
Cache time.Duration
|
||||
}{
|
||||
Region: "de_DE",
|
||||
Cache: interval,
|
||||
Cache: interval,
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue