BMW: add wakeup (by locking door) (#10903)
This commit is contained in:
parent
82ecb9f312
commit
c6e0c640d9
4 changed files with 104 additions and 49 deletions
|
|
@ -63,10 +63,15 @@ func NewBMWMiniFromConfig(brand string, other map[string]interface{}) (api.Vehic
|
|||
|
||||
api := bmw.NewAPI(log, brand, cc.Region, ts)
|
||||
|
||||
cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles)
|
||||
vehicle, err := ensureVehicleEx(
|
||||
cc.VIN, api.Vehicles,
|
||||
func(v bmw.Vehicle) string {
|
||||
return v.VIN
|
||||
},
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
v.Provider = bmw.NewProvider(api, cc.VIN, cc.Cache)
|
||||
v.Provider = bmw.NewProvider(api, vehicle.VIN, cc.Cache)
|
||||
}
|
||||
|
||||
return v, err
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
|
@ -17,50 +18,36 @@ import (
|
|||
// API is an api.Vehicle implementation for BMW cars
|
||||
type API struct {
|
||||
*request.Helper
|
||||
xUserAgent string
|
||||
region string
|
||||
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),
|
||||
xUserAgent: fmt.Sprintf("android(SP1A.210812.016.C1);%s;99.0.0(99999);row", brand),
|
||||
region: strings.ToUpper(region),
|
||||
Helper: request.NewHelper(log),
|
||||
region: strings.ToUpper(region),
|
||||
}
|
||||
|
||||
// replace client transport with authenticated transport
|
||||
v.Client.Transport = &oauth2.Transport{
|
||||
Source: identity,
|
||||
Base: v.Client.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() ([]string, error) {
|
||||
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())
|
||||
|
||||
req, err := request.New(http.MethodGet, uri, nil, map[string]string{
|
||||
"Content-Type": request.JSONContent,
|
||||
"X-User-Agent": v.xUserAgent,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := v.DoJSON(req, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vehicles []string
|
||||
for _, v := range res {
|
||||
vehicles = append(vehicles, v.VIN)
|
||||
}
|
||||
|
||||
return vehicles, err
|
||||
err := v.GetJSON(uri, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Status implements the /user/vehicles/<vin>/status api
|
||||
|
|
@ -69,9 +56,7 @@ func (v *API) Status(vin string) (VehicleStatus, error) {
|
|||
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=%d", regions[v.region].CocoApiURI, time.Now().UnixMilli())
|
||||
|
||||
req, err := request.New(http.MethodGet, uri, nil, map[string]string{
|
||||
"Content-Type": request.JSONContent,
|
||||
"X-User-Agent": v.xUserAgent,
|
||||
"bmw-vin": vin,
|
||||
"bmw-vin": vin,
|
||||
})
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
|
|
@ -79,3 +64,44 @@ func (v *API) Status(vin string) (VehicleStatus, error) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
// Provider implements the vehicle api
|
||||
type Provider struct {
|
||||
statusG func() (VehicleStatus, error)
|
||||
actionS func(action string) error
|
||||
}
|
||||
|
||||
// NewProvider creates a vehicle api provider
|
||||
|
|
@ -18,6 +19,10 @@ func NewProvider(api *API, vin string, cache time.Duration) *Provider {
|
|||
statusG: provider.Cached(func() (VehicleStatus, error) {
|
||||
return api.Status(vin)
|
||||
}, cache),
|
||||
actionS: func(action string) error {
|
||||
_, err := api.Action(vin, action)
|
||||
return err
|
||||
},
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
|
@ -90,3 +95,21 @@ func (v *Provider) Odometer() (float64, error) {
|
|||
|
||||
return float64(res.State.CurrentMileage), nil
|
||||
}
|
||||
|
||||
var _ api.Resurrector = (*Provider)(nil)
|
||||
|
||||
func (v *Provider) WakeUp() error {
|
||||
return v.actionS(DOOR_LOCK)
|
||||
}
|
||||
|
||||
var _ api.VehicleChargeController = (*Provider)(nil)
|
||||
|
||||
// StartCharge implements the api.VehicleChargeController interface
|
||||
func (v *Provider) StartCharge() error {
|
||||
return v.actionS(CHARGE_START)
|
||||
}
|
||||
|
||||
// StopCharge implements the api.VehicleChargeController interface
|
||||
func (v *Provider) StopCharge() error {
|
||||
return v.actionS(CHARGE_STOP)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,16 +15,19 @@ func ensureVehicle(vin string, list func() ([]string, error)) (string, error) {
|
|||
}
|
||||
|
||||
// ensureVehicleEx extracts vehicle with matching VIN from list of vehicles
|
||||
func ensureVehicleEx[Vehicle any](
|
||||
func ensureVehicleEx[T any](
|
||||
vin string,
|
||||
list func() ([]Vehicle, error),
|
||||
extract func(Vehicle) string,
|
||||
) (Vehicle, error) {
|
||||
list func() ([]T, error),
|
||||
extract func(T) string,
|
||||
) (T, error) {
|
||||
var zero T
|
||||
|
||||
vehicles, err := list()
|
||||
if err != nil {
|
||||
return *new(Vehicle), fmt.Errorf("cannot get vehicles: %w", err)
|
||||
return zero, fmt.Errorf("cannot get vehicles: %w", err)
|
||||
}
|
||||
|
||||
// vin defined
|
||||
if vin = strings.ToUpper(vin); vin != "" {
|
||||
for _, vehicle := range vehicles {
|
||||
if vin == extract(vehicle) {
|
||||
|
|
@ -33,17 +36,15 @@ func ensureVehicleEx[Vehicle any](
|
|||
}
|
||||
|
||||
// vin defined but doesn't exist
|
||||
err = fmt.Errorf("cannot find vehicle: %s", vin)
|
||||
} else {
|
||||
// vin empty
|
||||
if len(vehicles) == 1 {
|
||||
return vehicles[0], nil
|
||||
}
|
||||
|
||||
err = fmt.Errorf("cannot find vehicle, got: %v", lo.Map(vehicles, func(v Vehicle, _ int) string {
|
||||
return extract(v)
|
||||
}))
|
||||
return zero, fmt.Errorf("cannot find vehicle: %s", vin)
|
||||
}
|
||||
|
||||
return *new(Vehicle), err
|
||||
// vin empty
|
||||
if len(vehicles) == 1 {
|
||||
return vehicles[0], nil
|
||||
}
|
||||
|
||||
return zero, fmt.Errorf("cannot find vehicle, got: %v", lo.Map(vehicles, func(v T, _ int) string {
|
||||
return extract(v)
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue