diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21c9409d..7da8f1c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.5.1 + rev: v0.5.5 hooks: - id: ruff args: @@ -24,7 +24,7 @@ repos: exclude_types: [csv, json] exclude: ^test/responses/ - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.0 hooks: - id: mypy name: mypy diff --git a/bimmer_connected/const.py b/bimmer_connected/const.py index cfc2c5dd..a007f66c 100644 --- a/bimmer_connected/const.py +++ b/bimmer_connected/const.py @@ -39,19 +39,19 @@ class Regions(str, Enum): } APP_VERSIONS = { - Regions.NORTH_AMERICA: "3.11.1(29513)", - Regions.REST_OF_WORLD: "3.11.1(29513)", - Regions.CHINA: "3.11.1(29513)", + Regions.NORTH_AMERICA: "4.7.2(35379)", + Regions.REST_OF_WORLD: "4.7.2(35379)", + Regions.CHINA: "4.7.2(35379)", } HTTPX_TIMEOUT = 30.0 USER_AGENTS = { - Regions.NORTH_AMERICA: "Dart/3.0 (dart:io)", - Regions.REST_OF_WORLD: "Dart/3.0 (dart:io)", - Regions.CHINA: "Dart/3.0 (dart:io)", + Regions.NORTH_AMERICA: "Dart/3.3 (dart:io)", + Regions.REST_OF_WORLD: "Dart/3.3 (dart:io)", + Regions.CHINA: "Dart/3.3 (dart:io)", } -X_USER_AGENT = "android(TQ2A.230405.003.B2);{brand};{app_version};{region}" +X_USER_AGENT = "android(AP2A.240605.024);{brand};{app_version};{region}" AUTH_CHINA_PUBLIC_KEY_URL = "/eadrax-coas/v1/cop/publickey" @@ -66,10 +66,11 @@ class Regions(str, Enum): VEHICLE_PROFILE_URL = "/eadrax-vcs/v5/vehicle-data/profile" VEHICLE_STATE_URL = "/eadrax-vcs/v4/vehicles/state" -REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v3/presentation/remote-commands" -REMOTE_SERVICE_URL = REMOTE_SERVICE_BASE_URL + "/{vin}/{service_type}" -REMOTE_SERVICE_STATUS_URL = REMOTE_SERVICE_BASE_URL + "/eventStatus?eventId={event_id}" -REMOTE_SERVICE_POSITION_URL = REMOTE_SERVICE_BASE_URL + "/eventPosition?eventId={event_id}" +REMOTE_SERVICE_V3_BASE_URL = "/eadrax-vrccs/v3/presentation/remote-commands" +REMOTE_SERVICE_V4_BASE_URL = "/eadrax-vrccs/v4/presentation/remote-commands" +REMOTE_SERVICE_URL = REMOTE_SERVICE_V4_BASE_URL + "/{service_type}" +REMOTE_SERVICE_STATUS_URL = REMOTE_SERVICE_V3_BASE_URL + "/eventStatus?eventId={event_id}" +REMOTE_SERVICE_POSITION_URL = REMOTE_SERVICE_V4_BASE_URL + "/eventPosition?eventId={event_id}" VEHICLE_CHARGING_DETAILS_URL = "/eadrax-crccs/v2/vehicles" VEHICLE_CHARGING_BASE_URL = "/eadrax-crccs/v1/vehicles/{vin}" @@ -78,7 +79,7 @@ class Regions(str, Enum): VEHICLE_CHARGING_START_STOP_URL = VEHICLE_CHARGING_BASE_URL + "/{service_type}" VEHICLE_IMAGE_URL = "/eadrax-ics/v5/presentation/vehicles/images" -VEHICLE_POI_URL = "/eadrax-dcs/v1/send-to-car/send-to-car" +VEHICLE_POI_URL = "/eadrax-dcs/v2/user/{gcid}/send-to-car" VEHICLE_CHARGING_STATISTICS_URL = "/eadrax-chs/v2/charging-statistics" VEHICLE_CHARGING_SESSIONS_URL = "/eadrax-chs/v2/charging-sessions" diff --git a/bimmer_connected/models.py b/bimmer_connected/models.py index cd01ff3a..a45d33b6 100644 --- a/bimmer_connected/models.py +++ b/bimmer_connected/models.py @@ -96,6 +96,7 @@ class PointOfInterestAddress: postalCode: Optional[str] = None city: Optional[str] = None country: Optional[str] = None + formatted: Optional[str] = None # The following attributes are not by us but available in the API banchi: Optional[str] = None @@ -107,6 +108,7 @@ class PointOfInterestAddress: region: Optional[str] = None regionCode: Optional[str] = None settlement: Optional[str] = None + subblock: Optional[str] = None @dataclass @@ -115,33 +117,38 @@ class PointOfInterest: lat: InitVar[float] lon: InitVar[float] - name: Optional[str] = DEFAULT_POI_NAME + name: InitVar[str] = DEFAULT_POI_NAME street: InitVar[str] = None postal_code: InitVar[str] = None city: InitVar[str] = None country: InitVar[str] = None - coordinates: GPSPosition = field(init=False) - locationAddress: Optional[PointOfInterestAddress] = field(init=False) + position: Dict[str, float] = field(init=False) + address: Optional[PointOfInterestAddress] = field(init=False) # The following attributes are not by us but required in the API formattedAddress: Optional[str] = None - entryPoints: List = field(init=False, default_factory=list) + entrances: Optional[List] = field(init=False) + placeType: Optional[str] = "ADDRESS" + category: Dict[str, Optional[str]] = field(init=False) + title: str = "Sent with ♥ by bimmer_connected" # The following attributes are not by us but available in the API - address: Optional[str] = None - baseCategoryId: Optional[str] = None - phoneNumber: Optional[str] = None provider: Optional[str] = None providerId: Optional[str] = None providerPoiId: str = "" sourceType: Optional[str] = None - type: Optional[str] = None vehicleCategoryId: Optional[str] = None - def __post_init__(self, lat, lon, street, postal_code, city, country): - self.coordinates = GPSPosition(lat, lon) + def __post_init__(self, lat, lon, name, street, postal_code, city, country): + position = GPSPosition(lat, lon) + self.position = { + "lat": position.latitude, + "lon": position.longitude, + } - self.locationAddress = PointOfInterestAddress(str(street), str(postal_code), str(city), str(country)) + self.address = PointOfInterestAddress(str(street), str(postal_code), str(city), str(country)) + self.category = {"losCategory": "Address", "mguVehicleCategoryId": None, "name": "Address"} + self.title = name if not self.formattedAddress: self.formattedAddress = ", ".join([str(i) for i in [street, postal_code, city] if i]) or "Coordinates only" diff --git a/bimmer_connected/tests/common.py b/bimmer_connected/tests/common.py index 0ea1c8b4..d4e32a62 100644 --- a/bimmer_connected/tests/common.py +++ b/bimmer_connected/tests/common.py @@ -122,7 +122,7 @@ def add_vehicle_routes(self) -> None: def add_remote_service_routes(self) -> None: """Add routes for remote services.""" - self.post(path__regex=r"/eadrax-vrccs/v3/presentation/remote-commands/(?P.+)/(?P.+)$").mock( + self.post(path__regex=r"/eadrax-vrccs/v4/presentation/remote-commands/(?!event.*)(?P.+)$").mock( side_effect=self.service_trigger_sideeffect ) self.post(path__regex=r"/eadrax-crccs/v1/vehicles/(?P.+)/(?P(start|stop)-charging)$").mock( @@ -138,8 +138,8 @@ def add_remote_service_routes(self) -> None: side_effect=self.service_status_sideeffect ) - self.post("/eadrax-dcs/v1/send-to-car/send-to-car").mock(side_effect=self.poi_sideeffect) - self.post("/eadrax-vrccs/v3/presentation/remote-commands/eventPosition", params={"eventId": mock.ANY}).respond( + self.post(path__regex=r"/eadrax-dcs/v2/user/(?P.+)/send-to-car$").mock(side_effect=self.poi_sideeffect) + self.post("/eadrax-vrccs/v4/presentation/remote-commands/eventPosition", params={"eventId": mock.ANY}).respond( 200, json=load_response(REMOTE_SERVICE_RESPONSE_EVENTPOSITION), ) @@ -224,10 +224,12 @@ def vehicle_charging_settings_sideeffect(self, request: httpx.Request) -> httpx. # # # # # # # # # # # # # # # # # # # # # # # # def service_trigger_sideeffect( - self, request: httpx.Request, vin: str, service: Optional[str] = None + self, request: httpx.Request, vin: Optional[str] = None, service: Optional[str] = None ) -> httpx.Response: """Return specific eventId for each remote function.""" + vin = vin or request.headers["bmw-vin"] + if service in ["door-lock", "door-unlock"]: new_state = "LOCKED" if service == "door-lock" else "UNLOCKED" self.states[vin]["state"]["doorsState"]["combinedSecurityState"] = new_state @@ -316,15 +318,18 @@ def service_status_sideeffect(request: httpx.Request) -> httpx.Response: return httpx.Response(200, json=load_response(response_data)) @staticmethod - def poi_sideeffect(request: httpx.Request) -> httpx.Response: + def poi_sideeffect(request: httpx.Request, gcid: str) -> httpx.Response: """Check if payload is a valid POI.""" + + assert gcid == "DUMMY" + data = json.loads(request.content) tests = all( [ - len(data["vin"]) == 17, - isinstance(data["location"]["coordinates"]["latitude"], float), - isinstance(data["location"]["coordinates"]["longitude"], float), - len(data["location"]["name"]) > 0, + len(data["vehicleInformation"]["vin"]) == 17, + isinstance(data["places"][0]["position"]["lat"], float), + isinstance(data["places"][0]["position"]["lon"], float), + len(data["places"][0]["title"]) > 0, ] ) if not tests: diff --git a/bimmer_connected/tests/responses/F31/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000000F31.json b/bimmer_connected/tests/responses/F31/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000000F31.json index 15668a3d..5671494a 100644 --- a/bimmer_connected/tests/responses/F31/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000000F31.json +++ b/bimmer_connected/tests/responses/F31/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000000F31.json @@ -12,6 +12,7 @@ "vehicleSoftwareUpgradeRequired": false }, "horn": true, + "isBatteryPreconditioningSupported": false, "isBmwChargingSupported": false, "isCarSharingSupported": false, "isChargeNowForBusinessSupported": false, @@ -38,16 +39,57 @@ "isRemoteEngineStartSupported": false, "isRemoteHistoryDeletionSupported": false, "isRemoteHistorySupported": true, + "isRemoteParkingEes25Active": false, "isRemoteParkingSupported": false, "isRemoteServicesActivationRequired": false, "isRemoteServicesBookingRequired": false, "isScanAndChargeSupported": false, "isSustainabilityAccumulatedViewEnabled": false, "isSustainabilitySupported": false, + "isThirdPartyAppStoreSupported": false, "isWifiHotspotServiceSupported": false, "lights": true, + "locationBasedCommerceFeatures": { + "fueling": false, + "parking": false, + "reservations": false + }, "lock": true, "remoteChargingCommands": {}, + "remoteServices": { + "doorLock": { + "id": "doorLock", + "state": "ACTIVATED" + }, + "doorUnlock": { + "id": "doorUnlock", + "state": "ACTIVATED" + }, + "hornBlow": { + "id": "hornBlow", + "state": "ACTIVATED" + }, + "inCarCamera": { + "id": "inCarCamera", + "state": "NOT_AVAILABLE" + }, + "inCarCameraDwa": { + "id": "inCarCameraDwa", + "state": "NOT_AVAILABLE" + }, + "lightFlash": { + "id": "lightFlash", + "state": "ACTIVATED" + }, + "remote360": { + "id": "remote360", + "state": "NOT_AVAILABLE" + }, + "surroundViewRecorder": { + "id": "surroundViewRecorder", + "state": "NOT_AVAILABLE" + } + }, "sendPoi": true, "specialThemeSupport": [], "unlock": true, @@ -102,6 +144,18 @@ "lastFetched": "2024-01-20T10:18:57.283Z", "lastUpdatedAt": "0001-01-01T00:00:00Z", "requiredServices": [], - "securityOverviewMode": null + "securityOverviewMode": null, + "vehicleSoftwareVersion": { + "iStep": { + "iStep": 502, + "month": 11, + "seriesCluster": "F020", + "year": 13 + }, + "puStep": { + "month": 11, + "year": 13 + } + } } } \ No newline at end of file diff --git a/bimmer_connected/tests/responses/G01/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO04.json b/bimmer_connected/tests/responses/G01/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO04.json index c34391d9..88cd7c53 100644 --- a/bimmer_connected/tests/responses/G01/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO04.json +++ b/bimmer_connected/tests/responses/G01/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO04.json @@ -1,15 +1,19 @@ { "capabilities": { "a4aType": "BLUETOOTH", - "checkSustainabilityDPP": false, + "alarmSystem": false, "climateFunction": "AIR_CONDITIONING", "climateNow": true, "digitalKey": { "bookedServicePackage": "SMACC_1_5", + "isDigitalKeyFirstSupported": false, "readerGraphics": "readerGraphics", - "state": "ACTIVATED" + "state": "ACTIVATED", + "vehicleSoftwareUpgradeRequired": false }, "horn": true, + "inCarCamera": false, + "inCarCameraDwa": false, "isBmwChargingSupported": true, "isCarSharingSupported": false, "isChargeNowForBusinessSupported": true, @@ -20,35 +24,78 @@ "isChargingPowerLimitEnabled": false, "isChargingSettingsEnabled": false, "isChargingTargetSocEnabled": false, + "isClimateTimerSupported": false, "isClimateTimerWeeklyActive": false, "isCustomerEsimSupported": false, "isDCSContractManagementSupported": true, "isDataPrivacyEnabled": false, "isEasyChargeEnabled": false, "isEvGoChargingSupported": false, + "isLocationBasedChargingSettingsSupported": false, "isMiniChargingSupported": false, "isNonLscFeatureEnabled": false, "isPersonalPictureUploadSupported": false, - "isRemoteEngineStartSupported": false, + "isPlugAndChargeSupported": false, + "isRemoteEngineStartEnabled": false, + "isRemoteEngineStartSupported": true, "isRemoteHistoryDeletionSupported": false, "isRemoteHistorySupported": true, + "isRemoteParkingEes25Active": false, "isRemoteParkingSupported": false, "isRemoteServicesActivationRequired": false, "isRemoteServicesBookingRequired": false, "isScanAndChargeSupported": true, "isSustainabilityAccumulatedViewEnabled": false, "isSustainabilitySupported": false, + "isThirdPartyAppStoreSupported": false, "isWifiHotspotServiceSupported": true, "lastStateCallState": "ACTIVATED", "lights": true, + "locationBasedCommerceFeatures": { + "fueling": false, + "parking": false, + "reservations": false + }, "lock": true, "remote360": true, "remoteChargingCommands": {}, + "remoteServices": { + "doorLock": { + "id": "doorLock", + "state": "ACTIVATED" + }, + "doorUnlock": { + "id": "doorUnlock", + "state": "ACTIVATED" + }, + "hornBlow": { + "id": "hornBlow", + "state": "ACTIVATED" + }, + "inCarCamera": { + "id": "inCarCamera" + }, + "inCarCameraDwa": { + "id": "inCarCameraDwa" + }, + "lightFlash": { + "id": "lightFlash", + "state": "ACTIVATED" + }, + "remote360": { + "id": "remote360", + "state": "ACTIVATED" + }, + "surroundViewRecorder": { + "id": "surroundViewRecorder" + } + }, "remoteSoftwareUpgrade": true, "sendPoi": true, "specialThemeSupport": [], - "speechThirdPartyAlexa": false, + "speechThirdPartyAlexa": true, "speechThirdPartyAlexaSDK": false, + "surroundViewRecorder": false, "unlock": true, "vehicleFinder": true, "vehicleStateSource": "LAST_STATE_CALL" @@ -218,6 +265,7 @@ "type": "VEHICLE_CHECK" } ], + "securityOverviewMode": "ARMED", "tireState": { "frontLeft": { "status": { @@ -244,6 +292,18 @@ } } }, + "vehicleSoftwareVersion": { + "iStep": { + "iStep": 0, + "month": 0, + "seriesCluster": "", + "year": 0 + }, + "puStep": { + "month": 0, + "year": 0 + } + }, "windowsState": { "combinedState": "CLOSED", "leftFront": "CLOSED", diff --git a/bimmer_connected/tests/responses/G20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO03.json b/bimmer_connected/tests/responses/G20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO03.json index 52c72d13..5a0f61e6 100644 --- a/bimmer_connected/tests/responses/G20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO03.json +++ b/bimmer_connected/tests/responses/G20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO03.json @@ -1,16 +1,20 @@ { "capabilities": { "a4aType": "NOT_SUPPORTED", - "checkSustainabilityDPP": false, + "alarmSystem": false, "climateFunction": "VENTILATION", "climateNow": true, "climateTimerTrigger": "DEPARTURE_TIMER", "digitalKey": { "bookedServicePackage": "SMACC_1_5", + "isDigitalKeyFirstSupported": false, "readerGraphics": "readerGraphics", - "state": "ACTIVATED" + "state": "ACTIVATED", + "vehicleSoftwareUpgradeRequired": false }, "horn": true, + "inCarCamera": false, + "inCarCameraDwa": false, "isBmwChargingSupported": false, "isCarSharingSupported": false, "isChargeNowForBusinessSupported": false, @@ -28,29 +32,71 @@ "isDataPrivacyEnabled": false, "isEasyChargeEnabled": false, "isEvGoChargingSupported": false, + "isLocationBasedChargingSettingsSupported": false, "isMiniChargingSupported": false, "isNonLscFeatureEnabled": false, "isPersonalPictureUploadSupported": false, - "isRemoteEngineStartSupported": false, + "isPlugAndChargeSupported": false, + "isRemoteEngineStartEnabled": false, + "isRemoteEngineStartSupported": true, "isRemoteHistoryDeletionSupported": false, "isRemoteHistorySupported": true, + "isRemoteParkingEes25Active": false, "isRemoteParkingSupported": false, "isRemoteServicesActivationRequired": false, "isRemoteServicesBookingRequired": false, "isScanAndChargeSupported": false, "isSustainabilityAccumulatedViewEnabled": false, "isSustainabilitySupported": false, - "isWifiHotspotServiceSupported": true, + "isThirdPartyAppStoreSupported": false, + "isWifiHotspotServiceSupported": false, "lastStateCallState": "ACTIVATED", "lights": true, + "locationBasedCommerceFeatures": { + "fueling": false, + "parking": false, + "reservations": false + }, "lock": true, "remote360": true, "remoteChargingCommands": {}, + "remoteServices": { + "doorLock": { + "id": "doorLock", + "state": "ACTIVATED" + }, + "doorUnlock": { + "id": "doorUnlock", + "state": "ACTIVATED" + }, + "hornBlow": { + "id": "hornBlow", + "state": "ACTIVATED" + }, + "inCarCamera": { + "id": "inCarCamera" + }, + "inCarCameraDwa": { + "id": "inCarCameraDwa" + }, + "lightFlash": { + "id": "lightFlash", + "state": "ACTIVATED" + }, + "remote360": { + "id": "remote360", + "state": "ACTIVATED" + }, + "surroundViewRecorder": { + "id": "surroundViewRecorder" + } + }, "remoteSoftwareUpgrade": true, "sendPoi": true, "specialThemeSupport": [], - "speechThirdPartyAlexa": false, + "speechThirdPartyAlexa": true, "speechThirdPartyAlexaSDK": false, + "surroundViewRecorder": false, "unlock": true, "vehicleFinder": true, "vehicleStateSource": "LAST_STATE_CALL" @@ -180,6 +226,7 @@ "type": "TIRE_WEAR_FRONT" } ], + "securityOverviewMode": null, "tireState": { "frontLeft": { "details": { @@ -266,6 +313,18 @@ } } }, + "vehicleSoftwareVersion": { + "iStep": { + "iStep": 0, + "month": 0, + "seriesCluster": "", + "year": 0 + }, + "puStep": { + "month": 0, + "year": 0 + } + }, "windowsState": { "combinedState": "CLOSED", "leftFront": "CLOSED", diff --git a/bimmer_connected/tests/responses/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json b/bimmer_connected/tests/responses/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json index b9a035cb..43a345ca 100644 --- a/bimmer_connected/tests/responses/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json +++ b/bimmer_connected/tests/responses/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json @@ -1,15 +1,19 @@ { "capabilities": { "a4aType": "NOT_SUPPORTED", - "checkSustainabilityDPP": false, + "alarmSystem": false, "climateFunction": "AIR_CONDITIONING", "climateNow": true, "digitalKey": { "bookedServicePackage": "SMACC_1_5", + "isDigitalKeyFirstSupported": false, "readerGraphics": "readerGraphics", - "state": "ACTIVATED" + "state": "ACTIVATED", + "vehicleSoftwareUpgradeRequired": false }, "horn": true, + "inCarCamera": false, + "inCarCameraDwa": false, "isBmwChargingSupported": true, "isCarSharingSupported": false, "isChargeNowForBusinessSupported": true, @@ -20,35 +24,78 @@ "isChargingPowerLimitEnabled": true, "isChargingSettingsEnabled": true, "isChargingTargetSocEnabled": true, + "isClimateTimerSupported": false, "isClimateTimerWeeklyActive": false, - "isCustomerEsimSupported": true, + "isCustomerEsimSupported": false, "isDCSContractManagementSupported": true, "isDataPrivacyEnabled": false, "isEasyChargeEnabled": true, "isEvGoChargingSupported": false, + "isLocationBasedChargingSettingsSupported": false, "isMiniChargingSupported": false, "isNonLscFeatureEnabled": false, "isPersonalPictureUploadSupported": false, - "isRemoteEngineStartSupported": false, + "isPlugAndChargeSupported": false, + "isRemoteEngineStartEnabled": false, + "isRemoteEngineStartSupported": true, "isRemoteHistoryDeletionSupported": false, "isRemoteHistorySupported": true, + "isRemoteParkingEes25Active": false, "isRemoteParkingSupported": false, "isRemoteServicesActivationRequired": false, "isRemoteServicesBookingRequired": false, "isScanAndChargeSupported": true, "isSustainabilityAccumulatedViewEnabled": false, "isSustainabilitySupported": false, + "isThirdPartyAppStoreSupported": false, "isWifiHotspotServiceSupported": false, "lastStateCallState": "ACTIVATED", "lights": true, + "locationBasedCommerceFeatures": { + "fueling": false, + "parking": false, + "reservations": false + }, "lock": true, "remote360": true, "remoteChargingCommands": {}, + "remoteServices": { + "doorLock": { + "id": "doorLock", + "state": "ACTIVATED" + }, + "doorUnlock": { + "id": "doorUnlock", + "state": "ACTIVATED" + }, + "hornBlow": { + "id": "hornBlow", + "state": "ACTIVATED" + }, + "inCarCamera": { + "id": "inCarCamera" + }, + "inCarCameraDwa": { + "id": "inCarCameraDwa" + }, + "lightFlash": { + "id": "lightFlash", + "state": "ACTIVATED" + }, + "remote360": { + "id": "remote360", + "state": "ACTIVATED" + }, + "surroundViewRecorder": { + "id": "surroundViewRecorder" + } + }, "remoteSoftwareUpgrade": true, "sendPoi": true, "specialThemeSupport": [], - "speechThirdPartyAlexa": false, + "speechThirdPartyAlexa": true, "speechThirdPartyAlexaSDK": false, + "surroundViewRecorder": false, "unlock": true, "vehicleFinder": true, "vehicleStateSource": "LAST_STATE_CALL" @@ -212,6 +259,7 @@ "type": "TIRE_WEAR_FRONT" } ], + "securityOverviewMode": "NOT_ARMED", "tireState": { "frontLeft": { "details": { @@ -302,6 +350,18 @@ } } }, + "vehicleSoftwareVersion": { + "iStep": { + "iStep": 0, + "month": 0, + "seriesCluster": "", + "year": 0 + }, + "puStep": { + "month": 0, + "year": 0 + } + }, "windowsState": { "combinedState": "CLOSED", "leftFront": "CLOSED", diff --git a/bimmer_connected/tests/responses/G70/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO05.json b/bimmer_connected/tests/responses/G70/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO05.json index 2f346b59..79982ab4 100644 --- a/bimmer_connected/tests/responses/G70/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO05.json +++ b/bimmer_connected/tests/responses/G70/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO05.json @@ -1,16 +1,19 @@ { "capabilities": { "a4aType": "BLUETOOTH", - "checkSustainabilityDPP": false, + "alarmSystem": true, "climateFunction": "AIR_CONDITIONING", "climateNow": true, "digitalKey": { "bookedServicePackage": "SMACC_2_UWB", + "isDigitalKeyFirstSupported": false, "readerGraphics": "readerGraphics", - "state": "ACTIVATED" + "state": "ACTIVATED", + "vehicleSoftwareUpgradeRequired": true }, "horn": true, "inCarCamera": true, + "inCarCameraDwa": true, "isBmwChargingSupported": true, "isCarSharingSupported": false, "isChargeNowForBusinessSupported": true, @@ -21,35 +24,80 @@ "isChargingPowerLimitEnabled": true, "isChargingSettingsEnabled": true, "isChargingTargetSocEnabled": true, + "isClimateTimerSupported": false, "isClimateTimerWeeklyActive": false, "isCustomerEsimSupported": true, "isDCSContractManagementSupported": true, "isDataPrivacyEnabled": false, "isEasyChargeEnabled": true, "isEvGoChargingSupported": false, + "isLocationBasedChargingSettingsSupported": true, "isMiniChargingSupported": false, "isNonLscFeatureEnabled": false, "isPersonalPictureUploadSupported": false, - "isRemoteEngineStartSupported": false, + "isPlugAndChargeSupported": false, + "isRemoteEngineStartEnabled": false, + "isRemoteEngineStartSupported": true, "isRemoteHistoryDeletionSupported": false, "isRemoteHistorySupported": true, + "isRemoteParkingEes25Active": false, "isRemoteParkingSupported": false, "isRemoteServicesActivationRequired": false, "isRemoteServicesBookingRequired": false, "isScanAndChargeSupported": true, "isSustainabilityAccumulatedViewEnabled": false, "isSustainabilitySupported": false, + "isThirdPartyAppStoreSupported": false, "isWifiHotspotServiceSupported": false, "lastStateCallState": "ACTIVATED", "lights": true, + "locationBasedCommerceFeatures": { + "fueling": false, + "parking": false, + "reservations": false + }, "lock": true, "remote360": true, "remoteChargingCommands": {}, + "remoteServices": { + "doorLock": { + "id": "doorLock", + "state": "ACTIVATED" + }, + "doorUnlock": { + "id": "doorUnlock", + "state": "ACTIVATED" + }, + "hornBlow": { + "id": "hornBlow", + "state": "ACTIVATED" + }, + "inCarCamera": { + "id": "inCarCamera", + "state": "ACTIVATED" + }, + "inCarCameraDwa": { + "id": "inCarCameraDwa", + "state": "ACTIVATED" + }, + "lightFlash": { + "id": "lightFlash", + "state": "ACTIVATED" + }, + "remote360": { + "id": "remote360", + "state": "ACTIVATED" + }, + "surroundViewRecorder": { + "id": "surroundViewRecorder", + "state": "ACTIVATED" + } + }, "remoteSoftwareUpgrade": true, "sendPoi": true, "specialThemeSupport": [], "speechThirdPartyAlexa": false, - "speechThirdPartyAlexaSDK": false, + "speechThirdPartyAlexaSDK": true, "surroundViewRecorder": true, "unlock": true, "vehicleFinder": true, @@ -219,6 +267,7 @@ "roofState": "CLOSED", "roofStateType": "SUN_ROOF" }, + "securityOverviewMode": "ARMED", "tireState": { "frontLeft": { "details": { @@ -309,6 +358,18 @@ } } }, + "vehicleSoftwareVersion": { + "iStep": { + "iStep": 0, + "month": 0, + "seriesCluster": "", + "year": 0 + }, + "puStep": { + "month": 0, + "year": 0 + } + }, "windowsState": { "combinedState": "CLOSED", "leftFront": "CLOSED", diff --git a/bimmer_connected/tests/responses/I20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO01.json b/bimmer_connected/tests/responses/I20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO01.json index 417a31c7..4219e90f 100644 --- a/bimmer_connected/tests/responses/I20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO01.json +++ b/bimmer_connected/tests/responses/I20/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO01.json @@ -1,16 +1,19 @@ { "capabilities": { "a4aType": "BLUETOOTH", - "checkSustainabilityDPP": false, + "alarmSystem": true, "climateFunction": "AIR_CONDITIONING", "climateNow": true, "digitalKey": { "bookedServicePackage": "SMACC_2_UWB", + "isDigitalKeyFirstSupported": false, "readerGraphics": "readerGraphics", - "state": "ACTIVATED" + "state": "ACTIVATED", + "vehicleSoftwareUpgradeRequired": true }, "horn": true, "inCarCamera": true, + "inCarCameraDwa": true, "isBmwChargingSupported": true, "isCarSharingSupported": false, "isChargeNowForBusinessSupported": true, @@ -21,27 +24,38 @@ "isChargingPowerLimitEnabled": true, "isChargingSettingsEnabled": true, "isChargingTargetSocEnabled": true, + "isClimateTimerSupported": false, "isClimateTimerWeeklyActive": false, "isCustomerEsimSupported": true, "isDCSContractManagementSupported": true, "isDataPrivacyEnabled": false, "isEasyChargeEnabled": true, "isEvGoChargingSupported": false, + "isLocationBasedChargingSettingsSupported": false, "isMiniChargingSupported": false, "isNonLscFeatureEnabled": false, "isPersonalPictureUploadSupported": false, - "isRemoteEngineStartSupported": false, + "isPlugAndChargeSupported": false, + "isRemoteEngineStartEnabled": false, + "isRemoteEngineStartSupported": true, "isRemoteHistoryDeletionSupported": false, "isRemoteHistorySupported": true, + "isRemoteParkingEes25Active": false, "isRemoteParkingSupported": false, "isRemoteServicesActivationRequired": false, "isRemoteServicesBookingRequired": false, "isScanAndChargeSupported": true, "isSustainabilityAccumulatedViewEnabled": false, "isSustainabilitySupported": false, + "isThirdPartyAppStoreSupported": false, "isWifiHotspotServiceSupported": false, "lastStateCallState": "ACTIVATED", "lights": true, + "locationBasedCommerceFeatures": { + "fueling": false, + "parking": false, + "reservations": false + }, "lock": true, "remote360": true, "remoteChargingCommands": { @@ -56,10 +70,44 @@ "NOT_SUPPORTED" ] }, + "remoteServices": { + "doorLock": { + "id": "doorLock", + "state": "ACTIVATED" + }, + "doorUnlock": { + "id": "doorUnlock", + "state": "ACTIVATED" + }, + "hornBlow": { + "id": "hornBlow", + "state": "ACTIVATED" + }, + "inCarCamera": { + "id": "inCarCamera", + "state": "ACTIVATED" + }, + "inCarCameraDwa": { + "id": "inCarCameraDwa", + "state": "ACTIVATED" + }, + "lightFlash": { + "id": "lightFlash", + "state": "ACTIVATED" + }, + "remote360": { + "id": "remote360", + "state": "ACTIVATED" + }, + "surroundViewRecorder": { + "id": "surroundViewRecorder", + "state": "ACTIVATED" + } + }, "remoteSoftwareUpgrade": true, "sendPoi": true, "specialThemeSupport": [], - "speechThirdPartyAlexa": false, + "speechThirdPartyAlexa": true, "speechThirdPartyAlexaSDK": false, "surroundViewRecorder": true, "unlock": true, @@ -230,6 +278,7 @@ "roofState": "CLOSED", "roofStateType": "SUN_ROOF" }, + "securityOverviewMode": "ARMED", "tireState": { "frontLeft": { "details": { @@ -320,6 +369,18 @@ } } }, + "vehicleSoftwareVersion": { + "iStep": { + "iStep": 0, + "month": 0, + "seriesCluster": "", + "year": 0 + }, + "puStep": { + "month": 0, + "year": 0 + } + }, "windowsState": { "combinedState": "CLOSED", "leftFront": "CLOSED", diff --git a/bimmer_connected/tests/test_remote_services.py b/bimmer_connected/tests/test_remote_services.py index fd489bcc..79b0fa4e 100644 --- a/bimmer_connected/tests/test_remote_services.py +++ b/bimmer_connected/tests/test_remote_services.py @@ -384,31 +384,30 @@ async def test_poi(bmw_fixture: respx.Router): def test_poi_parsing(): """Test correct parsing of PointOfInterest.""" - poi_data = PointOfInterest(**POI_DATA) - # Check parsing of attributes required by API - assert poi_data.coordinates.latitude == POI_DATA["lat"] - assert poi_data.coordinates.longitude == POI_DATA["lon"] - assert poi_data.name == POI_DATA["name"] + poi_data = PointOfInterest(**POI_DATA) + assert poi_data.position["lat"] == POI_DATA["lat"] + assert poi_data.position["lon"] == POI_DATA["lon"] + assert poi_data.title == POI_DATA["name"] assert poi_data.formattedAddress == f"{POI_DATA['street']}, {POI_DATA['postal_code']}, {POI_DATA['city']}" # Check the default attributes poi_data = PointOfInterest(lat=POI_DATA["lat"], lon=POI_DATA["lon"]) - assert poi_data.coordinates.latitude == POI_DATA["lat"] - assert poi_data.coordinates.longitude == POI_DATA["lon"] - assert poi_data.name == "Sent with ♥ by bimmer_connected" + assert poi_data.position["lat"] == POI_DATA["lat"] + assert poi_data.position["lon"] == POI_DATA["lon"] + assert poi_data.title == "Sent with ♥ by bimmer_connected" assert poi_data.formattedAddress == "Coordinates only" # Check the default attributes with formatted address poi_data = PointOfInterest(lat=POI_DATA["lat"], lon=POI_DATA["lon"], formattedAddress="Somewhere over rainbow") - assert poi_data.coordinates.latitude == POI_DATA["lat"] - assert poi_data.coordinates.longitude == POI_DATA["lon"] - assert poi_data.name == "Sent with ♥ by bimmer_connected" + assert poi_data.position["lat"] == POI_DATA["lat"] + assert poi_data.position["lon"] == POI_DATA["lon"] + assert poi_data.title == "Sent with ♥ by bimmer_connected" assert poi_data.formattedAddress == "Somewhere over rainbow" # Check parsing with numeric postal code poi_data = PointOfInterest(lat=POI_DATA["lat"], lon=POI_DATA["lon"], postal_code=1234) - assert poi_data.coordinates.latitude == POI_DATA["lat"] - assert poi_data.coordinates.longitude == POI_DATA["lon"] - assert poi_data.name == "Sent with ♥ by bimmer_connected" - assert poi_data.locationAddress.postalCode == "1234" + assert poi_data.position["lat"] == POI_DATA["lat"] + assert poi_data.position["lon"] == POI_DATA["lon"] + assert poi_data.title == "Sent with ♥ by bimmer_connected" + assert poi_data.address.postalCode == "1234" diff --git a/bimmer_connected/vehicle/remote_services.py b/bimmer_connected/vehicle/remote_services.py index a41462d4..426c3f89 100644 --- a/bimmer_connected/vehicle/remote_services.py +++ b/bimmer_connected/vehicle/remote_services.py @@ -33,7 +33,7 @@ _LOGGER = logging.getLogger(__name__) #: time in seconds between polling updates on the status of a remote service -_POLLING_CYCLE = 3 +_POLLING_CYCLE = 3.5 #: maximum number of seconds to wait for the server to return a positive answer _POLLING_TIMEOUT = 240 @@ -110,15 +110,20 @@ async def trigger_remote_service( # Check if service requires a specific url and add all required parameters url = SERVICE_URLS.get(service_id, REMOTE_SERVICE_URL) - url = url.format(vin=self._vehicle.vin, service_type=service_id.value) + + remote_service_headers = {"content-type": "application/json"} + if "{vin}" not in url: + remote_service_headers["bmw-vin"] = self._vehicle.vin + + url = url.format(vin=self._vehicle.vin, service_type=service_id.value, gcid=self._account.gcid) # Trigger service and get event id async with MyBMWClient(self._account.config, brand=self._vehicle.brand) as client: response = await client.post( url, - headers={"content-type": "application/json"} if data else None, + headers=remote_service_headers, params=params, - content=json.dumps(data, cls=MyBMWJSONEncoder) if data else None, + content=json.dumps(data or {}, cls=MyBMWJSONEncoder), ) event_id = response.json().get("eventId") if response.content else None @@ -299,8 +304,10 @@ async def trigger_send_poi(self, poi: Union[PointOfInterest, Dict]) -> RemoteSer return await self.trigger_remote_service( Services.SEND_POI, data={ - "location": poi, - "vin": self._vehicle.vin, + "places": [poi], + "vehicleInformation": { + "vin": self._vehicle.vin, + }, }, ) diff --git a/bimmer_connected/vehicle/vehicle.py b/bimmer_connected/vehicle/vehicle.py index 62841c3b..f6a8cff5 100644 --- a/bimmer_connected/vehicle/vehicle.py +++ b/bimmer_connected/vehicle/vehicle.py @@ -41,7 +41,7 @@ class VehicleViewDirection(StrEnum): This is used to get a rendered image of the vehicle. """ - FRONTSIDE = "AngleSideViewForty" + FRONTSIDE = "VehicleStatus" FRONT = "FrontView" # REARSIDE = 'REARSIDE' # REAR = 'REAR'