Skip to content

feat(bmw-cardata): add bmw-cardata#1417

Open
jvm wants to merge 2 commits into
mvanhorn:mainfrom
jvm:feat/bmw-cardata
Open

feat(bmw-cardata): add bmw-cardata#1417
jvm wants to merge 2 commits into
mvanhorn:mainfrom
jvm:feat/bmw-cardata

Conversation

@jvm

@jvm jvm commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

bmw-cardata

Your BMW's telemetry, on the command line — device-code login, live telematic snapshots, charging history, and a local SQLite store that turns raw VSS data into trends, cost, and fleet insight no other tool gives you.

API: bmw-cardata | Category: devices | Press version: 4.24.0
Spec: BMW CarData REST + OAuth Device Code Flow (downloaded as static OpenAPI assets; spec_format openapi3)

Publication Path

New print — first public release of the bmw-cardata CLI.

CLI Shape

$ bmw-cardata-pp-cli --help
Bmw Cardata CLI — Your BMW's telemetry, on the command line — device-code login, live telematic snapshots, charging history, and a local …

Highlights (not in the official API docs):
  • soc-trends   See your battery state-of-charge and derived range as a time series you can chart.
  • charging-cost   Reconcile your charging sessions against your electricity tariff and see DC charge efficiency.
  • quota   See how many of your ~50 daily API calls you have spent and when the budget resets (midnight UTC).
  • fleet status   One table of current SoC, range, charging state and last location across every vehicle on your account.
  • descriptors search   Search the VSS telematic descriptor catalogue (e.g. batteryManagement, charging, navigation) to build containers.
  • battery-health   Track battery capacity degradation by comparing observed max energy against the nameplate capacity.
  • vehicles diff   Show exactly what changed between two telematic snapshots (windows, charging, locks, location).
  • trips   Reconstruct trips (start, end, elapsed, distance) from your vehicle's location breadcrumbs.

Agent mode: add --agent to any command for JSON output + non-interactive mode.
Health check: run 'bmw-cardata-pp-cli doctor' to verify auth and connectivity.
See README.md or the bundled SKILL.md for recipes.
... (truncated)

Novel Commands

Command Name Description
soc-trends SoC/range trends See your battery state-of-charge and derived range as a time series you can chart.
charging-cost Charging cost & efficiency Reconcile your charging sessions against your electricity tariff and see DC charge efficiency.
quota Quota tracker See how many of your ~50 daily API calls you have spent and when the budget resets (midnight UTC).
fleet status Fleet status One table of current SoC, range, charging state and last location across every vehicle on your account.
descriptors search Descriptor search Search the VSS telematic descriptor catalogue (e.g. batteryManagement, charging, navigation) to build containers.
battery-health Battery health Track battery capacity degradation by comparing observed max energy against the nameplate capacity.
vehicles diff Snapshot diff Show exactly what changed between two telematic snapshots (windows, charging, locks, location).
trips Trip reconstruction Reconstruct trips (start, end, elapsed, distance) from your vehicle's location breadcrumbs.

What This CLI Does

BMW CarData is the official, free-for-personal-use API for your own vehicle data. This CLI handles the OAuth device-code onboarding, fetches live telematic snapshots, charging history and tyre diagnosis, then keeps it all in SQLite so you can chart SoC trends (soc-trends), reconcile charging cost against your tariff (charging-cost), and see every vehicle at once (fleet status).

The recommended path installs both the bmw-cardata-pp-cli binary and the pp-bmw-cardata agent skill in one shot:

npx -y @mvanhorn/printing-press-library install bmw-cardata

Manuscripts

Validation Results

Check Result
Manifest PASS
Transcendence PASS
Phase 5 (12/12 live) PASS
go mod tidy PASS
govulncheck (reachable) PASS (0 reachable; 1 module-level not called)
go vet PASS
go build PASS
--help PASS
--version PASS
Verify-skill PASS
Manuscripts PRESENT

Publish Live Gate — Documented Override

The publish-time live gate (dogfood --live, 84 tests) returned 5 failures, none of which are CLI defects. The user authorized proceeding with these documented as environmental/framework issues:

# Failure Category Why it is not a CLI defect
1 sync (json_fidelity): exit 1, HTTP 429 "API rate limit reached" (CU-429) Environmental BMW's documented ~50 calls/day cap was exhausted during the repeated live-gate re-runs required to validate fixes (Example sections, VIN validation, token extraction). Resets midnight UTC. The CLI's sync behavior is correct; the limit is an upstream cap.
2 customers get-container-details (happy_path + json_fidelity): exit 5, HTTP 400 "containerId must be 13 characters" Framework contract The dogfood runner's store-derived fixture is a name (12 chars), not a valid BMW containerId. The CLI correctly calls the API and surfaces the 400. Fixture is a dogfood bug.
3 descriptors search (error_path): expected non-zero exit Framework contract A search command returning an empty result for a no-match pattern is a valid outcome (exit 0). There is no syntactic "invalid pattern" class. The runner's error_path contract is misaligned with search semantics.
4 tail (error_path): expected non-zero exit Framework contract The generated tail command prints a warning and exits 0 when the initial fetch returns 4xx. The runner expects non-zero; the generator emits warn-and-continue. Generator contract gap.

The earlier Phase 5 full live dogfood (run during generation, before the rate limit was hit) was 12/12 PASS against the real BMW CarData API end-to-end (auth login, get-mappings, get-basic-data, get-telematic-data, write-through to SQLite, and soc-trends/fleet/quota all returned real vehicle data for a BMW X1 xDrive25e PHEV). The publish-time re-run failed on the 4 environmental/framework issues above, not on regressions. The phase5-acceptance.json shipped here represents that validated 12/12 result. Per the user's explicit operator decision, this PR proceeds.

Customizations

  • .printing-press-patches/bmw-cardata-publish-polish-examples-and-vin-validation.json — publish-session polish: realistic Example: blocks on the 8 transcendence commands + VIN format validation (17 chars, no I/O/Q) so the dogfood error_path probe returns non-zero on malformed VINs.

Security

  • betterleaks (secrets) — 4 findings, all false positives: vendor OpenAPI spec example JWTs (expired 2021) in the archived discovery evidence, not in the publish path.
  • semgrep — 4 findings, all mitigated: fmt.Sprintf SQL in the printer-emitted internal/store/store.go is gated by validIdentifierRE validation at function entry.
  • Hand-written code clean: all store queries use ? placeholders; MQTT uses tls.Dial TLS 1.2+; session sidecar at 0o600.

@github-actions

Copy link
Copy Markdown
Contributor

@greptileai review

Auto-nudge from greptile-policy-gate.yml because no Greptile Review check appeared for 25b1fa3f71989ed7f17d3c09de2abc931bacaeb4 after 185s. This usually means the PR is over Greptile auto-review size cap; manual triggers bypass it.

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces bmw-cardata, a new CLI wrapping the BMW CarData REST API with OAuth2 device-code login, live telematic snapshots, charging history, and a local SQLite store powering eight novel analytics commands (soc-trends, charging-cost, quota, fleet status, descriptors search, battery-health, vehicles diff, trips). All three issues from the prior review round are confirmed resolved in 171f02f3.

  • Auth login fix: openBrowser now branches on runtime.GOOS and surfaces browser-open failures to stderr.
  • Trips duration fix: segmentTrips captures time.Parse errors and emits duration: null + duration_parse_error instead of a bogus multi-year duration.
  • NULL-ts dedup fix: ux_cardata_telematic_vin_desc_ts_or_fetched uses COALESCE(ts, fetched_at) to deduplicate ts-less descriptors correctly.

Confidence Score: 4/5

Safe to merge; no P0/P1 defects — only two P2 findings that degrade UX in edge cases without corrupting data.

All three previously flagged issues were resolved in commit 171f02f. Two new P2 findings remain: dead /usr/bin LookPath checks on Windows in openBrowser (harmless fallback always fires), and RFC3339-only gap detection in segmentTrips that silently merges all breadcrumbs into one trip if BMW returns non-RFC3339 location timestamps. Neither causes data corruption or auth failures.

library/devices/bmw-cardata/internal/cli/trips.go (RFC3339 gap-detection assumption), library/devices/bmw-cardata/internal/cli/auth_login.go (dead LookPath on Windows)

Important Files Changed

Filename Overview
library/devices/bmw-cardata/internal/cli/trips.go Adds trips command that reconstructs drives from location breadcrumbs; gap detection silently skips non-RFC3339 timestamps, collapsing all points into one trip.
library/devices/bmw-cardata/internal/cli/auth_login.go Implements OAuth2 Device Code + PKCE S256 login flow; openBrowser has dead /usr/bin LookPath checks on Windows but falls back correctly.
library/devices/bmw-cardata/internal/store/extras.go BMW-specific SQLite migrations; COALESCE dedup unique index correctly handles NULL-ts descriptors to prevent duplicate accumulation on re-syncs.
library/devices/bmw-cardata/internal/cli/vehicles_diff.go Adds vehicles diff command comparing two telematic snapshots; correct handling of missing DB, VIN validation, and graceful empty-diff output.
library/devices/bmw-cardata/internal/client/client.go HTTP client with retry (429/5xx), adaptive rate limiting, 5-min response cache, credential masking, PKCE auth, and cross-host redirect stripping of Authorization header.
library/devices/bmw-cardata/internal/cli/cardata_persist.go Persists VSS telematic snapshots, charging history, and vehicle mappings into SQLite using INSERT OR IGNORE for dedup; best-effort writes with context.Background().

Reviews (2): Last reviewed commit: "fix(bmw-cardata): address 3 Greptile P2 ..." | Re-trigger Greptile

Comment thread library/devices/bmw-cardata/internal/cli/auth_login.go
Comment thread library/devices/bmw-cardata/internal/cli/trips.go Outdated
Comment thread library/devices/bmw-cardata/internal/store/extras.go Outdated
1. auth_login.go: openBrowser is now cross-platform (darwin/open,
   linux/xdg-open, windows/cmd /c start) and surfaces launch errors
   to stderr instead of silently dropping them.
2. trips.go: guard against bogus trip durations on RFC3339 parse
   failure (emit duration=null plus a duration_parse_error string
   correlating with the raw start_at / end_at).
3. store/extras.go: switched the telematic dedup unique index to
   COALESCE(ts, fetched_at) so NULL-ts rows (descriptors where BMW
   doesn't return a timestamp) are also deduped by fetched_at;
   drops the old ts-only index if present.

Recored in .printing-press-patches/bmw-cardata-greptile-round-fixes.json.
@github-actions

Copy link
Copy Markdown
Contributor

@greptileai review

Auto-nudge from greptile-policy-gate.yml because no Greptile Review check appeared for 171f02f3e9cd5de272437b9be8ce0199dfd3ad56 after 184s. This usually means the PR is over Greptile auto-review size cap; manual triggers bypass it.

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.

1 participant