evcc-io/vehicle/bmw/connected/api.go
2025-10-04 09:43:34 +00:00

109 lines
2.7 KiB
Go

package bmw
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
"golang.org/x/oauth2"
)
// https://github.com/bimmerconnected/bimmer_connected
// https://github.com/TA2k/ioBroker.bmw
// API is an api.Vehicle implementation for BMW cars
type API struct {
*request.Helper
region string
}
// NewAPI creates a new vehicle
func NewAPI(log *util.Logger, brand, region string, identity oauth2.TokenSource) *API {
v := &API{
Helper: request.NewHelper(log),
region: strings.ToUpper(region),
}
// replace client transport with authenticated transport
v.Client.Transport = &transport.Decorator{
Base: &oauth2.Transport{
Source: identity,
Base: v.Client.Transport,
},
Decorator: transport.DecorateHeaders(map[string]string{
"X-User-Agent": fmt.Sprintf("android(SP1A.210812.016.C1);%s;99.0.0(99999);row", brand),
}),
}
return v
}
// Vehicles implements returns the /user/vehicles api
func (v *API) Vehicles() ([]Vehicle, error) {
var res []Vehicle
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles?apptimezone=120&appDateTime=%d", regions[v.region].CocoApiURI, time.Now().UnixMilli())
err := v.GetJSON(uri, &res)
return res, err
}
// Status implements the /user/vehicles/<vin>/status api
func (v *API) Status(vin string) (VehicleStatus, error) {
var res VehicleStatus
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=%d", regions[v.region].CocoApiURI, time.Now().UnixMilli())
req, _ := request.New(http.MethodGet, uri, nil, map[string]string{
"bmw-vin": vin,
})
err := v.DoJSON(req, &res)
if err != nil && res.StatusCode != 0 {
err = fmt.Errorf("%d: %s (%w)", res.StatusCode, res.Message, err)
}
return res, err
}
const (
CHARGE_START = "start-charging"
CHARGE_STOP = "stop-charging"
DOOR_LOCK = "door-lock"
LIGHT_FLASH = "light-flash"
REMOTE_SERVICE_BASE_URL = "eadrax-vrccs/v3/presentation/remote-commands"
VEHICLE_CHARGING_BASE_URL = "eadrax-crccs/v1/vehicles"
)
var serviceUrls = map[string]string{
CHARGE_START: VEHICLE_CHARGING_BASE_URL,
CHARGE_STOP: VEHICLE_CHARGING_BASE_URL,
}
type Event struct {
EventID string
CreationTime time.Time
}
// Action implements the /remote-commands/<vin>/<service> api
func (v *API) Action(vin, action string) (Event, error) {
var res Event
path, ok := serviceUrls[action]
if !ok {
path = REMOTE_SERVICE_BASE_URL
}
uri := fmt.Sprintf("%s/%s/%s/%s", regions[v.region].CocoApiURI, path, vin, action)
req, err := request.New(http.MethodPost, uri, nil, map[string]string{
"Accept": request.JSONContent,
})
if err == nil {
err = v.DoJSON(req, &res)
}
return res, err
}