From 8db09e8160647dfb75a8a18510fa4cf6067e334a Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 3 Jan 2021 14:18:57 +0100 Subject: [PATCH] Add status and climatisation apis for ID (#569) --- vehicle/id/api.go | 50 ++++++++++++++++++++++++++++++++++-------- vehicle/id/provider.go | 42 +++++++++++++++++++++++++++++++++++ vehicle/vw/provider.go | 34 ++++++++++++++-------------- 3 files changed, 100 insertions(+), 26 deletions(-) diff --git a/vehicle/id/api.go b/vehicle/id/api.go index 0c291c4dd..3c79a6e8c 100644 --- a/vehicle/id/api.go +++ b/vehicle/id/api.go @@ -3,12 +3,16 @@ package id import ( "fmt" "net/http" + "strings" "github.com/andig/evcc/util" "github.com/andig/evcc/util/request" "github.com/andig/evcc/vehicle/vw" ) +// https://identity-userinfo.vwgroup.io/oidc/userinfo +// https://customer-profile.apps.emea.vwapps.io/v1/customers//realCarData + // API is an api.Vehicle implementation for VW ID cars type API struct { *request.Helper @@ -35,7 +39,8 @@ func (v *API) Vehicles() (res []string, err error) { var vehicles struct { Data []struct { - VIN string + VIN string + Nickname string } } @@ -55,10 +60,11 @@ type Status struct { Data struct { BatteryStatus BatteryStatus ChargingStatus ChargingStatus + ChargingSettings ChargingSettings PlugStatus PlugStatus RangeStatus RangeStatus ClimatisationSettings ClimatisationSettings - // ClimatisationStatus ClimatisationStatus // currently not available + ClimatisationStatus ClimatisationStatus // may be currently not available } } @@ -72,16 +78,16 @@ type BatteryStatus struct { // ChargingStatus is the /status.chargingStatus api type ChargingStatus struct { CarCapturedTimestamp string - ChargingState string - RemainingChargingTimeToCompleteMin int `json:"remainingChargingTimeToComplete_min"` - ChargePowerKW int `json:"chargePower_kW"` - ChargeRateKmph int `json:"chargeRate_kmph"` + ChargingState string // readyForCharging + RemainingChargingTimeToCompleteMin int `json:"remainingChargingTimeToComplete_min"` + ChargePowerKW int `json:"chargePower_kW"` + ChargeRateKmph int `json:"chargeRate_kmph"` } // ChargingSettings is the /status.chargingSettings api type ChargingSettings struct { CarCapturedTimestamp string - MaxChargeCurrentAC string + MaxChargeCurrentAC string // reduced, maximum AutoUnlockPlugWhenCharged string TargetSOCPercent int `json:"targetSOC_pct"` } @@ -89,17 +95,24 @@ type ChargingSettings struct { // PlugStatus is the /status.plugStatus api type PlugStatus struct { CarCapturedTimestamp string - PlugConnectionState string + PlugConnectionState string // connected, disconnected PlugLockState string } +// ClimatisationStatus is the /status.climatisationStatus api +type ClimatisationStatus struct { + CarCapturedTimestamp string + RemainingClimatisationTimeMin int `json:"remainingClimatisationTime_min"` + ClimatisationState string // off +} + // ClimatisationSettings is the /status.climatisationSettings api type ClimatisationSettings struct { CarCapturedTimestamp string TargetTemperatureK float64 `json:"targetTemperature_K"` TargetTemperatureC float64 `json:"targetTemperature_C"` ClimatisationWithoutExternalPower bool - ClimatizationAtUnlock bool + ClimatisationAtUnlock bool // ClimatizationAtUnlock? WindowHeatingEnabled bool ZoneFrontLeftEnabled bool ZoneFrontRightEnabled bool @@ -134,3 +147,22 @@ func (v *API) Status(vin string) (res Status, err error) { return res, err } + +// Any implements any api response +func (v *API) Any(uri, vin string) (interface{}, error) { + if strings.Contains(uri, "%s") { + uri = fmt.Sprintf(uri, vin) + } + + req, err := request.New(http.MethodGet, uri, nil, map[string]string{ + "Accept": "application/json", + "Authorization": "Bearer " + v.identity.Token(), + }) + + var res interface{} + if err == nil { + err = v.DoJSON(req, &res) + } + + return res, err +} diff --git a/vehicle/id/provider.go b/vehicle/id/provider.go index fcfb7dcdd..d8242ffc9 100644 --- a/vehicle/id/provider.go +++ b/vehicle/id/provider.go @@ -1,8 +1,10 @@ package id import ( + "strings" "time" + "github.com/andig/evcc/api" "github.com/andig/evcc/provider" ) @@ -31,6 +33,23 @@ func (v *Provider) ChargeState() (float64, error) { return 0, err } +// Status implements the Vehicle.Status interface +func (v *Provider) Status() (api.ChargeStatus, error) { + status := api.StatusA // disconnected + + res, err := v.statusG() + if res, ok := res.(Status); err == nil && ok { + if res.Data.PlugStatus.PlugConnectionState == "connected" { + status = api.StatusB + } + if res.Data.ChargingStatus.ChargingState == "charging" { + status = api.StatusC + } + } + + return status, err +} + // FinishTime implements the Vehicle.ChargeFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.statusG() @@ -52,3 +71,26 @@ func (v *Provider) Range() (int64, error) { return 0, err } + +// Climater implements the Vehicle.Climater interface +func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) { + res, err := v.statusG() + if res, ok := res.(Status); err == nil && ok { + state := strings.ToLower(res.Data.ClimatisationStatus.ClimatisationState) + + if state == "" { + return false, 0, 0, api.ErrNotAvailable + } + + active := state != "off" && state != "invalid" && state != "error" + + targetTemp = res.Data.ClimatisationSettings.TargetTemperatureC + + // TODO: not available; use target temp to avoid wrong heating/cooling display + outsideTemp = targetTemp + + return active, outsideTemp, targetTemp, nil + } + + return active, outsideTemp, targetTemp, err +} diff --git a/vehicle/vw/provider.go b/vehicle/vw/provider.go index cd78ffd39..817b123bb 100644 --- a/vehicle/vw/provider.go +++ b/vehicle/vw/provider.go @@ -37,6 +37,23 @@ func (v *Provider) ChargeState() (float64, error) { return 0, err } +// Status implements the Vehicle.Status interface +func (v *Provider) Status() (api.ChargeStatus, error) { + status := api.StatusA // disconnected + + res, err := v.chargerG() + if res, ok := res.(ChargerResponse); err == nil && ok { + if res.Charger.Status.PlugStatusData.PlugState.Content == "connected" { + status = api.StatusB + } + if res.Charger.Status.ChargingStatusData.ChargingState.Content == "charging" { + status = api.StatusC + } + } + + return status, err +} + // FinishTime implements the Vehicle.ChargeFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { res, err := v.chargerG() @@ -57,23 +74,6 @@ func (v *Provider) FinishTime() (time.Time, error) { return time.Time{}, err } -// Status implements the Vehicle.Status interface -func (v *Provider) Status() (api.ChargeStatus, error) { - status := api.StatusA // disconnected - - res, err := v.chargerG() - if res, ok := res.(ChargerResponse); err == nil && ok { - if res.Charger.Status.PlugStatusData.PlugState.Content == "connected" { - status = api.StatusB - } - if res.Charger.Status.ChargingStatusData.ChargingState.Content == "charging" { - status = api.StatusC - } - } - - return status, err -} - // Range implements the Vehicle.Range interface func (v *Provider) Range() (rng int64, err error) { res, err := v.chargerG()