evcc-io/vehicle/fiat/api.go
2025-05-29 09:49:10 +02:00

196 lines
4.3 KiB
Go

package fiat
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/samber/lo"
)
const (
ApiURI = "https://channels.sdpr-01.fcagcv.com"
ApiKey = "3_mOx_J2dRgjXYCdyhchv3b5lhi54eBcdCTX4BI8MORqmZCoQWhA0mV2PTlptLGUQI"
XApiKey = "qLYupk65UU1tw2Ih1cJhs4izijgRDbir2UFHA3Je"
AuthURI = "https://mfa.fcl-01.fcagcv.com"
XAuthApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"
)
// API is an api.Vehicle implementation for Fiat cars
type API struct {
identity *Identity
*request.Helper
}
func NewAPI(log *util.Logger, identity *Identity) *API {
api := &API{
identity: identity,
Helper: request.NewHelper(log),
}
return api
}
func (v *API) request(method, uri string, body io.ReadSeeker) (*http.Request, error) {
headers := map[string]string{
"Content-Type": "application/json",
"Accept": "application/json",
"X-Clientapp-Version": "1.0",
"ClientrequestId": lo.RandomString(16, lo.LettersCharset),
"X-Api-Key": XApiKey,
"X-Originator-Type": "web",
"locale": "de_de", // only required for pinAuth
}
req, err := request.New(method, uri, body, headers)
if err == nil {
// hack for pinAuth method
if strings.HasPrefix(uri, AuthURI) {
req.Header.Set("X-Api-Key", XAuthApiKey)
}
err = v.identity.Sign(req, body)
}
return req, err
}
func (v *API) Vehicles() ([]string, error) {
var res VehiclesResponse
uri := fmt.Sprintf("%s/v4/accounts/%s/vehicles?stage=ALL", ApiURI, v.identity.UID())
req, err := v.request(http.MethodGet, uri, nil)
if err == nil {
err = v.DoJSON(req, &res)
}
var vehicles []string
if err == nil {
for _, v := range res.Vehicles {
vehicles = append(vehicles, v.VIN)
}
}
return vehicles, err
}
func (v *API) Status(vin string) (StatusResponse, error) {
var res StatusResponse
uri := fmt.Sprintf("%s/v2/accounts/%s/vehicles/%s/status", ApiURI, v.identity.UID(), vin)
req, err := v.request(http.MethodGet, uri, nil)
if err == nil {
err = v.DoJSON(req, &res)
}
return res, err
}
func (v *API) Location(vin string) (LocationResponse, error) {
var res LocationResponse
uri := fmt.Sprintf("%s/v1/accounts/%s/vehicles/%s/location/lastknown", ApiURI, v.identity.UID(), vin)
req, err := v.request(http.MethodGet, uri, nil)
if err == nil {
err = v.DoJSON(req, &res)
}
return res, err
}
func (v *API) pinAuth(pin string) (string, error) {
var res PinAuthResponse
uri := fmt.Sprintf("%s/v1/accounts/%s/ignite/pin/authenticate", AuthURI, v.identity.UID())
data := struct {
PIN string `json:"pin"`
}{
PIN: base64.StdEncoding.EncodeToString([]byte(pin)),
}
req, err := v.request(http.MethodPost, uri, request.MarshalJSON(data))
if err == nil {
err = v.DoJSON(req, &res)
}
if err == nil && res.Message != "" {
err = fmt.Errorf("pin auth: %s", res.Message)
}
return res.Token, err
}
func (v *API) Action(vin, pin, action, cmd string) (ActionResponse, error) {
var res ActionResponse
token, err := v.pinAuth(pin)
if err != nil {
return res, err
}
uri := fmt.Sprintf("%s/v1/accounts/%s/vehicles/%s/%s", ApiURI, v.identity.UID(), vin, action)
data := struct {
Command string `json:"command"`
PinAuth string `json:"pinAuth"`
}{
Command: cmd,
PinAuth: token,
}
req, err := v.request(http.MethodPost, uri, request.MarshalJSON(data))
if err == nil {
err = v.DoJSON(req, &res)
}
if err == nil && res.Message != "" {
err = fmt.Errorf("action %s: %s", action, res.Message)
}
return res, err
}
func (v *API) ChargeNow(vin, pin string) (ActionResponse, error) {
return v.Action(vin, pin, "ev/chargenow", "CNOW")
}
func (v *API) UpdateSchedule(vin, pin string, schedules []Schedule) (ActionResponse, error) {
var res ActionResponse
token, err := v.pinAuth(pin)
if err != nil {
return res, err
}
uri := fmt.Sprintf("%s/v2/accounts/%s/vehicles/%s/ev/schedule", ApiURI, v.identity.UID(), vin)
data := struct {
Command string `json:"command"`
PinAuth string `json:"pinAuth"`
Schedules []Schedule `json:"schedules"`
}{
Command: "CPPLUS",
PinAuth: token,
Schedules: schedules,
}
req, err := v.request(http.MethodPost, uri, request.MarshalJSON(data))
if err == nil {
err = v.DoJSON(req, &res)
}
if err == nil && res.Message != "" {
err = fmt.Errorf("action schedules: %s", res.Message)
}
return res, err
}