What's new
Every stable release, newest first. Pre-releases are folded into the stable that follows them. The same text is on each GitHub release.
0.9.14 1 October 2026#
Ten languages, a website, a fix for Check Control warnings that never cleared, and an easier way to add a second car without losing history.
Added#
- Eight more languages: French, Italian, Spanish, Dutch, Polish, Portuguese,
Czech and Swedish. Everything is translated — entity names, the setup and
options screens, services, repair notices, device triggers, the printable
month report and the dashboard card. The names of BMW’s fields are BMW’s own,
taken from the catalogue BMW publishes in each language, so they match the
wording in the BMW portal. Portuguese is European Portuguese. The export
service’s
languagefield accepts the new codes. Entity IDs do not change. - A website: justchr.github.io/BavarianData, in English and German. It runs the real dashboard card on a demo car you can click through, carries the full manual, and has a searchable page for every descriptor BMW CarData can send. The wiki stays the source; the website renders it, because GitHub keeps wikis out of search engines.
- Coming from bimmer_connected: a manual page for everyone whose BMW
Connected Drive integration went
unavailablewhen BMW blocked it on 29 September 2025 — what replaces it, what you lose (remote commands), and which BavarianData entity stands in for each old one.
Changed#
- The card’s Vehicle events view shows only current Check Control
messages. Messages the car no longer reports fold away under Earlier
messages, with the day each was last reported and the day it stopped. The
sensor keeps the last ten in a new
resolvedattribute. - Updated to BMW’s latest data catalogue. The State of health (SOCE) sensor now keeps long-term statistics, so its trend shows in the history graphs. In German, Energieinhalt der Hochvoltbatterie is now called what BMW calls it: Nutzbare Energie aus Hochvoltbatterie (vollgeladen, prognostiziert). The entity ID does not change.
- The integration’s Documentation link in Home Assistant now opens the manual on the website.
- Discover vehicles now adds the cars it finds. It used to fetch BMW’s list of mapped cars and only write their count to the log, so a car this entry had never seen stayed invisible. A primary car that is new to the entry now becomes a device (one request against the day’s quota, once per car). Streaming still has to be switched on for it in the portal, under Configure → Choose streamed data. (#44)
- A second car no longer needs “the dance”. Finishing Configure → Choose streamed data, or re-authorizing the account, now looks for cars the entry has not met (one request, only when one is found), and a car found that way gets its entities from one telematics fetch instead of staying an empty device until it next streams. Before, a parked car stayed invisible until it happened to send something, or until Discover vehicles and Fetch telematics were pressed by hand. (#44)
Fixed#
- A Check Control warning stayed on after it was dealt with. Topping up the washer fluid did not clear “washer fluid level is low”: once the car has no messages, BMW reports the list as null rather than as an empty list, and the integration read that as “no news” and kept the old warning for good. It now reads it as “no messages”, so the sensor drops to 0 at the next daily refresh after a drive.
- The card could show a stopped charge as charging. BMW’s catalogue now lists two more charging states, interrupted and disrupted. Both would have shown a green ring and Time to full on the card. They now read as stopped, and have names in every language.
- A refused login could leave two connections to BMW open. When BMW refused the stream’s login and the token was renewed at once, a busy system could lose track of the new connection and open another one: two streams on one account, which BMW does not allow and which makes the connections drop each other. The refused connection is now shut down before anything else reacts to the refusal.
- Adding the same BMW account again no longer wipes its history. Running Add device with a Client ID that is already set up (for example to pick up a second car) used to delete the existing entry first, and with it every stored trip and charge. The existing entry is now re-authorized in place, keeping its history. (#44)
0.9.13 27 September 2026#
Promotes 0.9.13-beta.1 to beta.6. Two new features: device triggers for automations, and the integration catching up with BMW after a restart or an outage. Also the first round of fixes for plug-in hybrids, from the diagnostics of an X3 30e, and a stream that now recovers from network outages on its own.
Added#
- Device triggers for automations (beta.6). Create an automation, choose
Device, pick your car, and choose what should happen: arrived at a
zone, left a zone, parked and left unlocked (for 10 minutes, or how
long you choose), plugged in but not charging (15 minutes by default),
charging started, charging completed, and charging stopped before
the target with the cable still in. No YAML, no entity names. Only the
triggers your car can fire are listed. They are built to fire once and never
falsely: nothing fires from a restart or from a value Home Assistant
restored, the car unlocking to let you in doesn’t count, a car that is done
charging isn’t “not charging”, and unplugging early isn’t an interruption.
Arrival fires when the drive ends, not when the car crosses the zone edge.
Every trigger hands its details to the automation as
trigger.data(distance, SoC, zone, BMW’s reason for the stop…). The same moments are fired as events for YAML and Node-RED:bavariandata_zone_arrived,bavariandata_zone_left,bavariandata_charging_interruptedandbavariandata_situation. - Catch up with BMW after a restart or an outage (beta.1, beta.2). The live stream only carries what changes while Home Assistant is listening, so whatever the car reported in the meantime (a charge that ended, the doors being locked, a drive) never arrived, and a car charging at steady power can stay silent for hours afterwards. Two minutes after a start, and when the connection to BMW comes back after five minutes or more, the integration now asks BMW once per car for its current state. BMW’s servers return the last value each car sent, so this catches up without waking the car. It costs one of the 50 daily requests per car, is skipped when BMW was asked within the last hour (so restarting repeatedly costs nothing more), and always leaves 10 requests for your own service calls. On by default; switch it off under Configure → Automatic data refresh (Catch up after a restart or an outage).
- A charge running during a restart is settled by that answer instead of guessed at (beta.1). It used to wait for the car, and after 15 minutes of silence it was filed as ended by the restart while the car charged on; a charge that really had ended was only noticed whenever the car next sent anything. Now a running charge carries on as the same session, and a finished one is recorded as ended.
Fixed#
- The stream now recovers on its own after a network outage (beta.2). A DNS outage once left the streams of two BMW accounts disconnected for nine hours, until Home Assistant was restarted, although the network was back after two. A failed reconnect was never tried again, and a token refresh that failed on the network quietly ended token renewal for good. Both now keep retrying (the stream backs off from 10 seconds to 2 minutes, with a random spread), recovery can no longer open a second connection for the same account, and an outage logs one warning and one line when it ends instead of an error for every attempt. Found, fixed and tested by @netbasebe (TomDS). Thank you.
- A BMW hiccup no longer asks you to re-authorize (beta.2). A server error from BMW’s login service during a reconnect was taken for a rejected login. It is now retried. A login BMW really no longer accepts still asks you once, and no longer fills the log with an error every few minutes until you do.
- The tank level in % lost its long-term statistics in 0.9.10 (beta.3, #25). Home Assistant raised the repair “The entity no longer has a state class”. It is back, and the repair clears by itself after updating. Do not delete the statistics: recording resumes on your existing history. Thanks @JohannBlais for the precise diagnosis.
- Percentages without a device class now keep long-term statistics too (beta.3): seat and steering-wheel heating, door and sunroof positions, preconditioning progress, the 48 V battery’s health and the eco-driving shares, 24 sensors in all. Home Assistant starts recording them now, which adds a little to its database.
- Plug-in hybrids were shown a real range far beyond what the battery reaches (beta.4, #25). Consumption is measured by dividing the energy charged by the distance on the odometer, but a hybrid’s odometer also counts the kilometers the engine drove, so the real range read high by the share driven on fuel: roughly double for a car driven half on fuel. A hybrid now gets no measured consumption, real range, consumption trend or charging cost per 100 km, and the efficiency view says why. The Real Range and Charging Cost per 100 km sensors a hybrid was given are removed at the next restart. Battery health, the charging history and its costs are unaffected.
- Plug-in hybrid trips showed an unchanged battery charge (beta.5, #25). The X3 30e never streams its state of charge, so every trip read like “38 → 38 %”. A hybrid’s trip now records an end charge only when one arrived during the drive; otherwise the card shows no charge line for it. Battery cars are unchanged. Trips already stored keep their figures.
- The card kept saying “charging” after a charge had finished (beta.4).
BMW reports a charge that stopped at its target as
chargingended(alsochargingpaused,chargingerror), and the card read anything beginning with “charging” as active. - The card’s Plug tile could show the wrong sensor (beta.4, beta.5). On a car without the “plugged in” sensor, a lock state could win the tile, and on a German install the tile could be missing. The card now looks the plug state up by what it is, and its icon follows whether a cable is in rather than whether the car is charging.
- The card’s ring showed BMW’s last reading while the car charged (beta.3). That reading stands still whenever BMW goes quiet mid-charge, so the ring could sit on 38 % while the car had reached 47 %. It now shows the integration’s estimate, which keeps climbing while the car charges (card 1.15.3 with the fixes above).
- A drive after a restart started one position late, and not at Home (beta.3). The restored position was not used, so the trip began a report later and a drive out of your Home zone was not recognised as starting at Home, which commute detection needs.
- On a fresh install, every position was plotted from mismatched halves (beta.3): zig-zag tracks and inflated distances until the next restart.
- An impossible state-of-charge reading no longer moves the estimate (beta.3). A value below 0 % or above 100 % became the estimate’s anchor and a charging session’s start or end.
- The charging-started event could miss the charge target and the state of
charge (beta.3) when they arrived in the same message as the charging
status. Automations using
target_soconbavariandata_charging_startednow get it.
0.9.12 26 September 2026#
Promotes 0.9.12-beta.1 unchanged, plus one fix for the charging display: the estimated state of charge no longer freezes when Home Assistant restarts in the middle of a charge.
Added#
- British English. Setting Home Assistant’s language to English (UK) now
gives British entity names and card labels — Tyre pressure (front left),
Tyre Condition, Check tyres, Authorisation — while plain English stays
US. Entity IDs are untouched in both: they come from the descriptor BMW sends
(
sensor.…_tire_pressure_front_left), so automations, templates and dashboards keep working whichever you pick. Thanks to @thebertster for spotting it and for #24.
Fixed#
- The estimated state of charge froze after a restart mid-charge. BMW can stay silent for hours while a car charges, which is what the integration’s own estimate is for — it climbs at the rate the charging power implies. After a Home Assistant restart it stopped climbing and sat at its last value until the car next woke up: on the maintainer’s i5 it showed 39 % for two hours while the car reached 47 %. The restored charging status came back in a different letter case and was read as “not charging”. The estimate now keeps climbing across a restart, but only while the charge that was running is still the one on record, and never from before that charge began. Session recording, energy counting and what a charge controller is told still wait for the car’s own word, as before.
- English was a mixture of US and British spellings. Tire pressure (front
left) sat next to Tyre Condition; the charging state read Initialising
and the distance unit Kilometres; the Configure menu offered Fetch tyre
diagnosis while the card’s cluster was Tire data. BMW is the origin — its
descriptor paths are US (
…wheel.left.tire.pressure) while the English titles in the same catalogue export say “tyre”, so both arrived together and four of our own files picked sides independently. English is now consistently US throughout, with British English as a proper language rather than a spelling that leaked through. - Five tyre entities and two enum labels are renamed by the above. Only the display names change — no entity ID, service name or option key moves, so nothing needs adjusting. A German install is unaffected.
0.9.11 20 September 2026#
Promotes the five 0.9.11 betas unchanged: everything below has already been running in the betas. The theme is installs that aren’t one electric BMW in one account — two cars, two accounts, a MINI, a car that runs on petrol — each of which quietly got something wrong, and several of which got nothing at all.
Fixed#
- MINI owners could not select their streamed data at all. The in-browser
activator (and the portal call behind “Choose Streamed Data”) had BMW’s own
brand baked into every API path, so on a MINI portal every request came back
404and nothing was ever selected — the setup appeared to work and then streamed nothing. The segment is now read from the portal’s own address:mini/myminion a MINI site,bmw/mybmwotherwise. The regression arrived in 0.9.10-beta.1, which unified manual and guided setup onto this one activator; the console snippet manual setup handed out before that ticked the portal’s own checkboxes and never called an API at all, which is why a MINI set up before 13 September worked and the same car could not be reconfigured afterwards. The 0.9.1-beta.6 release notes claimed both brands share the BMW path; they do not, and that claim was written with no MINI to test it against. Measured both ways —www.bmw.co.ukanswers401for/utilities/bmw/api/cd/applicationsand404for theminispelling, and a MINI portal does the reverse. Thanks to @thebertster, who diagnosed it down to the four paths (#23, 0.9.11-beta.5). - Guided setup could never finish on a split-horizon network. The activator was told one address to report its result to — whichever Home Assistant prefers, usually the external one — so a browser on the same LAN as Home Assistant, where that external name does not resolve, silently failed to report and the setup sat waiting. It is now handed every https address the instance answers on and tries them in turn, stopping at the first that replies. The clipboard fallback is unchanged, and an http instance still goes straight to the paste screen (#23, 0.9.11-beta.5).
- A petrol car with no fuel data was shown an electric dashboard. The card
decided a car was electric unless it proved otherwise, and a MINI Cooper C
streams neither a high-voltage battery nor any fuel-system or engine field —
so it got a charge ring, a “Not charging” row and charging tiles it could
never fill. Worse, the ring was fed by the trip-end state of charge, a
battery-class percentage that only moves when a drive finishes: a petrol car
displayed a battery level. Such a car now gets a bare overview — remaining
range in the ring, odometer beside it, nothing electric — and the trip-end
value can no longer win the state-of-charge ring on any car. A car that has
simply not said anything yet still keeps the electric layout, and
drivetrain:in the card’s YAML still overrides all of it (0.9.11-beta.5). - With two accounts set up, the card’s Charging, Trips, Map and Efficiency views stayed empty. Those views read their data from services rather than from entity states, and a service call that does not say which account it means is refused as soon as a second one exists — so a household running a BMW and a MINI account side by side got blank views and a log filling with “multiple entries configured; specify entry_id”. The card now reads the account off the vehicle’s own device and names it on every call it makes. Found, diagnosed and fixed by @thebertster (#11, 0.9.11-beta.1).
- With more than one car on an account, only one of them got its daily REST
refresh. Everything BMW cannot stream — Condition Based Servicing, service
demands, charging level, door-lock status, the tyre diagnosis — reaches Home
Assistant only through that refresh, and it covered a single vehicle: whichever
car happened to send the first message after a restart. Every other car kept
the values it had at setup, indefinitely, while its stream ran normally and
hid the gap — one reporter’s i3s sat eight days behind its i4 on exactly those
fields. The refresh now walks every vehicle on the account, one request per
car, and so does a
fetch_telematic_datacall made without avin. Reported with the diagnostics that made it findable by @erwinweiss1955 (#13, 0.9.11-beta.2). - With two accounts set up, fetching basic vehicle data filed the car under the wrong account. The services are registered once, by whichever entry loads first, and this one handler kept writing to that entry instead of the one the call named: a second account’s car had its model and software version stored on the first account’s entry, and its device attached there too. Worse once the first account was removed while the other stayed — the handler still pointed at the deleted entry, so fetching basic data failed outright for the account that was still there (0.9.11-beta.4).
- A car added to the account after setup stayed a bare VIN. Its entities appeared on their own, because it rides the same stream, but everything that names a car — model, series, software version, the device name on the dashboard — comes from a REST call that ran only during first-time setup. Nothing ever revisited it, so a second BMW bought a year in was still listed by its VIN. It is now fetched the first time that car is seen on the stream: one request against that day’s quota, once, and never retried in a loop for a car BMW answers nothing for (0.9.11-beta.4).
- A car added later was declared incomplete on day one. The coverage self-test gives a new install a week before it reports a cluster as silent, but that clock belonged to the config entry — that is, to the day the first car was set up. A car added two years in was past its grace window the moment it arrived and was shown a Repairs warning for clusters it had not had a chance to stream yet. Each vehicle now has its own clock (0.9.11-beta.4).
- Removing the integration could leave a car’s evcc topics retained on the broker. The cleanup walked the stored basic-data record, which a car added after setup need never have appeared in — so a charge controller could go on reading a state of charge frozen at the moment of removal, which is precisely what that cleanup exists to prevent. It now covers every car the entry ever had, down to one that only ever streamed (0.9.11-beta.4).
- Calling a service by hand with two accounts no longer needs the entry id.
A
vinalready identifies the account, so an automation, script or Developer Tools call that passes one is now resolved from it instead of being refused. Namingentry_idexplicitly still wins, and nothing changes for the single account install (0.9.11-beta.1).
Changed#
- A cluster your car doesn’t have stops being reported as a gap. The coverage self-test compares your cluster selection against what has actually arrived, and it had only one exemption: the electric clusters on a petrol car. Everything else stayed “overdue” forever — an older i3, which streams no tyre pressure at all, was told 178 of 224 fields were missing and shown a Repairs warning it could never clear. Now, when a single cluster is still silent after 30 days while every other selected cluster has delivered, the selection has demonstrably saved and the car is simply missing those fields: the cluster is marked not applicable, its fields stop counting as overdue, and the warning clears itself. Two or more silent clusters still warn however long they stay silent — that is the signature of a Data Selection that did not save, which is what the self-test exists to catch (0.9.11-beta.3).
- The daily refresh now costs 2 requests per vehicle instead of 2 per
account — 2 a day for one car, 4 for two, out of BMW’s 50. That is the price
of the multi-car fix above; a one-car install is unaffected. Calling
fetch_telematic_datawith avinstill spends exactly one request (0.9.11-beta.2). - Debug logging is no longer decided by whichever account loaded last. The switch is per config entry but the log level is one logger’s, so with two accounts the last entry to set up — or to have any option saved — silently overruled the other, turning verbose logging off on the account being diagnosed. Logging now stays verbose while any entry has it enabled. It still spans both accounts while it is on; the options screen says so (0.9.11-beta.4).
- A second config entry for an account you already set up is now refused. Generating a second Client ID in the BMW portal for the same account passed the duplicate check and then broke both entries: BMW allows one stream connection per account, so the two evicted each other in a loop, their VIN-keyed entities collided, and each claimed the full 50-request quota that BMW counts once per account. Setup now stops with an explanation. Two different accounts side by side are unaffected — that is supported, and now documented (0.9.11-beta.4).
- Diagnostics now name the car’s drivetrain (
model_name,drive_train,propulsion_type, straight from BMW’s basic data). They decide which overview the card draws and which electric-only entities exist, so without them a wrong layout could not be diagnosed from a dump at all. They are model facts, not identifiers (0.9.11-beta.5). - Diagnostics carry an
account_fingerprint— a short, one-way digest of the account id, which stays redacted. BMW allows one concurrent stream per account, so “are these two config entries the same account?” is the first question when two of them misbehave together; until now redaction made it unanswerable. The digest cannot be reversed and only ever matches another dump of the same account (0.9.11-beta.5).
Documentation#
- New Wiki page Multiple cars & accounts (EN + DE): what one entry covers, adding a car later, quota with several cars, and how to name the car you mean in the card, in services, in automations and in the evcc bridge. Plus two new FAQ entries and the shared-log-level note in the settings reference (0.9.11-beta.4).
0.9.10 16 September 2026#
The first stable release since 0.9.8, promoting twelve betas (0.9.9-beta.1 through 0.9.10-beta.3) unchanged: everything below has already been running in the betas. The theme is energy you can account for — what a charge really cost, where it came from, how far it actually gets you — plus a long batch of fixes for cars that aren’t electric, which until now got an integration built for one that is.
Breaking#
- Petrol and diesel cars lose their electric-car sensors. The state-of-charge estimate and the charged-energy and charging-cost sensors were created for every car, so a combustion car had up to ten of them stuck at unknown. They are now created only once the car sends high-voltage battery data, and on a car that has sent fuel data and never any battery data the existing ones are removed on the first start after updating. If you built an automation or dashboard card on one of them it will break — but it was reading unknown anyway. Their old history may still show under Developer tools → Statistics, where it can be deleted (0.9.10-beta.1).
Added#
- How far the car really goes — measured, not predicted. A new Real Range sensor divides the usable battery capacity by the consumption your own charging history recorded, scaled to the charge in the car right now. It picks the shortest window that can answer — 30 days, then 90, then a year, then everything on file — and publishes which one it used beside the number, because 17.5 kWh/100 km means different things over a month and over a year. Battery-side and grid-side figures are never mixed: feeding it a grid figure would have the car driving on the charging losses (0.9.9-beta.1).
- A new
view: efficiencycard view — the real range, how it compares with the car’s own estimate, the measured consumption with its window and its side of the charger, the measured charging loss, the usable capacity, your cost per 100 km with the month’s solar share, and consumption by calendar month, which is where the seasonal story shows up (0.9.9-beta.1). - Where each charge’s energy came from: PV, house battery, or the grid. Point Configure → Solar & energy sources at your PV and grid power sensors and every session records the split, plus the share that came off your own roof. The card tags the session row ☀ 62 % solar; the month’s share rides on the existing Charging energy this month sensor. Energy is attributed as it is delivered, so a charge that starts in sunshine and ends after dark is split rather than filed under whichever came last — and a session that could attribute nothing records no mix at all, because “we couldn’t tell” must not look like “no sun”. Set Value of own solar per kWh and cost becomes what a charge really cost you (0.9.9-beta.1).
- Measured grid energy and cost, from your own wallbox meter. The wallbox
energy sensor setting has been offered since 0.9.0, and until now nothing read
it. Bind your wallbox’s cumulative energy total and each session records a
measured
grid_kwhbeside the battery-side figure, which the monthly totals, the long-term statistics and the CSV export all prefer. Cost is billed from the meter’s advance as the charge proceeds, so a dynamic tariff prices each kilowatt-hour at the rate in force when it arrived. A reading that cannot be right is refused rather than believed — a meter that went backwards, one reporting less than the pack absorbed, or one reporting nearly twice it, which is what binding the house import meter looks like (0.9.9-beta.5). The meter is read only for charges in your Home zone (0.9.9-beta.8). - evcc / wallbox bridge — hand your charge controller the state of charge, at
no API cost. Configure → evcc / wallbox bridge republishes what we
already know onto your own MQTT broker, and the screen after it hands you the
evcc
customvehicle configuration with your VIN, prefix and pack size filled in. Published underPREFIX/VIN/:soc,status(evcc’s A/B/C),range,odometer,limitSoc,chargePower,plugged,charging,updated, andstateas one JSON document. It publishes through Home Assistant’s own MQTT integration, so there is no host, port or password to enter. Off by default. Because a charge controller acts on what we publish: anything the car doesn’t report is not published rather than sent as a zero, everything is re-sent every five minutes so a parked car’s level stays current, and switching the bridge off removes what it published (0.9.9-beta.5).get_evcc_configreturns the same configuration at any time. No quota. - The card’s Overview fits the car’s drivetrain. A petrol or diesel car got a “Charge” ring and a charge Target tile, because BMW streams an EV charge target even to a petrol M2. The card now works the drivetrain out from what the car streams: combustion gets its tank in the ring and no charging tiles, a plug-in hybrid keeps the charge ring and adds tank and total range, an electric car looks exactly as before. Override it with Drivetrain in the card editor (0.9.9-beta.8). The charging, battery-health and efficiency views now say there is nothing to show for a fuel-only car instead of sitting empty, which looks like a fault (0.9.10-beta.1).
- Check Control messages appear in the card’s Vehicle events view. With “washer fluid level is low” on the car’s display the view still said there were no events — Check Control messages are filed under usage-based data, not under BMW’s Vehicle events cluster. Tap a message for the mileage it was last shown at, how long ago the car sent it, and BMW’s message code (0.9.10-beta.3).
- The fuel in the tank now has long-term statistics. It had no device class, so Home Assistant kept no history for it. It is now a volume sensor in whatever unit the car sends, shown as a whole number (0.9.10-beta.1).
- A BMW motorcycle gets a notice under Settings → Repairs. BMW lists Motorrad bikes in CarData but streams no data for them, so setup went through and the device then sat empty with no explanation (0.9.10-beta.1).
get_efficiencyreturns the whole profile plus the month trend, for templates and automations. Local store only, no quota (0.9.9-beta.1).
Changed#
- Manual setup switches the stream on the same way guided setup does. After you pick your clusters it runs the one-click Activate BMW data bookmarklet instead of handing you a console snippet to paste into the portal. Guided setup, manual setup and Configure → Choose streamed data now use the same screens. Already ticked the fields in the portal yourself? Leave the result box empty and press Submit (0.9.10-beta.1).
- Plug-in hybrid trips no longer show a kWh/100 km figure. A trip’s energy is the drop in battery charge, but a hybrid may have driven part of the distance on fuel, so dividing the two read far too low. The trip keeps its energy; the rate is left blank and stays out of the monthly average (0.9.10-beta.1).
Fixed#
- A restart no longer loses the charge that was running. An in-progress
session lived only in memory, and Home Assistant does not unload config entries
on shutdown, so any restart, update or options reload mid-charge deleted it.
The session is now snapshotted as it charges and picked up again on the way
back in: still charging and it carries on as the same record; already over and
it is filed ending at the last sample actually watched, flagged
interruptedso nothing treats it as a clean measurement. This was not cosmetic — measured on a live instance, two restarts that landed mid-charge cost about 22 kWh in a single week, and every total built on the ledger read low by exactly that much (0.9.9-beta.1). A drive in progress at a restart is now captured too. - The car’s measured state of charge was hidden on every install from before v0.9.6. That release enabled the HV battery state of charge entity by default, but Home Assistant decides an entity’s enabled state only once, when it is first registered — so every existing install kept it disabled and nothing ever revisited it. No recorded data was affected, but the measured figure was invisible next to the estimate. The integration now re-enables any entity it disabled whose default has since been turned on; entities you disabled yourself are never touched. Expect one extra reload about 30 seconds after the first start on this version (0.9.9-beta.8).
- A charge away from home could be booked with another car’s energy from your wallbox. The meter was read for every charge wherever the car was plugged in, so a charge at work while someone else used your wallbox would have taken that meter’s advance as this car’s measured grid energy and cost (0.9.9-beta.8).
- The “stream data has never arrived” repair appeared on every car. BMW publishes one catalogue for its whole fleet, so no car sends every field — the maintainer’s i5 was warned about 154 “missing” fields on a perfectly healthy stream. The warning now appears only when a selected cluster has sent nothing at all for 7 days, which is what a Data Selection that didn’t save looks like. An existing warning clears itself on the next check (0.9.9-beta.8).
- The main card showed the wrong range, and the state-of-charge ring could show the fuel tank or the 12 V battery. Three entities answer to “electric range” and which one won came down to registration order — the usual winner was BMW’s estimate during charging, which read 128 km on a car sitting at 86 % with a real 379. The card now names the figures it wants and rejects the impostors (0.9.9-beta.3, 0.9.9-beta.8). The charge ring no longer reads ”—” when the measured entity has not reported yet (0.9.9-beta.9).
- Condition Based Service and Check Control messages now really show their
data. BMW sends both as a list, as text, and Home Assistant cannot store a
state that long — so it logged an error on every start and showed unknown.
The state is now the number of entries, so “a Check Control message
appeared” works in an automation, and the full list is in the
itemsattribute. Installs that already stored the rejected text fix themselves on the first start after updating (0.9.9-beta.9, really fixed in 0.9.10-beta.1). - Distances are whole kilometres again — Home Assistant stamps two decimals on any sensor whose unit it can convert, so the card’s headline read “379.00 km” (0.9.9-beta.4). Tyre pressures likewise show “260 kPa” rather than “260,00 kPa”; a display precision you set yourself is kept (0.9.10-beta.1).
- The card wrote its own figures with a decimal point on a German dashboard. Consumption, charged energy, capacity, trip distances and costs that the card works out itself read “19.8” right next to Home Assistant’s “110,10 kWh”. They now follow your profile’s language and Number format setting (0.9.10-beta.1).
- The Real Range sensor no longer freezes at what it knew when Home Assistant started. The two figures it is measured against arrive as ordinary stream messages, and a restart delivers them with no stream message at all (0.9.9-beta.2, 0.9.9-beta.3).
- The evcc bridge published no plug state for a BMW i5. It read only two of the descriptors cars use to report “is a cable in”, neither of which an i5 streams. The source was checked against ten days of recorder history — and 27 trips — before trusting a charge controller to act on it (0.9.9-beta.7).
- A genuine wallbox reading could have been refused on a small charge. The cross-check vetoed a meter delta below 90 % of our battery-side figure, but against a real wallbox over twelve sessions the meter read a median 0.989 of ours — below it, which the grid cannot do, so it is our figure running a little high. It now allows for our own error in absolute terms (0.9.9-beta.7).
- The monthly driving-distance and cost-per-100 km sensors now appear on cars
that report
travelledDistance— the i5 streams that spelling of the odometer and neither of the two the sensors were gated on (0.9.9-beta.1). - The car’s own remaining range is a properly classified sensor. BMW ships
kombiRemainingElectricRangewith an empty unit, so it arrived with no unit, no device class and no statistics (0.9.9-beta.4). - Fetching BMW’s charging history repairs charges recorded before v0.9.6. Until v0.9.6 the state of charge never reached the stream, so those charges were stored flat — “38 → 38 %” and about 1.4 kWh. The import now takes BMW’s start and end level where BMW recorded a rise of at least 2 percentage points for a charge stored as flat. The battery-side kWh and the solar share came from the same wrong figure, so they are removed rather than kept. To repair: run Fetch charging history once with From set before your first affected charge — without it, only the last 30 days are fetched. Thanks to @karpilin for asking (#6) (0.9.10-beta.2).
- The card editor no longer offers the CarData Debug Device. It was listed next to your cars and even preselected when adding the card, although it holds only diagnostics and the card had nothing to show for it (0.9.10-beta.3).
- The bridge’s settings screen failed Home Assistant’s own validation, which fails validation for the whole integration rather than for the one string. Now guarded by a test that runs in CI (0.9.9-beta.6).
Documentation#
- New wiki page evcc & wallbox bridge, covering both directions, the full
topic table, when a wallbox reading is refused, and the troubleshooting path
for “nothing appears on the broker”.
docs/clean-install.mdgains a section on retained MQTT messages, the one thing this integration leaves behind that lives on someone else’s machine (0.9.9-beta.5). - The manual is now complete in German, page for page with the English one (0.9.10-beta.1).
0.9.8 12 September 2026#
The first stable release since 0.9.7. No code has changed since
v0.9.8-beta.2: this promotes the two 0.9.8 betas unchanged, so everything
below has already been running in the betas.
Breaking#
- “Doors overall state” now reports a lowercase slug (
SECURED→secured, displayed “Secured”). Automations that match the old ALL-CAPS text silently stop matching — open the condition and re-pick the state from the dropdown, which now offers the real values. This is the one disruptive part of this release; it is what made the entity usable from the automation editor in the first place.
Also in this release#
- The streamed lock state is usable in automations, and the card follows it
(0.9.8-beta.1).
Doors overall stateis the lock BMW actually streams —Doors lockis REST-only and can sit stale for days on a 50-request budget — but its values parsed as no enum at all, so the state picker offered only Unknown and Unavailable. It is now a proper enum in both languages, the card’s central-lock tile prefers it, and trip capture watches it. Reported by @nevryn (#8). - Two BMW accounts start up together again (0.9.8-beta.2). Home Assistant sets all config entries up at once and both registered the bundled card’s static route, so the second entry failed on every restart and stayed unavailable until reloaded by hand. Found and fixed by @netbasebe — the first external code contribution (#9).
- BMW’s unit spellings are canonicalised in one shared table (0.9.8-beta.1), and the catalogue pipeline now refuses to build on a unit it cannot name — an unrecognised spelling used to strip a sensor’s device class, unit conversion and long-term statistics with nothing in the build saying so.
See the beta sections below for the full detail of each.
0.9.7 9 September 2026#
Fixed#
-
Sensor values no longer shrink a little more with every restart. Home Assistant saves the value it displayed, not the one we reported, and the integration read that back as if it were the raw reading — so any sensor shown in a unit other than the one BMW sends had its conversion applied again on every restart. Tyre pressure held in kPa but displayed in bar was divided by 100 each time: 250 kPa became 2.5, then 0.025, and after five restarts the 2.5e-10 bar reported in #7. It could not self-correct, because the unit never disagreed with itself — only a fresh message from the car reset it, which is why slow-moving readings like tyre pressure were where it showed. The same descent is visible in the maintainer’s own history on a charge-time sensor displayed in hours, dividing by 60 a step at a time.
Sensors now save their native value and its unit (Home Assistant’s
RestoreSensor), so there is nothing left to infer. This affected any entity whose unit was changed in its settings, and — with no user action at all — distance, speed, temperature, pressure and volume on installs using the US customary unit system, where Home Assistant picks the display unit itself.After updating, an affected sensor reads as unknown until the car next reports it (for tyre pressure, that means the next drive). A value already decayed cannot be recovered, and restoring it would only preserve a wrong number; recorded history for those entities keeps the bad readings.
0.9.6 8 September 2026#
Documentation#
- The wiki now asks for feedback on the pages users actually reach. A short ask closes 4. Choose which data to stream (which model, how many entities appeared, whether setup needed the portal snippet), The dashboard card (a screenshot on anything that isn’t an i5) and Troubleshooting & FAQ (say so when nothing on the page matched). Placed only where a reader has just succeeded or just failed — the earlier Getting Started steps are mid-funnel and are left uninterrupted. The links point at the Discussions index rather than a numbered thread, so they survive announcement-thread rotation.
- Install instructions now describe the HACS default store. BavarianData was accepted into the HACS default list (hacs/default#9018, merged 2026-08-25), so it installs by searching HACS — the README quick start and the Install via HACS wiki page no longer walk users through adding a custom repository. That route is kept on the wiki page as a fallback for instances whose HACS data hasn’t refreshed yet, alongside a note that the missing logo in the HACS list is a HACS-side limitation and not a broken install.
Security#
If you have attached a diagnostics download to a public issue or forum post, it may contain your VIN and the entity id of your price sensor. Both are fixed below; a file downloaded before this release is not retroactively cleaned, so consider replacing it.
- The VIN escaped the diagnostics redaction through a repair id.
diagnostics.pybuilds a safe payload and runsasync_redact_dataover it, but that covers only our payload: Home Assistant’s own diagnostics wrapper appends the registered repair issues to the download, and the coverage repair’s id wasstream_coverage_gaps_{entry_id}_{vin}. The VIN therefore rode straight into the one file users are asked to attach to an issue. The v0.9.5 log masking was never at fault and is intact — this escaped as an identifier, not a message. The id is now a stable 12-character digest of the VIN, and the pre-0.9.6 id is deleted on every refresh so an existing repair is cleared rather than orphaned, which also removes the leaked value from the issue registry. The repair body’s vehicle name also fell back to the raw VIN for a car with no name, and now falls back to the masked form. Guarded intests/test_services_and_privacy.py. - Diagnostics dumped the entity id of your price and wallbox sensors. The
config-entry options are included verbatim, and
price_entity/grid_energy_entityhold an entity id the user picked — free text named by whichever integration created it, which routinely embeds identifiers of its own. Energy-supplier integrations in particular name theirs after the meter serial and supply-point number, so a home’s electricity connection could travel with the file. Both are now reduced to their domain (sensor.**REDACTED**); whether one is configured is all the triage ever used.
Fixed#
- The state of charge was never activated on the stream.
enabled_defaultindescriptor_metadata.pyis generated by substring-matching the descriptor intools/generate_metadata.py, and it decides far more than whether an entity starts enabled:descriptors_for_sections()streams only what it marks, so the flag governs what the portal snippet ticks, what the stream activator turns on, what onboarding selects, and what the coverage self-test expects. The pattern.headermatched exactly one descriptor in BMW’s 295-field catalogue —vehicle.drivetrain.batteryManagement.header, which despite the name is the high-voltage state of charge the whole charging history is built on. It was therefore never ticked in Data Selection, never arrived, and was not reported missing either, because it had been excluded from the expected set as well. Anyone who set up Data Selection with our snippet has had a state of charge frozen at whatever the REST bootstrap last wrote (#6). Fixed in the generator and regenerated; a new guard intests/test_review_guards.pypins every stream-critical descriptor into the activation set so a future pattern can’t quietly drop one. Existing installs must re-run the Data Selection snippet under Configure — the coverage repair now names the missing descriptor and says so. - The card’s gauge showed the trip-end state of charge. Two battery-class
percentages impersonate the live SoC in the overview’s entity pick: BMW’s
predicted SoC (
charging.level, REST-only, so it never updates on the stream) and the SoC at the end of the last trip, which only moves when a drive finishes. With the live SoC missing (above) one of them silently won the pick, so the reported iX showed a confident 58 % that was hours old and disagreed with its own charging history. Both are now excluded from the primary pick and kept only as an explicit fallback. The predicted one is matched on its descriptor rather than its name: the old"predicted"keyword only rejected it in English, so a German install picked it in preference to the real thing. Card 1.11.0. - The state-of-charge sensor had no
device_class. The generator infers it fromstateofcharge/soc/.levelin the descriptor andbatteryManagement.headercontains none of them, so HA had no idea the most important sensor in the integration was a battery percentage — and the card’s gauge could not find it even once it was streaming. Now set explicitly. - A frozen state of charge no longer wrecks the charging figures. Downstream of the above, and the reason it was so destructive rather than merely missing: with the same stale reading at both ends of every session, the SoC arc read “38 → 38%” and the energy ceiling saw a rise of zero, pinning every charge to its bare margin — 1.4 kWh on a 71 kWh pack, whatever the car actually took. Four DC charges peaking above 100 kW were each filed as 1.4 kWh, with the average power and the cost that follow from it. A SoC reading now only describes a session if it was taken during it; without one there is no ceiling (the energy stands on the power integration alone) and no SoC arc, which leaves both ends free for BMW’s own charging history to fill in on the next import. Duration and peak power were always right and are unchanged. This holds for any car that genuinely doesn’t produce the descriptor, not just for the misconfiguration above.
- README overstated the export formats. The feature summary advertised
“CSV/PDF export”, but
export_historyemits CSV and a self-contained HTML report — there is deliberately no PDF renderer (seehistory/export.py); the HTML report prints to PDF from any browser. Wording only, no code change. Spotted by @frenck during the HACS default review (hacs/default#9018).
0.9.5 20 August 2026#
Security#
- The last unescaped value in the bundled card. v0.9.4 escaped every value
the card renders, but one site survived: the cluster view’s empty state passes
the card’s
title:option through a translation placeholder, andt()substitutes placeholders raw so that a caller can deliberately pass markup. A cluster card whose title contained markup therefore had it parsed rather than shown, on any vehicle where that cluster has no entities. Card 1.10.1. - The VIN is masked in ordinary log lines. Verbose debug logging is opt-in
because it carries the VIN and GPS position — but 27 lines at INFO and above
wrote the full VIN to
home-assistant.logon every install, and that file is what users paste into public issues. Those now show the last four characters (***1234), which still tells two cars apart while reading a log. Debug lines are unchanged: triage needs the full value, and the user opted in to it.
Fixed#
webhookis now declared inmanifest.json. Guided setup registers a webhook for the in-browser stream activator to report its result to, but never declared the dependency. On an install withoutdefault_configthe route would not have existed, and the guided flow would have silently fallen back to the manual paste step.
Changed#
- The charging-price options are read through the shared
OPTION_*constants instead of repeated string literals, so renaming one cannot silently reset everybody’s pricing to the defaults.
Documentation#
- The diagnostics download is documented at last. It was shipped but described nowhere — despite being the one artifact that answers most “it doesn’t work” reports. Troubleshooting now has a Download diagnostics section covering what it contains, what it redacts, and that it costs no API quota, and Where to get help points at it.
- Settings reference now matches the screen. Seven rows on the Charging costs & history and Debug logging screens were documented under paraphrased names (“Price mode”, “Charging loss %”, “History retain months”) that never appeared in the UI. They now read exactly as the dialog does.
- Fixed a broken link on the card page pointing at a retention page that does not exist; it now goes to the retention setting itself.
Added#
- The HACS review audit now runs in CI. Two Home Assistant-free guard
modules replace a checklist that was being re-derived by hand before each
release:
tests/test_card_escaping.pyscans the card for user-controlled text reachinginnerHTMLunescaped (it catches the bug above), for Leaflet tooltips and for external resource loads;tests/test_review_guards.pypins the classes HACS review actually rejects for — disabled TLS verification, new unauthenticated HTTP views, credentials reaching logs or diagnostics, undeclared integration dependencies and payload size.
0.9.4 18 August 2026#
Security#
-
The bundled card now escapes every value it renders. The card composes its views as HTML strings, and several sources of user-controllable text reached
innerHTMLunescaped: the card’stitle:option (in both the heading and the subtitle), the device name as renamed in Home Assistant, entityfriendly_names, formatted entity states and their units, the free-text currency option, and the cluster heading. The trip map was affected too — Leaflet treats a tooltip string as HTML, and an endpoint tooltip carries a zone name or a reverse-geocoded address. Markup placed in any of those was parsed rather than shown.Escaping now happens at the source wherever there is one —
_fmt(),_shortName()and_fmtCost()escape what they return — plus at each individual site the vehicle name, card title or currency is interpolated, and on the map tooltip. A value containing&,<,>or"now displays as typed. The one deliberate exception is the trip-map subtitle, which is written throughtextContentand must stay unescaped.Setting any of those strings requires an authenticated Home Assistant user, so this was not remotely reachable — but a card should not depend on that.
Changed#
- The onboarding helper view now carries a full rationale for why it is served
unauthenticated (one-time capability token, GET-only, no identifiers in the
page, torn down with the flow), and
stream.pyrecords whypaho-mqttstays declared in the manifest even though Home Assistant’s container image already ships it — a Home Assistant Core install does not.
Also in this release#
This is the first stable release since 0.9.3. It carries the two fixes published in the 0.9.4 betas: average consumption was badly overstated (0.9.4-beta.1) and charged energy could overshoot when the stream went quiet mid-charge (0.9.4-beta.2). See those sections below for the detail.
0.9.3 31 July 2026#
Added#
- Actions are now translated. All 15 actions — their names, descriptions and
every field — now live in
translations/en.jsonandtranslations/de.jsoninstead of being hardcoded English inservices.yaml. German installs showed English action names in the UI; they no longer do.
Fixed#
- Removed real vehicle identifiers from the shipped integration. A real VIN
was used as the
example:for every VIN field inservices.yaml(rendered to every user in the actions UI), a real config entry id as theentry_idexample, andconst.pycarried three pasted debug-log dumps containing two VINs plus colour, build date, country and the full option list of the cars they came from. All replaced with the synthetic placeholderWBAEXAMPLE0000000or deleted; the response shapes those dumps documented are indocs/reference/customer-api.swagger.jsonanyway. A test now fails if a VIN- or entry-id-shaped literal reappears in any shipped file.
0.9.2 27 July 2026#
Trips grow up: every recorded drive can now be drawn on a map — a route line per trip and a clustering map of where you actually go — and a trip’s start and end times finally describe the drive rather than the moment the detector noticed it. Tires gain BMW’s wear diagnosis next to pressure on a rebuilt tire card. And the REST poller stops eating your quota: the daily container now carries the 41 fields the stream cannot, taking normal running from about 36 requests a day down to 2 — while filling entities that until now could only ever sit empty. Everything below accumulated across the 0.9.2 pre-releases; upgrading from 0.9.1 gets it all at once.
Added#
- Each trip now shows its route on a map. Expanding a trip in the Trips card view draws that drive on a small map — a single clean line with a start and an end marker — for trips recorded with Record route on. No map data leaves your browser to do it.
- A destinations map (
view: map) on the dashboard card. A new card view plots where your trips end as markers that cluster into counted bubbles when zoomed out and split apart as you zoom in — a quick read on where you go most, with an honest “times arrived here” count (a trip’s start is the previous trip’s end, so plotting both would double-count). A time-window chip row (This month / 3 months / All) filters it. It reuses Home Assistant’s own map and clustering, so there are no new dependencies, and it reads routes viaget_trips, so it spends no API quota. Destinations appear once Record route (trip_track) is enabled; the map shows a hint until then. - Tire wear, on a rebuilt tire card (card 1.8.1). BMW’s smart-maintenance
tyre diagnosis was being fetched and thrown away —
fetch_tyre_diagnosislogged it and nothing else. It now becomes five entities per car (Tyre Condition plus one per wheel BMW reports), and the card is a tire card rather than a tire-pressure card: titled Tires, it opens with the two things that can actually be wrong with one, side by side — Pressure (the measured spread across the set, with the shared target under it) and Wear (BMW’s verdict, with the mileage until the soonest wheel is due). Each wheel carries its own size, tread pattern, season and fitting date beside it, because staggered setups are normal: the i5 runs 245s at the front and 275s at the rear, and a single shared line had to show the wrong size for half the car. Wear outranks pressure in the wheel colour and the header badge — a tyre BMW flags as worn reads “Check tires” even at perfect pressure. Note that BMW reports remaining mileage, not tread depth;treadis the tread pattern (“EcoContact 6 Q”). Cars with no tyre service record on file get no tyre entities and an unchanged card. - Sharper trip start and end on cars that stream the driver door (e.g. the i5). The driver door brackets the drive: closing it (you got in) anchors where a trip starts, and opening it again after the car has stopped (you got out) ends the trip promptly instead of waiting out the 5-minute stationary timer. Used only as an accelerator — cars that don’t stream the door fall back to GPS exactly as before.
- Recorded routes now carry timing. With Record route (
trip_track) on, each GPS fix in a trip’s track is stored with the number of seconds since the trip started (trackpoints become[lat, lon, t]), so a map can replay a drive in real time, colour it by pace and show where the car stopped. Only affects opted-in recording; routes captured before this update keep their two-element[lat, lon]points and read back without timing (their times can’t be backfilled). - Trip-capture diagnostics (
Configure → Trips). An opt-in troubleshooting toggle for improving trip detection. When on, the integration logs the raw detector substrate under greppable tags — every GPS fix with its cadence, latency, odometer and the close-timer countdown ([trip.gps]), the timer lifecycle ([trip.timer]), full BMW segment batches ([trip.seg]), a per-message descriptor firehose ([trip.raw]) and a per-trip post-mortem ([trip.post]) — and writes a replayablebavariandata_trip_capture.ndjsoncapture to the config folder. Each[trip.gps]line also carries the GPS fix state, satellite count and heading (to tell a stopped car from a lost fix), and a[trip.watch]line surfaces catalogue signals that might drive a better detector (speed, HV-system and connector state, the ignition trio, driver door/lock, active navigation) whenever the car streams them. Independent of Debug logging, off by default, and contains GPS/VIN, so it’s meant to be switched on for a test drive and back off. - Documented how to remove BavarianData completely. Troubleshooting & FAQ has
a new “Removing BavarianData completely” section: deleting the integration
already wipes your tokens, your charging/trip history and the statistics it
published, but the quota log, the cached vehicle image and any trip-capture
file are left behind — and a trip capture contains GPS coordinates, so it’s
worth deleting. Also covers the one trap when testing a fresh install: long-term
statistics live in the recorder database, so a partial removal can leave
bavariandata:…series showing up in your Energy dashboard with nothing installed.
Changed#
-
The REST container is polled once a day instead of every 40 minutes, and now carries everything the stream cannot. The container held 30 descriptors of which 26 duplicated the stream, and was refetched every 40 minutes — about 36 of the 50 daily requests. It now holds the 41 non-streamable descriptors the container endpoint can serve (service demands, Condition Based Servicing, check-control messages, door-lock status, lifetime-consumption counters, state of health) plus the battery keys for a fast first paint, and refreshes daily.
Net effect: from ~36 requests a day to 2 (the container, plus the tyre diagnosis on its own endpoint), leaving 48 free for manual fetches — while filling entities that until now could only ever sit empty. Existing installs migrate on the next token refresh: the old container is deleted and replaced, rather than left to idle against BMW’s 10-container limit.
-
A trip’s start and end times now describe the drive, not the detection. A trip was stamped from the first position fix that registered movement to the moment the five-minute stationary timer expired — so a drive from 10:51 to 10:57 was recorded as 10:54 → 11:00, and its duration overstated by minutes at both ends. The start now falls back to when the car was last seen parked (on cars that stream the driver door, bounded to five minutes before detection, so sitting in the car before pulling away doesn’t count as driving), and a stationary or segment close ends the trip at the last fix that showed movement instead of when the timer fired. A driver-door arrival still ends the trip where it always did — the door opening is the arrival. Trips already recorded keep their old timestamps.
-
The tire-pressure band is no longer symmetric: low from 8% under target, high only past 15% over. The old ±4% flagged every wheel of a perfectly healthy car — BMW’s target is the cold pressure and a tire you have just driven on reads 8–10% high. Under-inflation is the condition worth an early hint; over-inflation is only worth one past what warm-up explains.
-
The dashboard card is now the “BavarianData Card”. It was previously shown as the “BMW CarData Card” (element
custom:bmw-cardata-card); the card, its element (custom:bavariandata-card) and its bundled file have been renamed to match the integration’s name. Existing dashboards keep working: the oldcustom:bmw-cardata-cardelement is still registered as a hidden alias, so no card needs to be re-added. The old name simply no longer appears in the card picker. -
Refreshed BMW’s API reference material from source.
docs/reference/now carries Integration Guide v1.5 (09/01/2026) and the current Swagger files, re-fetched from BMW. Notable spec changes: the charging-history session gainedenergyDecreaseHvbKwhandenergyDischargedKwhand lostchargingCostInformation;basicDatagainedreessNominalCapacityGross; the device-code response no longer documentsverification_uri_complete; and the streaming chapter now states MQTT QoS 0 only plus a per-IP connection-rate policy over a one-minute window. No integration behaviour changes. The README in that folder records the exact URLs to re-fetch from, andtools/README.mdnow records the direct Telematics Data Catalogue download URL and spells out that the catalogue — not the Swagger — is the stream’s contract.
Fixed#
-
Cards no longer break on a browser reload. Every BavarianData card turned into a “Configuration error” box after pressing F5 — a hard refresh didn’t help, only closing and reopening the browser did. The card was published as a frontend module, which Home Assistant renders into the page as a fire-and-forget
import(); the frontend’s service worker can serve that page from cache without it, so thebavariandata-cardelement was never defined and every placed card fell back to the error box (home-assistant/frontend#18728). The card is now registered as a proper Lovelace dashboard resource, which is loaded before any dashboard renders and is version-stamped on every update. Installations whose dashboard resources are YAML-managed keep the old behaviour and should add the resource by hand — see Troubleshooting. -
Trip routes were drawn from mismatched latitude/longitude pairs, putting every recorded point somewhere the car never was: a right-angle staircase where each vertex took its latitude from the previous fix and its longitude from the current one. BMW sends the two coordinates as separate messages, and the pairing guard compared each coordinate only against its own previous value — so once a single unpaired message slipped through (the first one after connect always does, with nothing to compare against), the stale half counted as “advanced” too and every later fix paired one message behind, permanently. Pairing now waits for both halves of a fix to actually arrive, and drops BMW’s duplicate redelivery by the fix’s own timestamp. The phantom right-angle points also inflated the measured trip distance, which is now correct too. Routes recorded before this fix keep their bad points; new drives are correct.
-
Routes now start where the car was parked. A trip’s track (and its start place) began at the first point that registered as movement — often a block or more past the actual start. It is now seeded from the last known parked position, so the line connects from where the drive really began.
-
The tire diagnosis now survives a restart. Wear, tread, size, season and fitting date were held in memory only, so every Home Assistant restart blanked the tire sensors and the wear half of the tire card until the next daily refresh came due — up to 24 hours later — or
fetch_tyre_diagnosiswas called by hand. The last fetched diagnosis is now stored and restored at startup, at no cost to the API quota. The sensors also carry afetched_atattribute now, so a day-old reading is recognisable as one. -
The coverage report no longer reports gaps that can never close. BMW’s telematic catalogue marks each field with whether the MQTT stream can carry it (246 of 295 can), but that column is drawn as a tick glyph rather than text, so the catalogue generator read it as empty and the flag never existed. As a result 35 fields BMW cannot stream — tyre diagnosis, vehicle image, state of health, Condition Based Servicing, door-lock status, the lifetime-consumption counters — were being requested on the stream and counted as expected by
get_coverage_report, which then listed them as missing on every car, forever. That is exactly the false alarm the self-test exists to prevent.The flag is now parsed and carried through the pipeline, and non-streamable fields are excluded from the cluster picker, the portal snippet,
activate_stream_fieldsand the coverage comparison. Entities are unaffected — several of these fields arrive over REST via a container, so they keep their entity and simply are not expected on the stream.telematics-fields.mdgained a Stream column so you can see which is which per field.
0.9.1 26 July 2026#
Added#
- Optional route recording for trips. A new Record route toggle under
Configure → Trips (off by default) makes each new trip additionally store
its GPS track — the polyline of coordinates along the drive — so a map can show
where the car went. It is the only setting that persists raw coordinates, is
independent of address resolution, and takes effect on the next trip that
starts. The track is served through
bavariandata.get_trips(atracklist of[lat, lon]points) and is never included in the CSV / printable export, which stays place-names-only. Trips keep storing named places only when the toggle is off, exactly as before.
0.9.0 24 July 2026#
The big one: a full history layer. BavarianData now keeps its own local store — charging sessions with real cost, a Fahrtenbuch/trips log with a month-in-review, a learned battery-health estimate, and long-term statistics that feed the Energy dashboard — none of which touches your 50-request BMW API quota. Past charges and drives can be imported from BMW’s history, and the Lovelace card gained views for all of it. Everything below accumulated across the 0.9.0 pre-releases; upgrading from 0.8.1 gets it all at once.
Added#
- History layer — the foundation (Phases 0–2). A local history store that records charging sessions with real cost and surfaces them in a new charging-history card view, plus a battery-health estimate learned from wide-SoC charges with its own view. All local — no BMW API quota.
- Trips / Fahrtenbuch with a month-in-review (Phase 3). Trips are recorded
locally with their endpoints stored as place names, never coordinates. The
get_tripsservice lists them, andget_driving_summaryaggregates a month — distance, the business/private/commute split, consumption, recuperation, a driving-style score, top destinations, and (with a tariff configured) an estimated driving cost. - Statistics backfill & month export (Phase 4). The new
import_statisticsservice rebuilds long-term statistics from recorded history, so charging and driving from before the install — or from while Home Assistant was down — appear on the Energy dashboard. The newexport_historyservice returns a month of charging sessions and/or trips as CSV files or a self-contained, printable HTML report. Both read the local store and cost no BMW API quota. fetch_charging_historynow imports into the local history, instead of only logging BMW’s response. Past charges — including ones from before the integration was installed, or from while Home Assistant was down — appear on the card’s charging view and in the monthly summaries. BMW’s measured grid energy, SoC, odometer, location and power curve are mapped into session records; a charge that also streamed live is enriched in place (no duplicate), and cost is backfilled for a fixed tariff. Re-running the import is idempotent.- Descriptor coverage self-test. A new
get_coverage_reportservice (costs no BMW API quota) lists, per vehicle, which descriptors from the stream clusters you enabled have never actually arrived — so you can tell whether a missing entity is down to your BMW Data Selection, your car, or a bug. A dismissible repair issue is raised once a gap is genuinely overdue (7-day grace period), never a notification. - Selectors for every service action. Action fields (VIN, config entry, date ranges, limits, month) now render in Home Assistant’s visual action editor with proper pickers, instead of being reachable only in YAML mode.
- German translations for the derived entities and the setup/config flow.
- Tagged debug tracing across the history layer
(
[trip]/[charge]/[health]/[stats]/[history]) to make the opt-in debug log readable.
Changed#
- Imported charging sessions now resolve their location to a Home Assistant zone (Home, Work, …) from BMW’s coordinates, the same way live-recorded sessions do — so the card shows “Home” for a home charge instead of “Away”. Only the resolved zone is stored; the raw latitude/longitude BMW returns are used for the lookup and then dropped, matching the live path’s privacy stance.
- Card charging view: the collapsed session row now leads with the energy (kWh) instead of the price; the per-session cost moves into the expanded detail. The power-curve chart is now stepped — each sampled power holds until the next reading rather than being drawn as a diagonal ramp between samples.
Fixed#
- A charge that streamed live and was imported from BMW no longer becomes two
records. A brief
charging.statusblip (a momentary “not charging” that came straight back) split one plug-in into two live sessions; BMW’s import then enriched only one of them and orphaned the other, so the monthly total counted the same charge’s energy twice. Two fixes: the session close is now debounced, so a status flap no longer splits a live session in the first place; and on import, every live-only fragment that falls inside BMW’s charge window is folded into the single measured record instead of being left behind. Re-running “Fetch charging history” once reconciles any already-split charge into one record. - The charging-state sensor (“Ladestatus”) now shows readable states. BMW’s
catalogue lists the wrong allowed values for this field (a charging-mode
list), so the actual stream states —
nocharging,chargingactive,initialization,chargingpaused,chargingended,chargingerror— were shown raw and untranslated. The real enum (which BMW documents in the field’s own description) is now pinned, so the sensor reads “Not charging” / “Charging” / “Charging paused” … in English and “Lädt nicht” / “Lädt” / … in German. - Already-imported home charges now show “Home” instead of “Away”. Sessions imported by an earlier build kept their unresolved location, and a re-import wouldn’t overwrite it — so a charge at home stayed labelled “Away” forever. On startup those legacy records are now re-resolved locally (no BMW request, no quota) and a re-import upgrades an unresolved location in place.
- Public charges now show BMW’s address. When a charge’s location matches no
Home Assistant zone, BMW’s own
formattedAddress(which the charging-history API already returns) is kept as the label and shown on the card, in the CSV/ HTML export, and as a sensor attribute — no reverse geocoding, and still no raw coordinates stored. - Trips are now detected from the live GPS position stream. On vehicles that
don’t stream a live
isMoving/ ignition signal or a fresh completed-trip batch (e.g. the i5, even with those descriptors selected in Data Selection), no trip was ever recorded. Trip detection now opens on GPS movement, sums the drive’s distance from the position track when no odometer or BMWtravelledDistanceis available, and closes after the car has been stationary — so drives finally appear in the trip list and the monthly distance sensor. - A stale “last trip end” field can no longer close a live trip. BMW repeats
the previous drive’s
trip.segment.end.*fields (with their old timestamp) in every telematic snapshot; these are now ignored unless their timestamp is recent, so a parked, charging car no longer looks like a just-finished trip. - Imported BMW charging now counts toward the monthly energy total. An
imported session carries only BMW’s measured grid energy (
grid_kwh), never the stream-integrated battery-side figure, so it was silently excluded from the “charging energy this month” sensor. The monthly summary now counts the measured grid figure — the same rule the Energy-dashboard statistics and the CSV/HTML export already use. - The Lovelace card showed “not charging” while the car was charging. The
overview picked the wrong status entity (
charging.hvStatus) over the authoritativecharging.status, and did not recognise BMW’s uncataloguedchargingactivevalue. The card now preferscharging.status, treatschargingactive/charging_in_progressas active, and renders them as a clean localized “Charging” label. - Card entity auto-selection ignored multi-word keywords on non-English installs. The picker matched keys like “charging status”, “electric range” or “connection status” only against the localized friendly name, never the English descriptor path — so on a German (or other) install it fell back to arbitrary tie-breaks. The descriptor is now also matched word-normalized, fixing selection of the charging-status, range, plug and time-to-full tiles regardless of Home Assistant’s language.
fetch_charging_historyfailed with HTTP 400 when run without a date range. BMW requiresfrom/to; the service now defaults to the last 30 days when they are omitted and sends the ISO 8601 UTC timestamps BMW expects. Thefrom/tofields also get date/time pickers.- Duplicate unique-ID warning after a restart for the monthly charging summary sensors (e.g. “energy this month”), which also spawned a phantom duplicate sensor. The restore path now recreates only the correct entity.
- Vehicle location went unavailable after every restart or upgrade until the car next streamed a GPS position (often hours away). The device tracker is now recreated immediately and its last known position restored, so the map shows where the car was parked straight away.
- hassfest validation: declared the
recorderdependency (now required because the history layer writes long-term statistics) and sorted the manifest keys into the required order.
0.8.1 21 July 2026#
Added#
- Options-flow toggle to enable debug logging (opt-in, since debug output can contain VIN/GPS).
Fixed#
- GPS and length sensors could fail to be added because of an
AttributeErrorreading_attr_device_class; the read is now guarded. - Invalid energy
state_class(measurement→None) corrected through the descriptor metadata pipeline, restoring Energy-dashboard compatibility. - Device tracker crash (
_update_name) and a blocking manifest read during setup.
Changed#
- Minimum supported Home Assistant raised to 2026.3 (needed for self-served
brand icons). Untracked
.venvand dropped the obsoletebrands/staging dir. - A clean MQTT
rc=0disconnect is now logged at debug instead of warning. - README images use absolute
raw.githubusercontent.comURLs so they render on the HACS info screen.