Skip to content

Commit

Permalink
move myskoda to separate package
Browse files Browse the repository at this point in the history
  • Loading branch information
GrimmiMeloni committed Jun 13, 2024
1 parent 9c598e1 commit 9c47207
Show file tree
Hide file tree
Showing 7 changed files with 496 additions and 14 deletions.
22 changes: 8 additions & 14 deletions vehicle/skoda-enyaq.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/vehicle/skoda"
"github.com/evcc-io/evcc/vehicle/skoda/connect"
"github.com/evcc-io/evcc/vehicle/vag/service"
"github.com/evcc-io/evcc/vehicle/skoda/myskoda"
"github.com/evcc-io/evcc/vehicle/skoda/myskoda/service"
)

// https://github.com/lendy007/skodaconnect

// Enyaq is an api.Vehicle implementation for Skoda Enyaq cars
type Enyaq struct {
*embed
*skoda.Provider // provides the api implementations
*myskoda.Provider // provides the api implementations
}

func init() {
Expand Down Expand Up @@ -51,17 +50,17 @@ func NewEnyaqFromConfig(other map[string]interface{}) (api.Vehicle, error) {
log := util.NewLogger("enyaq").Redact(cc.User, cc.Password, cc.VIN)

// use Skoda api to resolve list of vehicles
ts, err := service.TokenRefreshServiceTokenSource(log, skoda.TRSParams, skoda.AuthParams, cc.User, cc.Password)
ts, err := service.TokenRefreshServiceTokenSource(log, myskoda.TRSParams, myskoda.AuthParams, cc.User, cc.Password)
if err != nil {
return nil, err
}

api := skoda.NewAPI(log, ts)
api := myskoda.NewAPI(log, ts)
api.Client.Timeout = cc.Timeout

vehicle, err := ensureVehicleEx(
cc.VIN, api.Vehicles,
func(v skoda.Vehicle) (string, error) {
func(v myskoda.Vehicle) (string, error) {
return v.VIN, nil
},
)
Expand All @@ -72,15 +71,10 @@ func NewEnyaqFromConfig(other map[string]interface{}) (api.Vehicle, error) {

// use Connect credentials to build provider
if err == nil {
ts, err := service.TokenRefreshServiceTokenSource(log, skoda.TRSParams, connect.AuthParams, cc.User, cc.Password)
if err != nil {
return nil, err
}

api := skoda.NewAPI(log, ts)
api := myskoda.NewAPI(log, ts)
api.Client.Timeout = cc.Timeout

v.Provider = skoda.NewProvider(api, vehicle.VIN, cc.Cache)
v.Provider = myskoda.NewProvider(api, vehicle.VIN, cc.Cache)
}

return v, err
Expand Down
113 changes: 113 additions & 0 deletions vehicle/skoda/myskoda/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package myskoda

import (
"fmt"
"net/http"

"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"golang.org/x/oauth2"
)

const BaseURI = "https://mysmob.api.connect.skoda-auto.cz/api"

// API is the Skoda api client
type API struct {
*request.Helper
}

// NewAPI creates a new api client
func NewAPI(log *util.Logger, ts oauth2.TokenSource) *API {
v := &API{
Helper: request.NewHelper(log),
}

v.Client.Transport = &oauth2.Transport{
Source: ts,
Base: v.Client.Transport,
}

return v
}

// Vehicles implements the /v2/garage response
func (v *API) Vehicles() ([]Vehicle, error) {
var res VehiclesResponse

uri := fmt.Sprintf("%s/v2/garage", BaseURI)
err := v.GetJSON(uri, &res)

return res.Vehicles, err
}

func (v *API) VehicleDetails(vin string) (Vehicle, error) {
var res Vehicle
uri := fmt.Sprintf("%s/v2/garage/vehicles/%s", BaseURI, vin)
err := v.GetJSON(uri, &res)
return res, err
}

// Status implements the /v2/vehicle-status/<vin> response
func (v *API) Status(vin string) (StatusResponse, error) {
var res StatusResponse
uri := fmt.Sprintf("%s/v1/vehicle-health-report/warning-lights/%s", BaseURI, vin)
err := v.GetJSON(uri, &res)
return res, err
}

// Charger implements the /v1/charging/<vin> response
func (v *API) Charger(vin string) (ChargerResponse, error) {
var res ChargerResponse
uri := fmt.Sprintf("%s/v1/charging/%s", BaseURI, vin)
err := v.GetJSON(uri, &res)
return res, err
}

// Settings implements the /v1/charging/<vin>/settings response
func (v *API) Settings(vin string) (SettingsResponse, error) {
var res SettingsResponse

chrgRes, err := v.Charger(vin)
if err == nil {
res = chrgRes.Settings
}
return res, err
}

// Climater implements the /v2/air-conditioning/<vin> response
func (v *API) Climater(vin string) (ClimaterResponse, error) {
var res ClimaterResponse
uri := fmt.Sprintf("%s/v2/air-conditioning/%s", BaseURI, vin)
err := v.GetJSON(uri, &res)
return res, err
}

const (
ActionCharge = "charging"
ActionChargeStart = "start"
ActionChargeStop = "stop"
)

// Action executes a vehicle action
func (v *API) Action(vin, action, value string) error {
// @POST("api/v1/charging/{vin}/start")
// @POST("api/v1/charging/{vin}/stop")
uri := fmt.Sprintf("%s/v1/%s/%s/%s", BaseURI, action, vin, value)

req, err := request.New(http.MethodPost, uri, nil, request.JSONEncoding)
if err == nil {
err = v.DoJSON(req, nil)
}
return err
}

func (v *API) WakeUp(vin string) error {
// @POST("api/v1/vehicle-wakeup/{vin}")
uri := fmt.Sprintf("%s/v1/vehicle-wakeup/%s", BaseURI, vin)

req, err := request.New(http.MethodPost, uri, nil, request.JSONEncoding)
if err == nil {
err = v.DoJSON(req, nil)
}
return err
}
24 changes: 24 additions & 0 deletions vehicle/skoda/myskoda/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package myskoda

import "net/url"

const (
Brand = "VW"
Country = "CZ"

// Authorization ClientID
AuthClientID = "afb0473b-6d82-42b8-bfea-cead338c46ef"
)

// Skoda native api
var AuthParams = url.Values{
"response_type": {"code id_token"},
"client_id": {"7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com"},
"redirect_uri": {"myskoda://redirect/login/"},
"scope": {"address badge birthdate cars driversLicense dealers email mileage mbb nationalIdentifier openid phone profession profile vin"},
}

// TokenRefreshService parameters
var TRSParams = url.Values{
"brand": {"skoda"},
}
162 changes: 162 additions & 0 deletions vehicle/skoda/myskoda/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package myskoda

import (
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider"
)

// Provider implements the vehicle api
type Provider struct {
statusG func() (StatusResponse, error)
chargerG func() (ChargerResponse, error)
settingsG func() (SettingsResponse, error)
climateG func() (ClimaterResponse, error)
action func(action, value string) error
wakeup func() error
}

// NewProvider creates a vehicle api provider
func NewProvider(api *API, vin string, cache time.Duration) *Provider {
impl := &Provider{
statusG: provider.Cached(func() (StatusResponse, error) {
return api.Status(vin)
}, cache),
chargerG: provider.Cached(func() (ChargerResponse, error) {
return api.Charger(vin)
}, cache),
climateG: provider.Cached(func() (ClimaterResponse, error) {
return api.Climater(vin)
}, cache),
settingsG: provider.Cached(func() (SettingsResponse, error) {
return api.Settings(vin)
}, cache),
action: func(action, value string) error {
return api.Action(vin, action, value)
},
wakeup: func() error {
return api.WakeUp(vin)
},
}
return impl
}

var _ api.Battery = (*Provider)(nil)

// Soc implements the api.Vehicle interface
func (v *Provider) Soc() (float64, error) {
res, err := v.chargerG()
if err == nil {
return float64(res.Status.Battery.StateOfChargeInPercent), nil
}

return 0, err
}

var _ api.ChargeState = (*Provider)(nil)

// Status implements the api.ChargeState interface
func (v *Provider) Status() (api.ChargeStatus, error) {
status := api.StatusA // disconnected

res, err := v.climateG()
if err == nil {
if res.ChargerConnectionState == "CONNECTED" {
status = api.StatusB
}
}

resChrg, err := v.chargerG()
if err == nil {
if resChrg.Status.State == "CHARGING" {
status = api.StatusC
}
}

return status, err
}

var _ api.VehicleFinishTimer = (*Provider)(nil)

// FinishTime implements the api.VehicleFinishTimer interface
func (v *Provider) FinishTime() (time.Time, error) {
res, err := v.chargerG()
if err == nil {
crg := res.Status

// estimate not available
if crg.State == "Error" || crg.ChargeType == "Invalid" {
return time.Time{}, api.ErrNotAvailable
}

remaining := time.Duration(crg.RemainingTimeToFullyChargedInMinutes) * time.Minute
return time.Now().Add(remaining), err
}

return time.Time{}, err
}

var _ api.VehicleRange = (*Provider)(nil)

// Range implements the api.VehicleRange interface
func (v *Provider) Range() (rng int64, err error) {
res, err := v.chargerG()
return res.Status.Battery.RemainingCruisingRangeInMeters / 1e3, err
}

var _ api.VehicleOdometer = (*Provider)(nil)

// Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (odo float64, err error) {
res, err := v.statusG()
return res.MileageInKm, err
}

// var _ api.VehicleClimater = (*Provider)(nil)

// // Climater implements the api.VehicleClimater interface
// func (v *Provider) Climater() (active bool, outsideTemp float64, targetTemp float64, err error) {
// res, err := v.climateG()
// err == nil {
// state := strings.ToLower(res.Climater.Status.ClimatisationStatusData.ClimatisationState.Content)
// active := state != "off" && state != "invalid" && state != "error"

// targetTemp = res.Climater.Settings.TargetTemperature.Content
// outsideTemp = res.Climater.Status.TemperatureStatusData.OutdoorTemperature.Content
// if math.IsNaN(outsideTemp) {
// outsideTemp = targetTemp // cover "invalid"
// }

// return active, outsideTemp, targetTemp, nil
// }

// return active, outsideTemp, targetTemp, err
// }

var _ api.SocLimiter = (*Provider)(nil)

// GetLimitSoc implements the api.SocLimiter interface
func (v *Provider) GetLimitSoc() (int64, error) {
res, err := v.chargerG()
if err == nil {
return int64(res.Settings.TargetStateOfChargeInPercent), nil
}

return 0, err
}

var _ api.ChargeController = (*Provider)(nil)

// ChargeEnable implements the api.ChargeController interface
func (v *Provider) ChargeEnable(enable bool) error {
action := map[bool]string{true: ActionChargeStart, false: ActionChargeStop}
return v.action(ActionCharge, action[enable])
}

var _ api.Resurrector = (*Provider)(nil)

// WakeUp implements the api.Resurrector interface
func (v *Provider) WakeUp() error {
return v.wakeup()
}
25 changes: 25 additions & 0 deletions vehicle/skoda/myskoda/service/tokenrefreshservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package service

import (
"net/url"

"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/vehicle/skoda/myskoda/tokenrefreshservice"
"github.com/evcc-io/evcc/vehicle/vag"
"github.com/evcc-io/evcc/vehicle/vag/vwidentity"
)

func TokenRefreshServiceTokenSource(log *util.Logger, data, q url.Values, user, password string) (vag.TokenSource, error) {
q, err := vwidentity.Login(log, q, user, password)
if err != nil {
return nil, err
}

trs := tokenrefreshservice.New(log, data)
token, err := trs.Exchange(q)
if err != nil {
return nil, err
}

return trs.TokenSource(token), nil
}
Loading

0 comments on commit 9c47207

Please sign in to comment.