Skip to content

Add BMW CarData vehicle module (SoC, range, charging status)#3251

Draft
GERDerDennis wants to merge 40 commits intoopenWB:masterfrom
GERDerDennis:feature/bmw-cardata
Draft

Add BMW CarData vehicle module (SoC, range, charging status)#3251
GERDerDennis wants to merge 40 commits intoopenWB:masterfrom
GERDerDennis:feature/bmw-cardata

Conversation

@GERDerDennis
Copy link
Copy Markdown

This PR adds an initial BMW CarData integration for openWB vehicles.

Features

  • Retrieves SoC, electric range and charging status via BMW CarData API
  • Supports modern BMW BEV models (e.g. iX, iX3)
  • Automatic token refresh handling
  • Automatic container detection
  • Automatic container creation if none exist (common CarData issue)
  • Prioritized SoC field selection:
    • charging.level (preferred for newer vehicles)
    • batteryManagement.header (fallback)

Background

BMW CarData often returns an empty container list even with valid authentication and vehicle mapping.

This implementation detects that condition and automatically creates a minimal container, allowing telematic data retrieval without manual intervention.

UI

  • Adds a configuration UI for BMW CarData vehicles

Notes

  • Respects BMW API limits (50 requests/day)
  • No aggressive polling
  • Tested with BMW iX M60
  • Telematic data also successfully retrieved from a BMW iX3 test vehicle

Status

Work in progress. Further improvements planned:

  • Auth flow refinement
  • UI improvements
  • additional telemetry fields

Feedback is very welcome.

@GERDerDennis GERDerDennis marked this pull request as draft March 28, 2026 15:13
Add BMW CarData SoC module - auth wrapper scripts

Add auth_start.py and auth_status.py as wrapper scripts
for the BMW CarData authentication flow.
@GERDerDennis
Copy link
Copy Markdown
Author

Seit dem initialen Commit wurde das Modul erheblich weiterentwickelt und auf einer eigenen openWB-Testinstallation (Proxmox VM, Debian 11, openWB 2.1.9-Patch.2) getestet.
Erreichter Stand:

Natives openWB SoC-Modul vollständig implementiert
SoC, Reichweite und Ladestatus werden korrekt abgerufen
BMW OAuth2 Device Code Flow mit PKCE vollständig implementiert
Auth kann direkt aus der openWB UI gestartet werden mit Statusanzeige und automatischem Polling
Token werden persistent gespeichert und automatisch erneuert
Container-ID wird gecacht – typischerweise nur 1 API-Call pro Abfrage
Automatische Container-Erstellung falls keine vorhanden (häufiges BMW CarData Problem)
Testmodus ohne BMW API-Calls für sichere Entwicklung

Getestete Fahrzeuge:

BMW iX M60 (2023) – vollständig funktionsfähig
BMW iX3 G08 – SoC und Reichweite erfolgreich abgerufen
BMW i3s – SoC und Reichweite erfolgreich abgerufen

Weitere Fahrzeuge noch nicht getestet. Feedback willkommen.
Bekannte offene Punkte:
Die Auth-Integration nutzt aktuell eine PHP-Bridge mit sudoers-Regel damit Apache Python-Skripte als openwb-User ausführen kann. Das ist eine temporäre Lösung und wird vor dem finalen PR ersetzt.
Die vehicle.vue liegt aktuell noch im Core-Fork und wird in einen separaten openwb-ui-settings PR verschoben.

@GERDerDennis
Copy link
Copy Markdown
Author

Update zum aktuellen Stand:

Die bisherige PHP-Bridge mit sudoers-Regel wurde vollständig ersetzt.

Auth-Architektur (neu):

  • Der BMW OAuth2 Device Code Flow mit PKCE läuft jetzt komplett in PHP via curl – kein sudo, kein Python-Subprocess, keine sudoers-Regel
  • Tokens werden direkt in der openWB-Fahrzeugkonfiguration gespeichert.
  • Kein separates Token-File mehr
  • config.py enthält die Token-Felder direkt in der Konfigurationsklasse

Weitere Verbesserungen:

  • Kilometerstand wird über vehicle.vehicle.travelledDistance abgerufen
  • Container wird bei Bedarf automatisch erstellt
  • Container-Auswahl bevorzugt openWB-eigene Container (purpose='openWB')
  • Token-Ablaufdatum wird in der UI angezeigt
  • Hinweis auf 'Speichern' nach erfolgreicher Auth
  • Fehlermeldung bei Tageslimit (CU-429) wird korrekt in der UI angezeigt

Getestete Fahrzeuge:

  • BMW iX M60 (2023) – vollständig funktionsfähig
  • BMW iX3 G08 – SoC und Reichweite erfolgreich abgerufen
  • BMW i3s – SoC und Reichweite erfolgreich abgerufen

Noch offen:

  • vehicle.vue liegt noch im Core-Fork und wird in einen separaten openwb-ui-settings PR verschoben
  • Rechteproblem: PHP schreibt die Auth-Status-Datei nach /var/www/html/openWB/data/ – dieses Verzeichnis gehört openwb. Gibt es ein Verzeichnis das sowohl von www-data beschrieben als auch von openwb gelesen werden kann?

Fix auth UI: show user_code regardless of connection state, simplify auth button
Add odometer support via vehicle.vehicle.travelledDistance
@GERDerDennis
Copy link
Copy Markdown
Author

Update 29.03.2026
Seit dem letzten Update wurden folgende Verbesserungen eingebaut:
Kilometerstand:

Wird über vehicle.vehicle.travelledDistance abgerufen
CarState.odometer wird korrekt befüllt (nutzt PR #3210)
Container-Descriptor wurde entsprechend erweitert

Auth-UI Fixes:

User-Code und Verification-URL werden jetzt auch angezeigt wenn bereits eine Verbindung besteht
Auth-Button immer sichtbar mit Hinweis wenn Verbindung bereits aktiv

Aktueller Stand:

SoC, Reichweite, Ladestatus und Kilometerstand werden korrekt abgerufen ✅

@GERDerDennis
Copy link
Copy Markdown
Author

Update: Feedback eingearbeitet

urllib → req.get_http_session()
Alle HTTP-Calls nutzen jetzt req.get_http_session() statt urllib.

Testmodus entfernt
Der Testmodus wurde entfernt. Die Einstellungen sind auf das Wesentliche reduziert: Client ID, VIN, SoC-Berechnung während Ladung.

Auth-Status im Broker statt Datei
Die bmw_cardata_auth_status.json Datei wird nicht mehr verwendet. Der Auth-Status (device_code, code_verifier, expires_at) wird während des Device Code Flows im lokalen Vue-State gehalten und direkt im POST-Body an PHP übergeben. Nach erfolgreicher Auth werden die Tokens via updateConfiguration im Broker gespeichert – analog zu den Token-Daten. Das Rechteproblem mit /var/www/html/openWB/data/ ist damit vollständig gelöst.

Unit Tests aktualisiert
Die Tests wurden auf req.get_http_session() umgestellt und der Testmodus entfernt. Alle 8 Tests bestehen.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an initial BMW CarData vehicle integration, including backend telemetry retrieval (SoC/range) plus a UI-driven device-code OAuth flow to obtain and refresh BMW API tokens.

Changes:

  • Added PHP endpoints to start BMW Device Code + PKCE auth and to poll token status.
  • Added a new Python vehicle module to fetch SoC/range (and handle token refresh + CarData container discovery/creation).
  • Added a UI settings component to configure BMW CarData vehicles and perform the coupling flow.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
web/bmw_cardata/bmw_cardata_auth_start.php Starts BMW Device Code flow and returns verifier/device_code metadata to the UI.
web/bmw_cardata/bmw_cardata_auth_status.php Polls the BMW token endpoint and returns access/refresh tokens when authorized.
packages/modules/vehicles/bmw_cardata/config.py Adds dataclass-based configuration/setup for BMW CarData vehicles.
packages/modules/vehicles/bmw_cardata/soc.py Implements token validation/refresh, container detection/creation, and telematic data extraction (SoC/range/odometer).
packages/modules/vehicles/bmw_cardata/soc_test.py Adds unit tests for basic extraction/error handling paths.
packages/modules/vehicles/bmw_cardata/__init__.py Initializes the new vehicle module package.
openwb-ui-settings/src/components/vehicles/bmw_cardata/vehicle.vue Adds UI configuration and coupling flow with polling for token retrieval.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +120 to +132
try:
raw = session.get(url).json()
except Exception as e:
msg = str(e)
if "404" in msg or "400" in msg:
log.warning("BMW CarData: Container ungültig, ermittle neu...")
cfg.container_id = ""
cid = get_container_id(cfg, token)
raw = session.get(
f"{BMW_API_URL}/customers/vehicles/{cfg.vin}/telematicData?containerId={cid}"
).json()
else:
raise
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die Erkennung eines ungültigen Containers basiert auf String-Suche nach "404"/"400" in str(e). Das ist fragil (Format kann variieren) und kann dazu führen, dass Container-Refresh nicht ausgelöst wird. Besser: gezielt requests.exceptions.HTTPError abfangen (kommt durch raise_for_status() Hook) und e.response.status_code prüfen.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +155
soc_raw = val(FIELD_SOC) or val(FIELD_SOC_ALT)
rng_raw = val(FIELD_RANGE)
status = val(FIELD_STATUS)
odo_raw = val(FIELD_ODOMETER)

soc = int(float(soc_raw)) if soc_raw is not None else None
rng = int(float(rng_raw)) if rng_raw is not None else None
odo = int(float(odo_raw)) if odo_raw is not None else None

if soc is None:
raise Exception("BMW CarData: Kein SoC-Wert in API-Antwort gefunden!")

log.info("BMW CarData: SoC=%s%%, Reichweite=%s km, Status=%s, Odometer=%s km",
soc, rng, status, odo)
return CarState(soc=soc, range=rng, odometer=odo)

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Es wird zwar charging.status ausgelesen und geloggt, aber nicht im CarState zurückgegeben oder anderweitig publiziert. Damit wird der im PR-Text erwähnte "charging status" aktuell nicht für openWB nutzbar. Entweder Status in ein passendes Datenfeld/Topic übernehmen oder die Feature-Behauptung/Logs entsprechend anpassen.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Der Ladestatus wird über den Ladecontroller ausgewertet. Im Fahrzeug-Modul ist er nicht nötig..

Copy link
Copy Markdown
Contributor

@LKuemmel LKuemmel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bitte auch die Kommentare von Copilot beachten und die vue-Datei als PR für das settings-Repo einreichen.

Comment on lines +140 to +155
soc_raw = val(FIELD_SOC) or val(FIELD_SOC_ALT)
rng_raw = val(FIELD_RANGE)
status = val(FIELD_STATUS)
odo_raw = val(FIELD_ODOMETER)

soc = int(float(soc_raw)) if soc_raw is not None else None
rng = int(float(rng_raw)) if rng_raw is not None else None
odo = int(float(odo_raw)) if odo_raw is not None else None

if soc is None:
raise Exception("BMW CarData: Kein SoC-Wert in API-Antwort gefunden!")

log.info("BMW CarData: SoC=%s%%, Reichweite=%s km, Status=%s, Odometer=%s km",
soc, rng, status, odo)
return CarState(soc=soc, range=rng, odometer=odo)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Der Ladestatus wird über den Ladecontroller ausgewertet. Im Fahrzeug-Modul ist er nicht nötig..

@LKuemmel LKuemmel requested a review from benderl April 2, 2026 08:50
Copy link
Copy Markdown
Contributor

@benderl benderl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wie Lena schon geschrieben hat, bitte die Vue-Datei in einen PR gegen das Settings-Repo packen. Die beiden PHP-Dateien gehören auch dort hin. Im Pfad public/modules/vehicles/ findest Du schon vergleichbare Dateien für die Tesla und Mercedeseq SoC-Module.

@GERDerDennis
Copy link
Copy Markdown
Author

Danke für das ausführliche Feedback!

Ein Großteil der angesprochenen Punkte ist bereits im aktuellen Stand umgesetzt:

  • Wechsel von urllib auf req.get_http_session()
  • Entfernung des Testmodus und Reduktion der Konfiguration
  • Auth-Zwischenstatus wird nicht mehr über Datei, sondern über das Konfig-/Broker-Modell gehandhabt
  • Container-Handling inkl. Retry wurde überarbeitet (HTTP-Status-basiert statt String-Matching)
  • Tests für Token-Refresh, Container-Auto-Create und Retry ergänzt (aktuell 11 Tests grün)

Zusätzlich werde ich:

  • vehicle.vue sowie die PHP-Dateien in einen separaten PR gegen das Settings-Repo auslagern
  • UI-Texte (z. B. „Auth“) verständlicher formulieren
  • HTTP-Statuscodes in den PHP-Endpunkten ergänzen

Einige vorgeschlagene Änderungen beziehen sich auf ältere Code-Stände und sind im aktuellen Branch bereits angepasst.

Falls ich etwas übersehen habe, gerne nochmal kurz Bescheid geben 🙂

@GERDerDennis
Copy link
Copy Markdown
Author

Die vehicle.vue wurde in einen separaten openwb-ui-settings PR verschoben:
openWB/openwb-ui-settings#941

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants