evcc-io/vehicle/vw/api.go
2021-10-22 17:43:08 +02:00

171 lines
4.2 KiB
Go

package vw
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"golang.org/x/oauth2"
)
// DefaultBaseURI is the VW api base URI
const DefaultBaseURI = "https://msg.volkswagen.de/fs-car"
// RegionAPI is the VW api used for determining the home region
const RegionAPI = "https://mal-1a.prd.ece.vwg-connect.com/api"
// API is the VW api client
type API struct {
*request.Helper
brand, country string
baseURI string
}
// NewAPI creates a new api client
func NewAPI(log *util.Logger, identity oauth2.TokenSource, brand, country string) *API {
v := &API{
Helper: request.NewHelper(log),
brand: brand,
country: country,
baseURI: DefaultBaseURI,
}
v.Client.Transport = &oauth2.Transport{
Source: identity,
Base: v.Client.Transport,
}
return v
}
func (v *API) getJSON(uri string, res interface{}) error {
req, err := request.New(http.MethodGet, uri, nil, request.AcceptJSON)
if err == nil {
err = v.DoJSON(req, &res)
}
return err
}
// VehiclesResponse is the /usermanagement/users/v1/%s/%s/vehicles api
type VehiclesResponse struct {
UserVehicles struct {
Vehicle []string
}
}
// Vehicles implements the /vehicles response
func (v *API) Vehicles() ([]string, error) {
var res VehiclesResponse
uri := fmt.Sprintf("%s/usermanagement/users/v1/%s/%s/vehicles", v.baseURI, v.brand, v.country)
err := v.getJSON(uri, &res)
return res.UserVehicles.Vehicle, err
}
// HomeRegion is the home region API response
type HomeRegion struct {
HomeRegion struct {
BaseURI struct {
SystemID string
Content string // api url
}
}
}
// HomeRegion updates the home region for the given vehicle
func (v *API) HomeRegion(vin string) error {
var res HomeRegion
uri := fmt.Sprintf("%s/cs/vds/v1/vehicles/%s/homeRegion", RegionAPI, vin)
err := v.getJSON(uri, &res)
if err == nil {
if api := res.HomeRegion.BaseURI.Content; strings.HasPrefix(api, "https://mal-3a.prd.eu.dp.vwg-connect.com") {
api = "https://fal" + strings.TrimPrefix(api, "https://mal")
api = strings.TrimSuffix(api, "/api") + "/fs-car"
v.baseURI = api
}
}
return err
}
// RolesRights updates the home region for the given vehicle
func (v *API) RolesRights(vin string) (string, error) {
var res json.RawMessage
uri := fmt.Sprintf("%s/rolesrights/operationlist/v3/vehicles/%s", RegionAPI, vin)
err := v.getJSON(uri, &res)
return string(res), err
}
// Charger implements the /charger response
func (v *API) Charger(vin string) (ChargerResponse, error) {
var res ChargerResponse
uri := fmt.Sprintf("%s/bs/batterycharge/v1/%s/%s/vehicles/%s/charger", v.baseURI, v.brand, v.country, vin)
err := v.getJSON(uri, &res)
if err != nil && res.Error != nil {
err = res.Error.Error()
}
return res, err
}
// Climater implements the /climater response
func (v *API) Climater(vin string) (ClimaterResponse, error) {
var res ClimaterResponse
uri := fmt.Sprintf("%s/bs/climatisation/v1/%s/%s/vehicles/%s/climater", v.baseURI, v.brand, v.country, vin)
err := v.getJSON(uri, &res)
if err != nil && res.Error != nil {
err = res.Error.Error()
}
return res, err
}
const (
ActionCharge = "batterycharge"
ActionChargeStart = "start"
ActionChargeStop = "stop"
)
type actionDefinition struct {
contentType string
appendix string
}
var actionDefinitions = map[string]actionDefinition{
ActionCharge: {
"application/vnd.vwg.mbb.ChargerAction_v1_0_0+xml",
"charger/actions",
},
}
// Action implements vehicle actions
func (v *API) Action(vin, action, value string) error {
def := actionDefinitions[action]
uri := fmt.Sprintf("%s/bs/%s/v1/%s/%s/vehicles/%s/%s", v.baseURI, action, v.brand, v.country, vin, def.appendix)
body := "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><action><type>" + value + "</type></action>"
req, err := request.New(http.MethodPost, uri, strings.NewReader(body), map[string]string{
"Content-type": def.contentType,
})
if err == nil {
var resp *http.Response
if resp, err = v.Do(req); err == nil {
resp.Body.Close()
}
}
return err
}
// Any implements any api response
func (v *API) Any(base, vin string) (interface{}, error) {
var res interface{}
uri := fmt.Sprintf("%s/"+strings.TrimLeft(base, "/"), v.baseURI, v.brand, v.country, vin)
err := v.getJSON(uri, &res)
return res, err
}