Skip to content

About

ESP32-S3 OBD-II gauge cluster — reads enhanced (Mode-22/UDS) PIDs your factory dash hides; auto-selects a vehicle profile by VIN; signed OTA.

Topics

Resources

Contributing

Security policy

Stars

6 stars

Watchers

0 watching

Forks

Repository files navigation

OBD Gauge Cluster

CI OpenSSF Scorecard OpenSSF Best Practices OTA Printables License: MIT Claude

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

The unit on the dash, running the towing page

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).

AI assistance

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.

What it does

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
TOWING POWER
Transmission · coolant · oil pressure · exhaust gas Boost · horsepower · RPM · engine load
REGENERATION RANGE
REGENERATION RANGE
DPF pressure · fuel rate · NOx · rail pressure Fuel and DEF level + gallons-to-fill
TRIP DIAGNOSTICS MISCELLANEOUS
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
Focus Night 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
Warning Error Alarm 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.

Status-bar states

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.

Vehicles it works on

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.

Hardware you need

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.

Wiring: one I²C cable, panel to knob

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

3D-printed case, CAD assembly

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.

Install 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.

Settings menu, reached by a long knob-press

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 — ⚠️ ships in Classic/MFi mode; see below
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.

Updates

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.

Port your vehicle

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.

One command, any adapter, any OS

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
  1. auto — discover and sweep the vehicle, parked. Minutes, not the hour the old stage-by-stage flow took.
  2. Drive — cold start if you can, then log.
  3. correlate — rank every candidate against known reference signals (RPM, speed, coolant) and write a report naming the likely identities.
  4. Add a src/vehicles/<your_vehicle>.cpp profile and open a pull request.

No laptop? Use the Android app

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.

Building from source

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.

Documentation

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

Contributing

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

Safety and scope

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.

License

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).

About

ESP32-S3 OBD-II gauge cluster — reads enhanced (Mode-22/UDS) PIDs your factory dash hides; auto-selects a vehicle profile by VIN; signed OTA.

Topics

Resources

Contributing

Security policy

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages