An in-cab gauge display that shows what your factory dash doesn't. It reads standard OBD-II on any vehicle, and where a profile exists it also reads the manufacturer-specific parameters — transmission temperature, oil pressure, EGT, DPF pressure, fuel rail pressure, DEF level — over a cheap Bluetooth adapter on a small dashboard screen.
Install it · Supported vehicles · Add your vehicle · Report a bug · Case STLs
Contents
One firmware image holds every vehicle profile and picks the right one automatically from the car's VIN on connect (with a Pick Vehicle menu override). It's validated on a 2025 GM Sierra 1500 3.0L Duramax (LZ0, Global B); a BMW 535i (F10) is mapped in depth from on-car scans, and an Audi Q5 (2.0T) and Jeep Wagoneer (5.7L Hemi) are skeleton profiles. Every major US full-size pickup back to 2003 is recognized by VIN and named on the boot splash — Ford Super Duty (2003–26) and F-150 (2010–23), Ram 1500/2500/3500 across both the Dodge (2006–11) and Ram (2013–24) eras, and Chevrolet/GMC 1500, HD, 2500 and 3500 through the GMT900, K2XX, T1XX and current platforms (2010–26). Recognized vehicles run the standard Mode-01 gauges rather than an enhanced profile (see Vehicles it works on).
Each of those eras needed its own VIN research pass, because the tonnage alphabet changes
between them — the same character means 2500 in one generation and 3500 in the next. Where a
pattern could not be verified the vehicle is deliberately left unidentified rather than guessed:
the derivation method, and the two ways it goes wrong, are written up in
docs/VEHICLES.md.
The enhanced parameters above are not standardized and no manufacturer publishes them, so adding
a vehicle means discovering its PID map on the vehicle itself — the tooling for that is included
(tools/obd_scan).
This project was built with substantial help from an AI coding assistant (Anthropic's Claude) — firmware, the scanner tooling, tests, and documentation. Every change is reviewed and gated by CI (host + fuzz tests, device builds, secret/PII scans), and each vehicle profile is validated against a real vehicle before it is trusted. Nothing here is auto-generated and left unchecked.
Tiles grouped by task, one page at a time. One rotary knob drives everything: turn to move, press to zoom a tile, hold for settings. Out-of-range values turn amber, then red.
Every screenshot below is the GMC Sierra Duramax profile — 7 pages, 27 tiles. Other vehicles ship fewer and different pages; see Vehicles it works on.
| TOWING | POWER |
|---|---|
![]() |
![]() |
| Transmission · coolant · oil pressure · exhaust gas | Boost · horsepower · RPM · engine load |
| REGENERATION | RANGE |
|---|---|
![]() |
![]() |
| DPF pressure · fuel rate · NOx · rail pressure | Fuel and DEF level + gallons-to-fill |
| TRIP | DIAGNOSTICS | MISCELLANEOUS |
|---|---|---|
![]() |
![]() |
![]() |
| Instant + average economy | Air flow · EGR valve · charge air · intake | Speed · voltage · oil temp |
Press the knob to zoom one tile, with a rolling trend graph coloured by alarm zone — a 5-minute window. The theme follows sunrise and sunset from the on-board clock; units switch imperial/metric.
| Focus view | Night theme | Metric |
|---|---|---|
![]() |
![]() |
![]() |
Every reading is checked against per-stat thresholds set in the vehicle profile: amber on a warn crossing, red on a critical one, with the trend line coloured per sample.
| Warning tile | Error tile | Trend graph, all three zones |
|---|---|---|
![]() |
![]() |
![]() |
| TRANSMISSION in the amber warn band | TRANSMISSION over the red critical line | Green → amber → red across the window |
The status bar shows link and logging state at a glance: Bluetooth icon, clock, page, and the SD-logging indicator.
Everything logs to a microSD card at 1 Hz. Full detail — every tile, the alarm model, the
status-bar states: docs/DISPLAY.md. The layout is editable (one table
in the vehicle profile): docs/CUSTOMIZING-VIEWS.md.
All profiles ship in one image; the firmware auto-selects by VIN on connect. An unrecognized VIN falls back to a generic Mode-01 profile, and Settings → Pick Vehicle overrides and locks the choice.
There are three tiers, and the difference between the first two is worth being clear about:
| Tier | What the dash does | What you see |
|---|---|---|
| ✅🟢🟡 Profiled | Reads manufacturer-specific (Mode 22 / UDS) parameters | 3–8 pages of enhanced stats — trans temp, DPF, EGT, regen… |
| 🔵 Recognized | Names the vehicle, and picks a Standard+ layout from the engine in its VIN | 3–4 pages of legislated SAE J1979 parameters |
| ⚪ Unknown | Nothing vehicle-specific | 2 Generic pages, no caption |
Recognized is not the same as profiled. A recognized vehicle has never been scanned, so it gets Standard+ — every parameter that SAE J1979 legislates (oil temp, EGT, fuel rate, torque, barometric, ambient, EGR, NOx…), laid out for the diesel or gas engine its VIN identifies. What it does not get is manufacturer-specific data — transmission temp, DPF differential pressure, regeneration state, DEF level — because those are undocumented and can only be discovered by scanning the vehicle itself.
Standard+ cannot show a wrong number. A vehicle that doesn't support one of these PIDs answers NO DATA and the tile stays blank. Alarms are off on every Standard+ tile except coolant and volts, since no thresholds have been sourced for any of these vehicles.
| Manufacturer | Model | Engine | Years | Pages | Status |
|---|---|---|---|---|---|
| Audi | Q5 (typ FY) | 2.0T TFSI EA888.3 | 2018–20 | 3 — TEMPERATURES · DRIVE · AIR | 🟡 Skeleton — details |
| BMW | 535i (F10) | N55 3.0L turbo I6 | 2011–15 | 6 — ENGINE · DRIVE · MISCELLANEOUS · TRIP · FLUIDS & FUEL · POWER | 🟢 In use — the most deeply mapped profile after the Sierra: oil temperature, fuel rate, crank torque and horsepower all measured on-car. Alarms remain off on everything but coolant and volts — details |
| Chevrolet | Silverado 1500 | 3.0L Duramax LZ0 | 2023–26 | 7 — same profile as the Sierra | ✅ Expected, not separately tested |
| Chevrolet | Silverado 1500 (gas) | 5.3L · 6.2L V8 · 2.7L I4 turbo | 2022–26 | 3 — Standard+ Gas | 🔵 Recognized — never scanned |
| Chevrolet | Silverado 1500 · 2500 · 3500 | (engine not identified) | 2014–18 (K2XX) · 2010–13 (GMT900) | 3 — Standard+ Gas | 🔵 Recognized — never scanned |
| Chevrolet | Silverado HD | 6.6L Duramax · 6.6L V8 | 2020–24 | 4 / 3 — Standard+ Diesel or Gas | 🔵 Recognized — 2500/3500 not distinguished |
| Chevrolet / GMC | Silverado / Sierra 1500 | 3.0L Duramax · 5.3L · 6.2L V8 · 4.3L V6 · 2.7L I4 turbo | 2019–21 (T1XX) | 4 / 3 — Standard+ Diesel or Gas | 🔵 Recognized — never scanned |
| Ford | F-150 | (engine not identified) | 2010–23 | 3 — Standard+ Gas | 🔵 Recognized — never scanned |
| Ford | F-250/350/450/550 Super Duty | 6.7L Power Stroke | 2020–26 | 8 — ENGINE · THERMAL · POWER · TRIP · AIR · EMISSIONS · AMBIENT · RANGE | 🟡 Skeleton — scanned 2026-08-09; trans temp + gear confirmed, not yet run on the vehicle — details |
| Ford | F-250/350/450/550 Super Duty | 6.7L Power Stroke (2011–19) · 6.2L · 7.3L V8 | 2011–26 | 4 / 3 — Standard+ Diesel or Gas | 🔵 Recognized — pre-2020 diesels are the 6R140, a different transmission, so they stay on Standard+ |
| Ford | F-250/350/450/550 Super Duty | 6.0L · 6.4L Power Stroke · 5.4L · 6.8L V10 | 2003–09 | 4 / 3 — Standard+ Diesel or Gas | 🔵 Recognized — a different engine alphabet from the 2011+ Super Duty |
| GMC | Sierra 1500 | 3.0L Duramax LZ0 | 2023–26 | 7 — TOWING · POWER · REGENERATION · RANGE · TRIP · DIAGNOSTICS · MISCELLANEOUS | ✅ Validated on a real vehicle |
| GMC | Sierra 1500 (gas) | 5.3L · 6.2L V8 · 2.7L I4 turbo | 2022–26 | 3 — Standard+ Gas | 🔵 Recognized — never scanned |
| GMC | Sierra 1500 · 2500 · 3500 | (engine not identified) | 2016–18 · 2014–15 (K2XX) · 2011–13 (GMT900) | 3 — Standard+ Gas | 🔵 Recognized — the tonnage alphabet changes between all three eras |
| GMC | Sierra HD | 6.6L Duramax · 6.6L V8 | 2020–24 | 4 / 3 — Standard+ Diesel or Gas | 🔵 Recognized — 2500/3500 not distinguished |
| Jeep | Wagoneer (WS) | 5.7L Hemi eTorque | 2022–23 | 4 — TEMPERATURES · DRIVE · POWER · MISCELLANEOUS | 🟡 Skeleton — details |
| Ram | 1500 · 2500 · 3500 | 5.7L · 6.4L · 6.2L HEMI · 6.7L Cummins · 3.6L V6 · 3.0L EcoDiesel | 2013–24 | 4 / 3 — Standard+ Diesel or Gas | 🔵 Recognized — never scanned |
| Ram (Dodge) | 1500 · 2500 · 3500 | (engine not identified) | 2006–11 | 3 — Standard+ Gas | 🔵 Recognized — pre-2013 is the Dodge era (1D7/3D7); 3500 identified 2006–07 only, MY2012 is a vPIC data gap |
| (any other) | — | — | — | 2 — ENGINE · AIR | ⚪ Generic — standard OBD-II only |
Gas cars have no DPF, DEF or regeneration, so those pages don't exist for them — a gas car
gets pages of what its engine actually reports. The BMW is the worked example: a 1,792-probe
sweep of its enhanced blocks took it from 3 pages to 6, including crank torque and horsepower
from a DID (2258BA) that appears in no published BMW PID table. Its 224402 oil temperature
sits in a block the scanner preset had never swept.
Enhanced (Mode 22 / UDS) PIDs are manufacturer-specific and undocumented, so adding a vehicle
means discovering its PID map on the actual vehicle. Standard OBD-II parameters (RPM, speed,
coolant, load) work anywhere; the enhanced ones only where a profile exists. Per-vehicle
detail and how VIN selection works: docs/VEHICLES.md.
| Part | Detail |
|---|---|
| Display / MCU | Elecrow CrowPanel Advance 3.5" (ESP32-S3-WROOM-1-N16R8), 480×320 IPS |
| Input | Arduino Modulino Knob rotary encoder (I²C) |
| Encoder cable | SparkFun Qwiic-to-Grove cable (the board is Grove, the encoder is Qwiic) |
| Storage | A microSD card (FAT32) for logging — the slot is on-board |
| OBD adapter | A BLE ELM327 — see Adapters |
| Power | Vehicle USB (switched 5 V) → board USB-C |
Assembly is one cable. The panel has a Grove I²C plug and the knob has a Qwiic plug,
so the SparkFun adapter cable bridges them. Everything else is on-board or plug-in. Full guide:
docs/WIRING.md.
Case — three printed parts plus 8× M3 heat-set inserts (4 mm long, 5 mm OD) and 4× M3×10 + 4× M3×6 screws. STLs, print settings and photos:
→ https://www.printables.com/model/1788789-obd-gauge-cluster-case
BOM and dimensions: hardware/. Why this board and this input method, and what
was tried first: docs/HARDWARE.md.
This is the only supported board. An earlier Elecrow WROVER-B was retired — see
docs/HARDWARE.md for what it was and why the CrowPanel replaced it.
Prerequisites: PlatformIO Core, Python 3.12, and git. Flashing the firmware is verified
on macOS and Linux; Windows is untested. The scanner (tools/obd_scan) runs on all three —
its full test suite is a required CI job on windows-latest. The first install is over USB;
everything after that is done from your phone and the knob.
1. Flash the firmware (once, over USB). Connect the CrowPanel with a data USB-C cable and, from a clone of this repo:
pio run -e crowpanel_obd -t upload
Wrong port? Find it with ls /dev/cu.usbmodem* (macOS) or ls /dev/ttyACM* (Linux) and pass
it with --upload-port.
2. Power it in the vehicle. Plug into a switched USB port. The dash boots to a connecting screen.
3. Provision WiFi and location (from your phone). Long-press the knob → WiFi setup. The
dash raises a per-device network OBD-XXXX with a random password shown on screen; join it
and a setup page opens at http://192.168.4.1. Add your WiFi (needed for updates) and your
location (enables automatic day/night). Tap Done and the dash reboots.
4. Plug in the OBD adapter. Get a BLE / "Bluetooth 4.0" ELM327 — the dash scans and auto-connects, no pairing step. It must be BLE: the CrowPanel's ESP32-S3 has a BLE-only radio with no Bluetooth Classic support, so a classic/SPP adapter cannot be made to work.
| Adapter | Transport | Build | Status |
|---|---|---|---|
| Vgate vLinker MS | BLE | crowpanel_obd |
✅ Validated — |
| Vgate iCar Pro BLE 4.0 | BLE | crowpanel_obd |
✅ Validated — and works out of the box |
Generic CC2541 / 0xFFE0 / 0xFFF0 clones |
BLE | crowpanel_obd |
🟡 Should work — not bench-tested |
| Any PIN-pairing classic-BT ELM327 | Classic BT | — | ❌ Unsupported — the ESP32-S3 on the CrowPanel has no Bluetooth Classic radio at all, only BLE. Not a software limit; nothing can be done in firmware |
| OBDLink MX+ / CX | BLE (proprietary) | — | ❌ Unsupported |
⚠️ The vLinker MS does not work out of the box. It ships in a Classic/MFi-only mode and will not advertise over BLE until you switch it to BT+BLE once, using Vgate's own updater app on a phone. Do this before you go looking for faults in the dash — a stock adapter simply never appears in the scan.The iCar Pro BLE 4.0 needs no such step — validated on the dash, plugged in and linked with nothing to configure. If you are buying an adapter now, that makes it the easier of the two.
Why each verdict, the GATT profiles supported, and how to report a working adapter:
docs/ADAPTERS.md. To switch adapters later, use Forget adapter in the
settings menu.
5. Set the clock (optional) via Set date/time — it backs up to the coin cell, so you only do it once.
6. Set the fuel tank size — only if a FILL tile reads SET UP. Usually there is nothing
to do: the vehicle profile already carries its own capacity, and the dash uses it
automatically. There is no auto-detection, because a VIN does not encode tank size — the
figure is a constant in the profile, with a Settings override that wins when you set one.
You only need this in two cases: a variant the VIN cannot distinguish (a Ford Super Duty is 29 / 34 / 48 gal depending on wheelbase), or an aftermarket tank.
Capacity feeds exactly two tiles — DSL+ and DEF+, which turn fuel level % into gallons
to fill (they sit on the RANGE page on profiles that have one). Every other readout,
including MPG, is unaffected, so if you do not care about a gallons-to-fill number you can
ignore tank size entirely. When the capacity is unknown the tile says SET UP rather than
showing a number, deliberately: zero capacity would otherwise render a confident
"0.0 gal — nothing to add" at any real fuel level.
Gauges appear once the adapter links. From here, updates are over-the-air — no cable.
Full walkthrough, per-OS notes and troubleshooting: docs/INSTALL.md.
The dash updates itself over WiFi — Settings → Check update. It fetches the published manifest, refuses anything not signed by this project's key, verifies a SHA-256 of the image, and flashes into a spare slot. Failure at any step leaves the running firmware untouched. No cable.
If an update says the server is unreachable, suspect power or signal before the network —
the message on screen tells you which half failed. Diagnosis, in order:
docs/OTA.md.
Releases publish one image, crowpanel_obd.bin — not one per vehicle, since all profiles
ship together and are selected at runtime by VIN. Signing, anti-rollback, hosting updates for
a fork, and cutting and verifying a release (maintainers):
docs/OTA.md.
The firmware, transport, UI, logging and OTA are vehicle-agnostic; the only per-vehicle part is
a profile in src/vehicles/ (PID table, decoders, thresholds, layout, tank
sizes). Mapping a new vehicle needs one thing this project cannot supply remotely: the
vehicle.
tools/obd_scan/ maps an unknown vehicle's enhanced PIDs from a laptop. It is read-only by
construction — only OBD read services and an allow-listed set of AT commands can be
transmitted; writes, routines, resets and clears are rejected in code, because it runs on
vehicles that may not be yours.
Windows, macOS and Linux. Bluetooth or WiFi. One command for everything you can do parked:
pip install numpy pandas # required — the CLI will not start without them
pip install bleak # only if your adapter is BLE
cd tools
python3 -m obd_scan --ble auto --vehicle auto -o scan/
On Windows use py -m obd_scan … — a python.org install provides python and the py
launcher, not python3. Everything else is identical.
That reads the VIN, finds which CAN headers answer, discovers which enhanced PID blocks exist, sweeps them, and works out which of the results are worth logging — on one adapter connection, then it tells you to go drive.
| Your adapter | Use |
|---|---|
| BLE (vLinker MS/MX, OBDLink CX, Vgate iCar Pro BLE) | --ble — or --ble vlinker to pick by name |
| WiFi (iCar Pro WiFi, most cheap clones) | --host 192.168.0.10 (the default; omit it) — no bleak needed |
Then drive — a cold start is worth more than a long drive, because a thermal ramp is what
separates an oil temperature from a coolant temperature — and run the two commands auto
prints for you:
python3 -m obd_scan --ble log --sweep scan/sweep.json --pids <the list auto chose> -o drive.csv
python3 -m obd_scan correlate drive.csv -o report.md
auto— discover and sweep the vehicle, parked. Minutes, not the hour the old stage-by-stage flow took.- Drive — cold start if you can, then
log. correlate— rank every candidate against known reference signals (RPM, speed, coolant) and write a report naming the likely identities.- Add a
src/vehicles/<your_vehicle>.cppprofile and open a pull request.
obd-discover does the same job from an
Android phone and a BLE adapter — no laptop in the passenger seat. It is a separate
project by @radiohound (MIT), built to feed this one:
it probes the same Mode-22 address space, and exports in the formats obd_scan
already reads, so a capture can be contributed without editing anyone's source or
forking anything.
It also probes Mode-21 on pre-CAN vehicles, which obd_scan does not — so it reaches
older cars this scanner cannot. Tested on one adapter (Vgate iCar Pro BLE 4.0).
Either tool produces a capture worth sending. If you have a laptop, the command above is the shortest path; if you do not, that app is.
Your vehicle does not need to be one of the six. auto falls back to discovering the
enhanced blocks by measurement rather than needing a preset that already lists them — which is
how a BMW oil-temperature PID was found in a block no preset had ever swept.
Running the stages by hand
auto chains these, but each still runs on its own — useful for re-sweeping one block, or
resuming after a link drops:
python3 -m obd_scan --ble census --vehicle auto -o census.json
python3 -m obd_scan --ble discover --census census.json -o discover.json # unlisted makes
python3 -m obd_scan --ble sweep --census census.json --blocks-from discover.json -o sweep.json
python3 -m obd_scan --ble log --sweep sweep.json -o drive.csv # during a drive
python3 -m obd_scan correlate drive.csv -o report.md
Start with docs/PORTING-LESSONS.md (the method and its pitfalls)
and docs/SIERRA-GATE-RUNBOOK.md (a worked example).
docs/FORD-STATUS.md is a partial head-start on the Ford 6.7L Power
Stroke. Issues and PRs welcome — including drive logs from a vehicle you can't finish mapping
yourself. How the scanner works internally: docs/obd-scan-design.md.
PlatformIO builds the firmware; the tools and tests are Python 3.12.
pio run -e crowpanel # dash UI with mock data (no adapter)
cd test && make # firmware logic tests + fuzz (no hardware)
cd tools/obd_scan && python3 -m pytest tests -q # scanner tests
Repository layout and the contribution workflow are in
CONTRIBUTING.md.
| Document | What it covers |
|---|---|
docs/DISPLAY.md |
Every tile per profile, the alarm model, focus view, theming, status bar |
docs/VEHICLES.md |
VIN auto-select, per-vehicle detail, what "not supported" means |
docs/INSTALL.md |
Full setup walkthrough, per-OS notes, troubleshooting |
docs/ADAPTERS.md |
Adapter compatibility, GATT profiles, reporting a working adapter |
docs/OTA.md |
Update mechanics, signing, release pipeline, hosting a fork |
docs/HARDWARE.md |
Why this board and this input, and what was tried first |
docs/WIRING.md |
The one I²C cable, and what is on-board |
docs/CUSTOMIZING-VIEWS.md |
Rearranging the tiles and pages |
docs/PORTING-LESSONS.md |
The method for mapping a new vehicle, and the traps |
docs/SIERRA-GATE-RUNBOOK.md |
A worked example of the discovery method |
tools/obd_scan/README.md |
Running the scanner — BLE/WiFi, auto, every stage and flag |
| obd-discover | The same job from an Android phone, no laptop — separate project, MIT, exports into obd_scan's formats |
docs/obd-scan-design.md |
The scanner's design |
docs/AUDI-STATUS.md · docs/BMW-STATUS.md · docs/FORD-STATUS.md · docs/JEEP-STATUS.md |
Per-vehicle port status |
ROADMAP.md |
Where the project is going, and where help is most useful |
The most useful contribution is another vehicle — it needs something the maintainer cannot do remotely: the vehicle.
| You want to… | Do this |
|---|---|
| Add a vehicle, or share a scan / drive log you couldn't finish | Open a New vehicle issue — a partial map is still useful. Method: Port your vehicle |
| Report something broken | Open a Bug report |
| Report a security problem | Not a public issue — use private reporting, see SECURITY.md |
| Send code or docs | Read CONTRIBUTING.md (layout, tests, PR checks, pre-commit hooks) |
| Find where help is needed | ROADMAP.md |
This reads diagnostic data; it does not modify the vehicle, and the scanner cannot transmit
a write. It is a hobby project provided without warranty (see LICENSE) —
you are responsible for what you plug into your own vehicle.
Enhanced PID values are facts measured from a vehicle through its legislated OBD-II port; no manufacturer documentation was copied to produce them. Some commercial tools compute derived parameters (air density, corrected horsepower) under patent — those are not implemented here.
This project's own code is MIT — see LICENSE.
The firmware statically links third-party libraries under other licences, and the compiled
binary is distributed both as a release asset and over the air from gh-pages. Their notices are
reproduced in THIRD-PARTY-NOTICES.md, which ships alongside the
binary as well as living here, because several of those licences require their notices to
accompany a redistribution in binary form.
| Linked into the firmware | Licence |
|---|---|
| Arduino core for ESP32 | LGPL-2.1-or-later |
| ESP-IDF, NimBLE-Arduino | Apache-2.0 |
| LovyanGFX | FreeBSD/BSD-2, with retained MIT and BSD notices |
| Modulino | MPL-2.0 |
| LVGL | MIT |
On the LGPL-2.1 component. The Arduino core is LGPL-2.1 and is statically linked. LGPL-2.1
section 6 requires that you be able to modify that library and relink it into a working
executable — and you can: the whole of this project is published as source under MIT, every
dependency is pinned to an exact version in platformio.ini, and
the build is one reproducible command. Substitute a modified Arduino core and
rebuild.
Every component's licence is also recorded per-component in the CycloneDX SBOM published with
each release (crowpanel_obd.bin.cdx.json).

















