A faithful recreation of the VW Digifiz digital instrument cluster, built to run fullscreen on a Raspberry Pi and driven by live sensor data.
The Digifiz is one of the best looking clusters ever fitted to a production car, and original units are getting rare, expensive and hard to keep alive. The goal here is to save the design: rebuild it in software, pixel for pixel, so it can be run on a modern panel — and eventually fitted to my own project bike once the rest of the pieces come together.
Started as a fork of GFunkbus76/Digifiz-Dash, which supplied the original artwork and the idea of driving it from pygame. The code has since been rebuilt around a package layout, a data-source abstraction, resolution-independent rendering and a test suite.
The sweep scenario on the demo source: every gauge, every lamp and the
odometer from end to end, while the MFA steps to its next mode every 3 seconds.
The full video on YouTube (56 s) starts from a
clean terminal, with the intro frames being extracted and the intro playing
before the dash.
tools/extract_intro.sh das_auto.mp4 1366x768
.venv/bin/python main.py --source demo --scenario sweep --mfa-cycle 3 --size 1366x768- Tachometer, speedometer, clock, odometer and trip counter.
- MFA panel with all six readings and the original mode chips, cycling clock → trip KM → L/100KM → KM/H → oil °C → ambient °C and back round. One chip lights at a time, as on the real cluster.
- Four aux gauges: coolant temperature, exhaust gas temperature, oil pressure and boost.
- Ten indicator lamps plus the fuel reserve warning.
- A startup bulb check that sweeps every gauge and lights every lamp.
- Resolution independent: the artwork is authored at 1920x720 and scaled once at startup to whatever panel it finds, letterboxed and centred. An 800x480 5 inch panel and a 1920x720 stretched cluster LCD both work with no code changes.
- Runs with no data at all, so the look can be worked on anywhere.
No hardware, no broker, one command:
python3 -m venv .venv
.venv/bin/pip install -U pip -r requirements.txt
.venv/bin/python -m digifiz.app --source demo --scenario drive --size 800x480 --debug--debug overlays frame timing, the active source and every live value.
For the whole thing end to end — intro, bulb check, then every scenario with the MFA stepping through its modes and the odometer running:
tools/demo_suite.sh 800x480 15Every input steps through the same list in the same order, because they all end up calling the same code:
| Input | How |
|---|---|
| Keyboard | m or → for next, ← for previous |
| GPIO button | --mfa-button-pin 17, button between that pin and ground |
| Arduino button | send mfabtn=1 / mfabtn=0 in the serial line |
| MQTT | publish to cabin/mfa_next/state |
| Rotary switch | publish a position to cabin/mfa_mode/state, or send mfa=3 over serial |
| Timer | --mfa-cycle SECONDS |
A button input is read as a press on any change to a non-zero value, so both a
0/1 edge and a press counter work, and holding the button down does not race
through the modes. Esc or q quits.
The GPIO button needs pip install gpiozero lgpio on the Pi. Without it the
dash logs a warning and carries on.
| Flag | What it does |
|---|---|
--selftest [SECONDS] |
Bulb check on startup: all gauges sweep, all lamps light |
--mfa-cycle SECONDS |
Step the MFA automatically (default 4s with demo) |
--odometer KM / --trip KM |
Start the counters somewhere believable |
--time-scale N |
Run simulated time N times faster, so kilometres tick over while you watch |
--size WxH |
Preview a panel you do not physically have |
Demo distance is never written back to odo.txt, so a demo cannot inflate the
real odometer.
Values arrive in engineering units — rpm, °C, psi, litres, km/h — and the dash
maps them onto the artwork itself. Pick a source with --source or the
DIGIFIZ_SOURCE environment variable:
| Source | What it is |
|---|---|
mqtt (default) |
Subscribes to a broker. The intended path for a real install. |
demo |
Synthetic data generated in-process. No broker, no hardware. |
serial |
Reads keyed lines from an Arduino over USB serial. |
obd |
Polls an OBD-II adapter. Needs a car with an ECU that speaks it. |
Topics, all publishing plain numbers:
engine/rpm/state rpm 0..5000
engine/coolant/state °C 50..120
engine/egt/state °C 0..500
engine/oilpressure/state psi 0..80
engine/boost/state psi 0..30
engine/fuel/state litres 0..60
engine/consumption/state L/100km 0..30
engine/oil_temp/state °C 40..150
cabin/speed_cv/state km/h 0..199
cabin/mfa_next/state button, any change to non-zero steps one mode
cabin/mfa_mode/state mode index or name, e.g. 3 or "trip"
cabin/avg_speed/state km/h 0..199
cabin/outside_temp/state °C -40..60
indicator/<name>/state 0 or 1
Indicator names: illumination, foglight, defog, highbeam, leftturn,
rightturn, brakewarn, oillight, alt, glow.
Run it with a broker and the simulator:
sudo apt install mosquitto mosquitto-clients
tools/run_dev.sh --scenario driveOr by hand:
.venv/bin/python tools/mqtt_sim.py --scenario drive --rate 20 --verbose
.venv/bin/python -m digifiz.app --source mqtt --size 800x480 --debugOne line per update, keys optional, so a sketch can send only what it has:
D rpm=1850 egt=430 boost=12.4 oilp=45 clt=88 fuel=32 spd=62 ind=0x1A4
Recognised keys: rpm, egt, boost, oilp, clt, fuel, spd, oat,
cons, oilt, avg, plus mfabtn (mode button) and mfa (absolute mode).
ind is a bitfield over the ten indicator lamps, bit 0 first.
DIGIFIZ_SERIAL_PORT=/dev/ttyUSB0 .venv/bin/python -m digifiz.app --source serialUsed by both --source demo and tools/mqtt_sim.py:
| Scenario | What it exercises |
|---|---|
idle |
Warm engine, stationary. Proves idle frames are skipped. |
drive |
A drive cycle with turbo lag, EGT inertia and a warm-up. |
sweep |
Every gauge end to end, and with --source demo the odometer from 000000 to 999999 too. Checks all artwork and the layout. |
warnings |
Each lamp in turn, blinking turn signals, fuel below reserve. |
cold-start |
Glow plugs, cold coolant, oil pressure lamp clearing. |
An optional splash plays before the dash comes up. The clip is converted to pre-scaled PNG frames once, so nothing is decoded at runtime:
tools/extract_intro.sh das_auto.mp4 800x480
.venv/bin/python -m digifiz.app --intro --size 800x480Extraction uses ffmpeg, or GStreamer if ffmpeg is not installed. Frames land in
images/intro/ and are not tracked, so re-run it for each panel size. Without
them, --intro simply skips.
The handover into the dash is built to be seamless:
- Black frames at the start and end of the clip are trimmed on extraction, so the intro is animation rather than a dead screen. The supplied clip loses 2.2 seconds of black this way.
- The dash's artwork loads during playback, a surface at a time in whatever is left of each frame's budget, so there is no pause once the intro ends.
- The last intro frame dissolves into the first dash frame over half a second
(
DIGIFIZ_INTRO_FADE, 0 to cut straight).
The dash is built to sit quietly on a Pi rather than peg a core:
- Every image is decoded, format-converted and pre-scaled once at startup.
- Rendered text is cached; the dash uses very few distinct strings.
- A frame is drawn only when it would actually look different. A sensor moving inside one gauge step, or a rounding-identical reading, repaints nothing — at idle roughly 98% of frames are skipped.
- The odometer is read once and written back atomically, at most every 30 seconds, to protect the SD card.
- Under
SDL_VIDEODRIVER=kmsdrmit draws straight to the panel with no X11, no Wayland and no compositor.
All coordinates live in digifiz/layout.py in 1920x720 logical space.
Geometry scales them to the real panel once at startup, so retargeting a
different screen never means touching drawing code.
See deploy/README.md for packages, the HDMI mode for a 5 inch panel, the KMS/DRM setup and the systemd unit.
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest tests -qCovers unit-to-frame scaling, the MQTT and serial parsers, odometer persistence and debounce, layout scaling at several panel sizes, the simulator staying renderable, MFA mode switching and chip lighting, and the repaint gate. The renderer tests run headless on SDL's dummy video driver.
Sensor data comes in from an Arduino, an OBD-II scanner or MQTT, is turned into one signal model, and is drawn by the renderer onto the display.
Raw telemetry from the vehicle, a simulator or a recorded ride, processed into a derived vehicle state with calibration and fault detection, before it reaches the visualizer.
The sources are docs/diagrams/*.excalidraw; open them at
excalidraw.com to edit, then re-export the PNGs.
digifiz/ the dash itself
app.py display setup, render loop, frame pacing
config.py tunables and environment variables
layout.py every coordinate, in logical space
mfa.py the MFA's modes, their chips and the cycle order
button.py a physical MFA button on a GPIO pin
assets.py load, convert and pre-scale artwork once
render.py drawing and the repaint gate
scaling.py engineering units to artwork frames
signals.py the one description of every value and topic
simulation.py synthetic engine behaviour
sources/ mqtt, demo, serial, obd
tools/ simulator, dev runner, intro extraction and trimming
deploy/ systemd unit and Raspberry Pi notes
docs/ demo video, architecture diagrams and their sources
images/, fonts/ artwork
arduino-node-red/ sketches and notes for the sensor hardware
tests/
- Original project and artwork: GFunkbus76, itself inspired by ManxGauged and miata-dash.
- Seven-segment fonts: DSEG by Keshikan.
Not affiliated with Volkswagen. The Digifiz design is theirs; this is a tribute to it.
Work in progress, and a hobby project. If you fit something like this to a vehicle, it is on you to make sure it does not distract you or replace anything safety critical, and to check your local rules. Use at your own risk.


