diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index ac3120de..befbc1b2 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -73,6 +73,7 @@ body:
options:
- label: I have 2 or more cars linked to the MyBMW account.
- label: I have a Mini vehicle linked to my account.
+ - label: I have a Toyota Supra vehicle linked to my account.
- type: textarea
attributes:
label: Output of bimmer_connected fingerprint
diff --git a/README.rst b/README.rst
index 2c8ff5d7..6ee08580 100644
--- a/README.rst
+++ b/README.rst
@@ -14,19 +14,19 @@ bimmer_connected
.. image:: https://static.pepy.tech/badge/bimmer_connected/month
:target: https://pepy.tech/project/bimmer-connected/month
.. image:: https://static.pepy.tech/badge/bimmer_connected
- :target: https://pepy.tech/project/bimmer-connected
-
+ :target: https://pepy.tech/project/bimmer-connected
-This is a simple library to query and control the status of your BMW or Mini vehicle from
+
+This is a simple library to query and control the status of your BMW, Mini, or Toyota Supra vehicle from
the MyBMW portal.
Installation
============
-:code:`bimmer_connected` is tested against **Python 3.8 or above**. Just install the latest release from `PyPI `_
-using :code:`pip3 install --upgrade bimmer_connected`.
+:code:`bimmer_connected` is tested against **Python 3.8 or above**. Just install the latest release from `PyPI `_
+using :code:`pip3 install --upgrade bimmer_connected`.
-Alternatively, clone the project and execute :code:`pip install -e .` to install the current
+Alternatively, clone the project and execute :code:`pip install -e .` to install the current
:code:`master` branch.
.. note::
@@ -40,7 +40,7 @@ After installation, execute :code:`bimmerconnected` from command line for usage
or see the full `CLI documentation `_.
Please be aware that :code:`bimmer_connected` is an :code:`async` library when using it in Python code.
-The description of the :code:`modules` can be found in the `module documentation
+The description of the :code:`modules` can be found in the `module documentation
`_.
Example in an :code:`asyncio` event loop
@@ -56,13 +56,13 @@ Example in an :code:`asyncio` event loop
await account.get_vehicles()
vehicle = account.get_vehicle(VIN)
print(vehicle.brand, vehicle.name, vehicle.vin)
-
+
result = await vehicle.remote_services.trigger_remote_light_flash()
print(result.state)
asyncio.run(main())
-
+
Example in non-async code
^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -77,25 +77,25 @@ Example in non-async code
asyncio.run(account.get_vehicles())
vehicle = account.get_vehicle(VIN)
print(vehicle.brand, vehicle.name, vehicle.vin)
-
+
result = asyncio.run(vehicle.remote_services.trigger_remote_light_flash())
print(result.state)
Compatibility
=============
-This works with BMW (and Mini) vehicles with a MyBMW account.
+This works with BMW/Mini/Toyota Supra vehicles with a MyBMW account.
So far it is tested on vehicles with a 'MGU', 'NBTEvo', 'EntryEvo', 'NBT', or 'EntryNav'
-navigation system. If you have any trouble with other navigation systems, please create
+navigation system. If you have any trouble with other navigation systems, please create
an issue with your server responses (see next section).
-To use this library, your BMW (or Mini) must have the remote services enabled for your vehicle.
-You might need to book this in the MyBMW/Mini Connected portal and this might cost
-some money. In addition to that you need to enable the Remote Services in your infotainment
+To use this library, your BMW/Mini/Toyota Supra must have the remote services enabled for your vehicle.
+You might need to book this in the MyBMW/Mini Connected/Supra Connect portal and this might cost
+some money. In addition to that you need to enable the Remote Services in your infotainment
system in the vehicle.
Different models of vehicles and infotainment systems result in different types of attributes
-provided by the server. So the experience with the library will certainly vary across the different
+provided by the server. So the experience with the library will certainly vary across the different
vehicle models.
Data Contributions
@@ -116,7 +116,7 @@ If you want to contribute your data, perform the following steps:
bimmerconnected fingerprint
This will create a set of log files in the "vehicle_fingerprint" folder.
-Before sending the data to anyone please **check for any personal data** such as **dealer name** or **country**.
+Before sending the data to anyone please **check for any personal data** such as **dealer name** or **country**.
The following attributes are by default replaced with anonymized values:
@@ -129,9 +129,9 @@ Create a new
`fingerprint data contribution `_
and add the files as attachment to the discussion.
-Please add your model and year to the title of the issue, to make it easier to organize.
-If you know the "chassis code" of your car, you can include that too. (For example,
-googling "2017 BMW X5" will show a Wikipedia article entitled "BMW X5 (F15)". F15 is
+Please add your model and year to the title of the issue, to make it easier to organize.
+If you know the "chassis code" of your car, you can include that too. (For example,
+googling "2017 BMW X5" will show a Wikipedia article entitled "BMW X5 (F15)". F15 is
therefore the chassis code of the car.)
@@ -142,7 +142,7 @@ let us know in advance.
Code Contributions
==================
-Contributions are welcome! Please make sure that your code passes the checks in :code:`.github/workflows/test.yml`.
+Contributions are welcome! Please make sure that your code passes the checks in :code:`.github/workflows/test.yml`.
We currently test against :code:`flake8`, :code:`pylint` and our own :code:`pytest` suite.
And please add tests where it makes sense. The more the better.
diff --git a/bimmer_connected/account.py b/bimmer_connected/account.py
index 02805901..7c715b08 100644
--- a/bimmer_connected/account.py
+++ b/bimmer_connected/account.py
@@ -86,6 +86,11 @@ async def _init_vehicles(self) -> None:
)
vehicle_profile = vehicle_profile_response.json()
+ # Special handling for DRITTKUNDE (third party customer) aka Toyota Supra.
+ # Requires TOYOTA in request, but returns DRITTKUNDE in response.
+ if brand == CarBrands.TOYOTA:
+ vehicle_profile["brand"] = CarBrands.TOYOTA.value.upper()
+
vehicle_base = dict(
{ATTR_ATTRIBUTES: {k: v for k, v in vehicle_profile.items() if k != "vin"}},
**{"vin": vehicle_profile["vin"]}
diff --git a/bimmer_connected/const.py b/bimmer_connected/const.py
index c88d4d91..cfc2c5dd 100644
--- a/bimmer_connected/const.py
+++ b/bimmer_connected/const.py
@@ -16,6 +16,7 @@ def _missing_(cls, value):
BMW = "bmw"
MINI = "mini"
+ TOYOTA = "toyota"
class Regions(str, Enum):
diff --git a/bimmer_connected/tests/__init__.py b/bimmer_connected/tests/__init__.py
index d7fe4dd1..b7b158f4 100644
--- a/bimmer_connected/tests/__init__.py
+++ b/bimmer_connected/tests/__init__.py
@@ -22,6 +22,7 @@
VIN_I01_NOREX = "WBY000000NOREXI01"
VIN_I01_REX = "WBY00000000REXI01"
VIN_I20 = "WBA00000000DEMO01"
+VIN_J29 = "WZ100000000000J29"
ALL_VEHICLES: Dict[str, Dict] = {brand.value: {} for brand in CarBrands}
ALL_PROFILES: Dict[str, Dict] = {}
diff --git a/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v4_vehicles_state_WZ100000000000J29.json b/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v4_vehicles_state_WZ100000000000J29.json
new file mode 100644
index 00000000..2169f483
--- /dev/null
+++ b/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v4_vehicles_state_WZ100000000000J29.json
@@ -0,0 +1,104 @@
+{
+ "capabilities": {
+ "a4aType": "BLUETOOTH",
+ "climateFunction": "VENTILATION",
+ "climateNow": true,
+ "climateTimerTrigger": "DEPARTURE_TIMER",
+ "digitalKey": {
+ "bookedServicePackage": "NONE",
+ "isDigitalKeyFirstSupported": false,
+ "state": "NOT_AVAILABLE",
+ "vehicleSoftwareUpgradeRequired": false
+ },
+ "horn": true,
+ "isBmwChargingSupported": false,
+ "isCarSharingSupported": false,
+ "isChargeNowForBusinessSupported": false,
+ "isChargingHistorySupported": false,
+ "isChargingHospitalityEnabled": false,
+ "isChargingLoudnessEnabled": false,
+ "isChargingPlanSupported": false,
+ "isChargingPowerLimitEnabled": false,
+ "isChargingSettingsEnabled": false,
+ "isChargingTargetSocEnabled": false,
+ "isClimateTimerSupported": true,
+ "isClimateTimerWeeklyActive": true,
+ "isCustomerEsimSupported": false,
+ "isDCSContractManagementSupported": false,
+ "isDataPrivacyEnabled": false,
+ "isEasyChargeEnabled": false,
+ "isEvGoChargingSupported": false,
+ "isMiniChargingSupported": false,
+ "isNonLscFeatureEnabled": false,
+ "isOptimizedChargingSupported": false,
+ "isPersonalPictureUploadSupported": false,
+ "isPlugAndChargeSupported": false,
+ "isRemoteEngineStartSupported": false,
+ "isRemoteHistoryDeletionSupported": false,
+ "isRemoteHistorySupported": true,
+ "isRemoteParkingSupported": false,
+ "isRemoteServicesActivationRequired": false,
+ "isRemoteServicesBookingRequired": false,
+ "isScanAndChargeSupported": false,
+ "isSustainabilityAccumulatedViewEnabled": false,
+ "isSustainabilitySupported": false,
+ "isWifiHotspotServiceSupported": false,
+ "lights": true,
+ "lock": true,
+ "remoteChargingCommands": {},
+ "sendPoi": true,
+ "specialThemeSupport": [],
+ "unlock": true,
+ "vehicleFinder": true,
+ "vehicleStateSource": "NOT_SUPPORTED"
+ },
+ "state": {
+ "checkControlMessages": [
+ {
+ "severity": "LOW",
+ "type": "ENGINE_OIL"
+ }
+ ],
+ "climateTimers": [
+ {
+ "departureTime": {
+ "hour": 7,
+ "minute": 0
+ },
+ "isWeeklyTimer": false,
+ "timerAction": "DEACTIVATE",
+ "timerWeekDays": []
+ },
+ {
+ "departureTime": {
+ "hour": 7,
+ "minute": 0
+ },
+ "isWeeklyTimer": true,
+ "timerAction": "DEACTIVATE",
+ "timerWeekDays": [
+ "MONDAY"
+ ]
+ },
+ {
+ "departureTime": {
+ "hour": 7,
+ "minute": 0
+ },
+ "isWeeklyTimer": true,
+ "timerAction": "DEACTIVATE",
+ "timerWeekDays": [
+ "MONDAY"
+ ]
+ }
+ ],
+ "driverPreferences": {
+ "lscPrivacyMode": "OFF"
+ },
+ "isLeftSteering": true,
+ "isLscSupported": false,
+ "lastFetched": "2023-09-14T03:27:47.176Z",
+ "lastUpdatedAt": "0001-01-01T00:00:00Z",
+ "requiredServices": []
+ }
+}
\ No newline at end of file
diff --git a/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v5_vehicle-data_profile_WZ100000000000J29.json b/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v5_vehicle-data_profile_WZ100000000000J29.json
new file mode 100644
index 00000000..d4f9fff7
--- /dev/null
+++ b/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v5_vehicle-data_profile_WZ100000000000J29.json
@@ -0,0 +1,36 @@
+{
+ "bodyType": "J29",
+ "brand": "DRITTKUNDE",
+ "color": 4281545523,
+ "countryOfOrigin": "US",
+ "driveTrain": "COMBUSTION",
+ "driverGuideInfo": {
+ "androidAppScheme": "com.bmwgroup.driversguide.usa",
+ "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.usa",
+ "iosAppScheme": "bmwdriversguide:///open",
+ "iosStoreUrl": "https://apps.apple.com/us/app/id714042570?mt=8"
+ },
+ "headUnitRaw": "NBTEVO",
+ "headUnitType": "NBT_EVO",
+ "hmiVersion": "ID5",
+ "model": "SPX 40i",
+ "softwareVersionCurrent": {
+ "iStep": 555,
+ "puStep": {
+ "month": 7,
+ "year": 19
+ },
+ "seriesCluster": "S18T"
+ },
+ "softwareVersionExFactory": {
+ "iStep": 560,
+ "puStep": {
+ "month": 3,
+ "year": 19
+ },
+ "seriesCluster": "S18T"
+ },
+ "telematicsUnit": "ATM1",
+ "vin": "WZ100000000000J29",
+ "year": 2019
+}
\ No newline at end of file
diff --git a/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v5_vehicle-list.json b/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v5_vehicle-list.json
new file mode 100644
index 00000000..227ddf17
--- /dev/null
+++ b/bimmer_connected/tests/responses/J29/toyota-eadrax-vcs_v5_vehicle-list.json
@@ -0,0 +1,13 @@
+{
+ "gcid": "ceb64158-d2ca-47e9-9ee6-cbffb881434e",
+ "mappingInfos": [
+ {
+ "isAssociated": false,
+ "isLmmEnabled": false,
+ "isPrimaryUser": true,
+ "mappingStatus": "CONFIRMED",
+ "vehicleMappingType": "CONNECTED",
+ "vin": "WZ100000000000J29"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/bimmer_connected/tests/responses/toyota-eadrax-vcs_v5_vehicle-list.json b/bimmer_connected/tests/responses/toyota-eadrax-vcs_v5_vehicle-list.json
new file mode 100644
index 00000000..0c546925
--- /dev/null
+++ b/bimmer_connected/tests/responses/toyota-eadrax-vcs_v5_vehicle-list.json
@@ -0,0 +1,4 @@
+{
+ "gcid": "ceb64158-d2ca-47e9-9ee6-cbffb881434e",
+ "mappingInfos": []
+}
\ No newline at end of file
diff --git a/bimmer_connected/tests/test_account.py b/bimmer_connected/tests/test_account.py
index c5067ebc..a040b97e 100644
--- a/bimmer_connected/tests/test_account.py
+++ b/bimmer_connected/tests/test_account.py
@@ -13,7 +13,7 @@
from bimmer_connected.api.authentication import MyBMWAuthentication, MyBMWLoginRetry
from bimmer_connected.api.client import MyBMWClient
from bimmer_connected.api.regions import get_region_from_name
-from bimmer_connected.const import ATTR_CAPABILITIES, VEHICLES_URL
+from bimmer_connected.const import ATTR_CAPABILITIES, VEHICLES_URL, CarBrands
from bimmer_connected.models import GPSPosition, MyBMWAPIError, MyBMWAuthError, MyBMWQuotaError
from . import (
@@ -448,8 +448,10 @@ async def test_429_retry_ok_vehicles(caplog, bmw_fixture: respx.Router):
side_effect=[
httpx.Response(429, json=json_429),
httpx.Response(429, json=json_429),
- httpx.Response(200, json=load_response(RESPONSE_DIR / "bmw-eadrax-vcs_v5_vehicle-list.json")),
- httpx.Response(200, json=load_response(RESPONSE_DIR / "mini-eadrax-vcs_v5_vehicle-list.json")),
+ *[
+ httpx.Response(200, json=load_response(RESPONSE_DIR / f"{brand.value}-eadrax-vcs_v5_vehicle-list.json"))
+ for brand in CarBrands
+ ],
]
)
caplog.set_level(logging.DEBUG)
@@ -498,8 +500,10 @@ async def test_429_retry_with_login_ok_vehicles(bmw_fixture: respx.Router):
side_effect=[
httpx.Response(429, json=json_429),
httpx.Response(429, json=json_429),
- httpx.Response(200, json=load_response(RESPONSE_DIR / "bmw-eadrax-vcs_v5_vehicle-list.json")),
- httpx.Response(200, json=load_response(RESPONSE_DIR / "mini-eadrax-vcs_v5_vehicle-list.json")),
+ *[
+ httpx.Response(200, json=load_response(RESPONSE_DIR / f"{brand.value}-eadrax-vcs_v5_vehicle-list.json"))
+ for brand in CarBrands
+ ],
]
)
diff --git a/bimmer_connected/tests/test_vehicle.py b/bimmer_connected/tests/test_vehicle.py
index 53af19b8..2280b90d 100644
--- a/bimmer_connected/tests/test_vehicle.py
+++ b/bimmer_connected/tests/test_vehicle.py
@@ -18,6 +18,7 @@
VIN_I01_NOREX,
VIN_I01_REX,
VIN_I20,
+ VIN_J29,
get_deprecation_warning_count,
)
from .conftest import prepare_account_with_vehicles
@@ -94,6 +95,7 @@ async def test_drive_train_attributes(caplog, bmw_fixture: respx.Router):
VIN_I01_NOREX: (False, True),
VIN_I01_REX: (True, True),
VIN_I20: (False, True),
+ VIN_J29: (True, False),
}
for vehicle in account.vehicles:
diff --git a/bimmer_connected/tests/test_vehicle_status.py b/bimmer_connected/tests/test_vehicle_status.py
index d600e5f3..bc3619e5 100644
--- a/bimmer_connected/tests/test_vehicle_status.py
+++ b/bimmer_connected/tests/test_vehicle_status.py
@@ -25,6 +25,7 @@
VIN_I01_NOREX,
VIN_I01_REX,
VIN_I20,
+ VIN_J29,
get_deprecation_warning_count,
)
from .conftest import prepare_account_with_vehicles
@@ -69,9 +70,16 @@ async def test_generic_error_handling(caplog, bmw_fixture: respx.Router):
@pytest.mark.asyncio
-async def test_range_combustion_no_info(caplog, bmw_fixture: respx.Router):
+@pytest.mark.parametrize(
+ ("vin"),
+ [
+ (VIN_F31),
+ (VIN_J29),
+ ],
+)
+async def test_range_combustion_no_info(caplog, bmw_fixture: respx.Router, vin: str):
"""Test if the parsing of very old vehicles."""
- vehicle = (await prepare_account_with_vehicles()).get_vehicle(VIN_F31)
+ vehicle = (await prepare_account_with_vehicles()).get_vehicle(vin)
status = vehicle.fuel_and_battery
assert status.remaining_fuel == (None, None)