# BMW CarData Binding
This binding integrates BMW and MINI vehicles via BMW CarData, the official service of the BMW Group that provides the telematic data of a vehicle to its owner.
The binding receives the data in two ways:
- Streaming: The CarData streaming service pushes the data of all vehicles of the account as soon as a vehicle reports it. This is the preferred way to keep the data up to date.
- API requests: The CarData API is read periodically and after stream outages for the current values of all channels. The API allows 50 requests per day for all vehicles of an account, so the binding uses it sparingly, see API Requests.
BMW CarData provides read-only access, the vehicles cannot be controlled with this binding. BMW CarData is currently only available in some regions; check the CarData section of the MyBMW or MINI customer portal of your country.
# Supported Things
account: Bridge representing the BMW CarData client of a customer account.vehicle: A vehicle mapped to the account, identified by its VIN.
# Prerequisites
Before the binding can be used, a CarData client has to be created in the customer portal:
- Log in to the MyBMW or MINI customer portal and open the vehicle overview.
- Open the BMW CarData section and create a client ID.
- Subscribe the client to both CarData API and CarData Streaming. Request the access to the CarData API explicitly (German portal: "Zugang zur CarData API beantragen"); enabling streaming alone is not sufficient. Enable one subscription at a time and check after a minute that both are still active, as the portal may reset them.
- In the streaming configuration of the portal, select the data that shall be streamed. Only the data selected there is pushed by the stream.
If the account goes OFFLINE (CONFIGURATION_ERROR) because BMW does not accept the client ID, check the client ID and that both subscriptions are active.
The binding then stops trying; changing the configuration, or disabling and enabling the account, starts a new authorization.
The DEBUG log shows BMW's error, e.g. invalid_request.
# Discovery
After the account has been authorized, the vehicles mapped to the account as primary user are discovered.
A discovery scan costs one request of the daily API quota, so it only runs automatically after authorization while the account has no vehicle Things.
Discovered vehicles are labeled with their brand and model, which costs one more request per vehicle once; a vehicle Thing created from the result reuses that data.
Known brands are shown with their usual name, e.g. BMW_I as BMW; the brand property keeps the value reported by BMW.
If BMW explicitly reports a vehicle as not telematics capable, its label says so.
BMW does not document this flag, so such a vehicle is still offered: it may not send any data.
Further scans can be started manually from the Inbox.
# Thing Configuration
# account Bridge Configuration
| Name | Type | Description | Default | Required | Advanced |
|---|---|---|---|---|---|
| clientId | text | The client ID created in the CarData section of the portal | N/A | yes | no |
| streaming | boolean | Receive the data pushed by the CarData streaming service | true | no | no |
| streamMode | text | direct connects to BMW, bridge receives the stream from the bmw-mqtt-bridge | direct | no | no |
| mirrorToBroker | boolean | Republish the direct stream to the local MQTT broker, see Mirroring the Stream | false | no | yes |
| mirrorRetain | boolean | Publish the mirrored stream messages retained, see Mirroring the Stream | false | no | yes |
| brokerHost | text | Host of the local MQTT broker, required in bridge mode and for mirroring | N/A | no | yes |
| brokerPort | integer | Port of the local MQTT broker | 1883 | no | yes |
| brokerSecure | boolean | Connect to the local MQTT broker with TLS | false | no | yes |
| brokerUsername | text | User name for the local MQTT broker | N/A | no | yes |
| brokerPassword | text | Password for the local MQTT broker | N/A | no | yes |
| topicPrefix | text | Prefix of the bridge topics (LOCAL_PREFIX of the bridge) | bmw/ | no | yes |
| refreshInterval | integer | Hours between refreshes of all vehicle data via the API, 0 disables them | 6 | no | no |
| requestBudget | integer | Maximum number of API requests in 24 hours (1 to 50) | 40 | no | yes |
The broker parameters are only used in bridge mode and for mirroring.
# Stream Modes
BMW allows only one connection to the CarData stream per account. A second connection with the same account, e.g. from another smart home system, MQTT Explorer or evcc, disconnects the first one, so both keep reconnecting.
- Direct (
streamMode=direct): The binding connects to the BMW streaming service itself. This needs no additional software, but no other tool can use the stream of the account at the same time. - Bridge (
streamMode=bridge): The bmw-mqtt-bridge (opens new window) holds the connection to BMW and republishes the unmodified stream messages to a local MQTT broker, on<prefix>raw/<VIN>, and its connection state on<prefix>status. The binding subscribes to these topics and does not connect to the BMW streaming service, but still uses the CarData API with its own authorization. Other tools, e.g. evcc, can read the same topics, so all of them share the single stream connection.
# Mirroring the Stream
In direct mode, openHAB holds the only stream connection of the account.
With mirrorToBroker=true, the binding republishes the stream to the local MQTT broker configured by the broker parameters, so that other tools, e.g. evcc, can use it without running the bmw-mqtt-bridge.
The topics are those of the bmw-mqtt-bridge, so existing consumers of the bridge, and this binding in bridge mode, can read the mirror unchanged:
| Topic | Payload | Retained |
|---|---|---|
<prefix>raw/<VIN> | Every stream message, unmodified as received from BMW | if mirrorRetain |
<prefix>status | State of the BMW stream connection: {"connected":true,"timestamp":<seconds since epoch>} | yes |
Like the bmw-mqtt-bridge with its default MQTT_RETAIN=0, the stream messages are not retained by default.
With mirrorRetain=true, a consumer receives the last message of each vehicle as soon as it subscribes, however old it is, e.g. after the vehicle has been parked for days.
The last will of the mirror sets <prefix>status to {"connected":false}, so consumers notice when openHAB stops.
Like the bmw-mqtt-bridge with its default STATUS_STABLE_DELAY, a disconnect from BMW is only published once it lasts for five seconds, so short drops of the connection do not let the state flap.
When openHAB stops the stream on purpose, e.g. for a new authorization, the disconnect is published right away.
After openHAB starts, the state is published once the state of the stream is known.
The mirror reconnects to the local broker on its own; while the broker is not reachable, messages are dropped and the status description of the account shows this, but the stream and the channels are not affected.
Mirroring is ignored in bridge mode, as the bridge already publishes the stream.
While openHAB is down or restarting, consumers of the mirror receive no data. If other tools need the stream independent of openHAB, run the bmw-mqtt-bridge and use bridge mode instead.
# API Requests
BMW allows 50 requests to the CarData API per account and day, reset at midnight UTC, shared by all vehicles and all tools using the account (see BMW's additional information on the CarData API (opens new window)). The stream is not affected by this limit, so the binding uses the API only to complete the stream data:
- When a vehicle goes
ONLINEafter its start, after the start of the account or after a new authorization, its current values are read once, one request. Later status changes, e.g. when the account was offline for a moment, do not read it again; if the request budget is used up at that time, the vehicle is read at the next periodic refresh. - Every
refreshIntervalhours, the current values of all vehicles are read, one request per vehicle. - When the stream was interrupted for five minutes or more, the values are read again after the reconnect, as the stream does not replay missed values. This catch-up runs at most every 30 minutes.
- When a read fails on BMW's side (e.g. HTTP 502) or times out, it is repeated once after 15 minutes for the affected vehicles.
While the stream is connected, such a failure does not set the vehicle
OFFLINE, as the stream keeps its values up to date. - The static vehicle data shown as Thing properties is read once per vehicle, and the binding sets up its own telematic data container named
openHABon first use, which costs a few requests. A vehicle goesONLINEonce its static data is known; if it cannot be read, e.g. while the request budget is used up, the vehicle staysOFFLINEand the binding tries again after 15 minutes. If BMW no longer accepts the container, e.g. because it was deleted, the binding sets up a new one at the next read. The same happens once after an update of the binding that reads other data points. BMW allows 10 containers per customer; if all are used by other tools, the vehicles report this, and one has to be deleted. - The vehicle image is read once per vehicle, and only when an item is linked to the
status#imagechannel. Like the static data, it is kept, so it costs no further requests after a restart; if it cannot be read, e.g. while the request budget is used up, the binding tries again after 15 minutes. It does not retry failures that do not pass by themselves, e.g. when BMW has no image for the vehicle. - If BMW does not provide the data of a vehicle to the client, e.g. because it is not mapped to the account as primary user, the vehicle goes
OFFLINE (CONFIGURATION_ERROR)and is no longer read; disable and enable the Thing once this is fixed.
The binding counts its requests over a rolling 24 hours per client ID, also across restarts and when the account thing is recreated, and sends at most requestBudget requests.
Counting over a rolling 24 hours is stricter than BMW's daily reset, so the binding slows down before BMW's limit is reached and does not use up the whole quota right after midnight UTC.
When the budget is used up, or BMW reports its limit as reached, the binding pauses its requests and shows this in the status description of the account; the stream keeps working.
Keep a reserve below 50 if other tools use the same account.
# Authorization
The binding is authorized with the OAuth device flow:
- Add the
accountbridge with the client ID. - The bridge goes
OFFLINE (AUTHORIZATION_PENDING)and its status description links to the authorization page athttp(s)://<your openHAB>:<port>/bmwcardata. - The page shows, for every account waiting for authorization, a button to the BMW portal, the same link as a QR code to scan with your phone, and the code to confirm.
- Log in with your BMW or MINI account and confirm the code. The BMW portal opens in the tab of the page, so you can close it once you have confirmed, and you are back where you opened the page.
- The bridge goes
ONLINEwithin a few seconds.
The page is shown in the language of the browser if the binding has a translation for it, and otherwise in the language of openHAB.
The tokens are stored encrypted by openHAB's OAuth client service and renewed automatically. If it expires or is revoked, the authorization page shows a new link and code.
# vehicle Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|---|---|---|---|---|---|
| vin | text | The 17 character vehicle identification number | N/A | yes | no |
| dynamicChannels | boolean | Create channels for received data without predefined channel | true | no | yes |
# Channels
All channels are read-only. A channel is only updated if the vehicle reports the corresponding data and, for streaming, if the data has been selected in the customer portal.
When the static vehicle data is known, the channels that do not apply to the vehicle are removed:
- For an electric vehicle (drive train
BEV), the fuel channels, the total range, the engine state and the electric share of a trip. - For a combustion vehicle (drive train
CONV), the charging, high-voltage battery and electric range channels and the electric share of a trip. - The sunroof channels, if BMW reports the vehicle has no sunroof.
The navigation channels are always kept: the location comes from the telematics unit, and BMW's hasNavi flag, shown as the hasNavigation property, is also reported as false for vehicles with a navigation system.
Plug-in hybrids (PHEV) and vehicles with another or no drive train keep all drive related channels.
BMW does not document the values, so the binding only acts on those seen on real vehicles; the reported values are shown as Thing properties.
Removed channels are added again once the vehicle has the corresponding features, e.g. after the VIN was changed to another vehicle; the static data of the new VIN is read for that.
| Channel | Type | Description | Advanced |
|---|---|---|---|
| status#door-lock | Switch | Whether all doors are locked (ON) | no |
| status#door-lock-state | String | Reported lock state of the doors (LOCKED, SECURED, SELECTIVELOCKED, UNLOCKED) | yes |
| status#trunk | Contact | Whether the trunk is open | no |
| status#hood | Contact | Whether the hood is open | no |
| status#trunk-locked | Switch | Whether the trunk is locked | no |
| status#door-front-driver | Contact | Whether the front driver door is open | no |
| status#door-front-passenger | Contact | Whether the front passenger door is open | no |
| status#door-rear-driver | Contact | Whether the rear driver side door is open | no |
| status#door-rear-passenger | Contact | Whether the rear passenger side door is open | no |
| status#window-front-driver | Contact | Whether the front driver window is open, also if partly open | no |
| status#window-front-driver-state | String | Reported state of the front driver window (CLOSED, INTERMEDIATE, OPEN) | yes |
| status#window-front-passenger | Contact | Whether the front passenger window is open, also if partly open | no |
| status#window-front-passenger-state | String | Reported state of the front passenger window (CLOSED, INTERMEDIATE, OPEN) | yes |
| status#window-rear-driver | Contact | Whether the rear driver side window is open, also if partly open | no |
| status#window-rear-driver-state | String | Reported state of the rear driver side window (CLOSED, INTERMEDIATE, OPEN) | yes |
| status#window-rear-passenger | Contact | Whether the rear passenger side window is open, also if partly open | no |
| status#window-rear-passenger-state | String | Reported state of the rear passenger side window (CLOSED, INTERMEDIATE, OPEN) | yes |
| status#rear-window | Contact | Whether the rear window is open | no |
| status#sunroof | Contact | Whether the sunroof is open or tilted | no |
| status#sunroof-state | String | Reported overall state of the sunroof | yes |
| status#moving | Switch | Whether the vehicle is moving | no |
| status#ignition | Switch | Whether the ignition is on | yes |
| status#engine | Switch | Whether the engine is running | no |
| status#deep-sleep | Switch | Whether the deep sleep mode is active | yes |
| status#battery-voltage | Number:ElectricPotential | Voltage of the 12V battery | yes |
| status#battery-soc | Number:Dimensionless | State of charge of the 12V battery | yes |
| status#last-update | DateTime | Time of the most recent data of the vehicle | no |
| status#image | Image | Picture of the vehicle, retrieved only while an item is linked | no |
| range#mileage | Number:Length | Total distance travelled | no |
| range#total | Number:Length | Total remaining range | no |
| range#electric | Number:Length | Remaining electric range | no |
| range#electric-charging-forecast | Number:Length | Predicted electric range during charging | yes |
| range#electric-target | Number:Length | Electric range at the target state of charge | no |
| range#fuel-level | Number:Dimensionless | Fill level of the fuel tank | no |
| range#remaining-fuel | Number:Volume | Amount of fuel in the tank | no |
| charge#soc | Number:Dimensionless | State of charge of the high-voltage battery | no |
| charge#target-soc | Number:Dimensionless | State of charge at which charging stops | no |
| charge#status | String | Charging state | no |
| charge#plugged | Switch | Whether a charging cable is plugged in (see below) | no |
| charge#power | Number:Power | Current charging power | no |
| charge#time-remaining | Number:Time | Estimated time until charging is completed | no |
| charge#ac-current | Number:ElectricCurrent | Current AC charging current | yes |
| charge#ac-voltage | Number:ElectricPotential | Current AC charging voltage | yes |
| charge#ac-limit | Number:ElectricCurrent | Selected limit of the AC charging current | yes |
| charge#ac-limit-min | Number:ElectricCurrent | Lowest limit of the AC charging current that can be selected | yes |
| charge#ac-limit-max | Number:ElectricCurrent | Highest limit of the AC charging current that can be selected | yes |
| charge#phases | String | Number of phases used for charging | yes |
| charge#method | String | Charging method and plug type | yes |
| charge#port | String | Whether a charging plug is connected to the charging port | yes |
| charge#predicted-soc | Number:Dimensionless | Predicted state of charge at the end of charging | yes |
| charge#min-target-soc | Number:Dimensionless | Minimum state of charge requested for smart charging | yes |
| charge#time-to-full | Number:Time | Estimated time until the battery is fully charged | yes |
| charge#mode | String | Current charging mode | yes |
| charge#end-reason | String | Reason why the last charging ended | yes |
| charge#last-result | String | Result of the last charging | yes |
| charge#cable-lock | String | Lock state of the charging cable | yes |
| charge#flap | Contact | Whether the charging flap is open | yes |
| charge#flap-locked | Switch | Whether the charging flap is locked | yes |
| battery#capacity | Number:Energy | Size of the high-voltage battery | yes |
| battery#energy-content | Number:Energy | Maximum energy content of the high-voltage battery | yes |
| battery#health | Number | Battery health as state of certified energy (SoCE) | no |
| battery#energy-to-full | Number:Energy | Energy required to fully charge the battery | no |
| climate#comfort-state | String | State of the cabin preconditioning | no |
| climate#progress | Number:Dimensionless | Progress of the active preconditioning | yes |
| climate#remaining-running-time | Number:Time | Remaining running time of the preconditioning | no |
| climate#target-temperature | Number:Temperature | Target cabin temperature of the preconditioning | no |
| climate#rear-defrost | Switch | Whether the rear window heating is active | yes |
| climate#mirror-heating | Switch | Whether the exterior mirror heating is active | yes |
| climate#air-purification | String | State of the cabin air purification | yes |
| climate#auxiliary-activity | String | Activity of the auxiliary climate control | no |
| climate#auxiliary-remaining-time | Number:Time | Remaining running time of the auxiliary climate | no |
| climate#auxiliary-error | String | Reason why the auxiliary climate did not run | yes |
| navigation#gps | Location | Current location of the vehicle | no |
| navigation#heading | Number:Angle | Compass heading of the vehicle | yes |
| navigation#altitude | Number:Length | Altitude of the vehicle above sea level | yes |
| navigation#gps-fix | String | Quality of the GPS position | yes |
| navigation#destination-distance | Number:Length | Distance to the active navigation destination | no |
| navigation#arrival-time | String | Expected arrival time at the destination (hh:mm) | no |
| trip#end-time | DateTime | Time when the last trip ended | no |
| trip#end-mileage | Number:Length | Total mileage at the end of the last trip | yes |
| trip#end-soc | Number:Dimensionless | State of charge at the end of the last trip | no |
| trip#recuperation | Number | Energy recovered during the last trip in kWh/100km | no |
| trip#comfort-energy | Number:Energy | Energy consumed in COMFORT mode during the last trip | yes |
| trip#electric-share | Number:Dimensionless | Share of the distance driven electrically | yes |
| trip#eco-pro-share | Number:Dimensionless | Share of the trip driven in ECO PRO mode | yes |
| trip#eco-pro-plus-share | Number:Dimensionless | Share of the trip driven in ECO PRO+ mode | yes |
| trip#acceleration-rating | Number | Rating of the acceleration behaviour (0-5 stars) | yes |
| trip#anticipation-rating | Number | Rating of the anticipatory driving (0-5 stars) | yes |
| tyres#pressure-front-left | Number:Pressure | Measured pressure of the front left tyre | no |
| tyres#pressure-front-right | Number:Pressure | Measured pressure of the front right tyre | no |
| tyres#pressure-rear-left | Number:Pressure | Measured pressure of the rear left tyre | no |
| tyres#pressure-rear-right | Number:Pressure | Measured pressure of the rear right tyre | no |
| tyres#target-front-left | Number:Pressure | Target pressure of the front left tyre | yes |
| tyres#target-front-right | Number:Pressure | Target pressure of the front right tyre | yes |
| tyres#target-rear-left | Number:Pressure | Target pressure of the rear left tyre | yes |
| tyres#target-rear-right | Number:Pressure | Target pressure of the rear right tyre | yes |
| tyres#temperature-front-left | Number:Temperature | Temperature of the front left tyre | yes |
| tyres#temperature-front-right | Number:Temperature | Temperature of the front right tyre | yes |
| tyres#temperature-rear-left | Number:Temperature | Temperature of the rear left tyre | yes |
| tyres#temperature-rear-right | Number:Temperature | Temperature of the rear right tyre | yes |
| security#alarm | Switch | Whether the anti-theft alarm is going off | no |
| security#arm-status | String | Arming state of the anti-theft alarm system | no |
| security#activation-time | String | Time of the last alarm as reported by the vehicle | yes |
| service#distance | Number:Length | Remaining distance until the next service | no |
| service#inspection-date | DateTime | Date of the next legal inspection | no |
| statistics#grid-energy | Number:Energy | Total energy charged from the grid | yes |
| statistics#fuel-consumed | Number:Volume | Total fuel consumed | yes |
| statistics#avg-consumption | Number | Average electric consumption in kWh/100km | yes |
| statistics#avg-speed | Number:Speed | Average speed | yes |
| statistics#weekly-distance | Number:Length | Average distance driven per week | yes |
| statistics#weekly-distance-long-term | Number:Length | Average distance per week over two months | yes |
The String channels show the values as reported by the vehicle; the door lock state is normalized, e.g. SELECTIVE-LOCKED becomes SELECTIVELOCKED.
status#door-lock is ON for LOCKED and SECURED (locked with the alarm system armed), and OFF for UNLOCKED and SELECTIVELOCKED (all doors locked except the driver door).
status#door-lock, status#trunk-locked and charge#flap-locked are ON when locked, so they can be combined in a group, e.g. Group:Switch:AND(ON,OFF).
Windows and the sunroof are OPEN also when they are only partly open or tilted; the advanced -state channels show the reported state.
Vehicles that do not report the state of charge header or the overall sunroof state, e.g. those of the Neue Klasse, update charge#soc and status#sunroof from the displayed state of charge and the sunroof state.
Not every vehicle reports whether a charging cable is plugged in; until it does, charge#plugged follows the charging port status (CONNECTED or DISCONNECTED).
Values the vehicle reports as unavailable (e.g. INVALID, -NA- or UNKNOWN), and values that cannot be interpreted, set the channel to UNDEF.
# Dynamic Channels
BMW CarData provides several hundred data points, of which only the most common ones have a predefined channel.
If dynamicChannels is enabled, the binding adds a channel for every other data point it receives through the stream.
These data points need to be enabled in the customer portal.
Note that data points that cannot be streamed and are not included in the binding will never become available as dynamic channels, even when your car could provide data.
Dynamic channels will also not get a state update at startup. They will only update from streamed data.
- The channels are created in the channel group
other. - The channel ID is the CarData identifier without the
vehicle.prefix in lower-case-hyphen notation, e.g.vehicle.cabin.sunroof.tiltStatusbecomesother#cabin-sunroof-tilt-status. - The item type is derived from the first received value:
Switchfor booleans,Number(with unit, e.g.Number:Length, if the data has one) for numbers andStringotherwise. - The channel is created when the first value is received, so it does not exist before the vehicle reported the data.
For Things defined in .things files, dynamic channels are not stored and are created again when the data is received after a restart.
# Troubleshooting
With the log level TRACE for org.openhab.binding.bmwcardata, the binding logs every request to BMW, every response and every stream message.
These logs are redacted so they can be shared, e.g. in an issue:
- A VIN is shortened to its first three and last four characters, e.g.
WBA***1234. - Tokens, codes, the client ID, the customer ID (
gcid) and theentityIdare replaced by***. - The values of the location coordinates are replaced by
***.
Other values, like the mileage or state of charge, are logged as received.
# Full Example
# bmwcardata.things Example
Bridge bmwcardata:account:myaccount "BMW CarData" [ clientId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ] {
Thing vehicle i4 "BMW i4" [ vin="WBA00000000000000" ]
}
# bmwcardata.items Example
Number:Length i4_Mileage "Mileage [%d %unit%]" { channel="bmwcardata:vehicle:myaccount:i4:range#mileage" }
Number:Length i4_Range "Range [%d %unit%]" { channel="bmwcardata:vehicle:myaccount:i4:range#electric" }
Number:Dimensionless i4_Soc "State of Charge [%d %%]" { channel="bmwcardata:vehicle:myaccount:i4:charge#soc" }
String i4_Charging "Charging [%s]" { channel="bmwcardata:vehicle:myaccount:i4:charge#status" }
Switch i4_DoorLock "Doors Locked [%s]" { channel="bmwcardata:vehicle:myaccount:i4:status#door-lock" }
Location i4_Location "Location" { channel="bmwcardata:vehicle:myaccount:i4:navigation#gps" }
DateTime i4_Updated "Last Update [%1$tF %1$tR]" { channel="bmwcardata:vehicle:myaccount:i4:status#last-update" }