🔷 Sapphire Dashboard • 🚀 Live Emulator • Screenshots • Features • Hardware • Quick Start • Architecture • Roadmap
"Built for the car Ford should have given us an app for."
openRS_ is a native Android app that turns your phone into a full telemetry dashboard for the Ford Focus RS MK3. It connects to a MeatPi USB (C3) or MeatPi Pro (S3) adapter — over Wi-Fi or Bluetooth Low Energy — and passively monitors the full CAN bus at ~2100 fps, decoding every parameter the car broadcasts in real time.
Unlike generic OBD apps, openRS_ is purpose-built for the Focus RS. It understands the GKN Twinster AWD system, polls TPMS tire pressures and temperatures from the BCM, decodes Ford-specific parameters across HS-CAN and MS-CAN, and presents everything in a dark, glanceable interface tuned for track days. A bundled FORScan PID catalog covers 1,149 PIDs across 8 ECU modules (PCM, OBDII, BCM, ABS, AWD, HVAC, IPC, PSCM) — and new parameters can be added from the catalog via JSON without touching code.
Try it now: klexical.github.io/openRS_/emulator — live browser emulator with animated demo data, no hardware required.
Analyse your data: klexical.github.io/openRS_ — Sapphire, the post-session analytics dashboard. Drop an export ZIP to explore charts, CAN data, and diagnostics in your browser.
DASH · POWER · CHASSIS · TEMPS · MAP · DIAG · MORE
| Screen | Description |
|---|---|
| DASH | Hero boost/RPM/speed gauges (with session peak values), 0-60 / 0-100 performance timer, real-time fuel economy (idle L/hr, instant/average MPG or L/100km, DTE), throttle/brake/clutch/fuel/battery, Temps Quick (oil, coolant, intake, oil life), G-forces (lat, lon, torque), animated AWD split bar, IPC warning lamp banner (CEL, ABS, BRK, CHRG, OIL, TEMP) |
| POWER | AFR hero cards (actual/desired/lambda), commanded AFR (CMD AFR), ETC actual/desired, WGDC, TIP, HP fuel rail PSI, low-pressure fuel rail (LP FUEL), timing, engine load, OAR, per-cylinder knock correction (KR C1–C4, colour-coded), VCT intake/exhaust, STFT/LTFT short/long-term fuel trims |
| CHASSIS | AWD detail (4 wheel speeds, wheel rotation counts, torque bar, F/R delta, L/R delta, rear bias, clutch temps L/R, clutch actuator current L/R, hydraulic pressure L/R, trans oil temp), G-force + yaw + steering with peak reset, TPMS Focus RS wireframe (pressure + temp, 3-tier colour-coded Low/Warn/High thresholds), pressure spread warning (⚠ PRESSURE IMBALANCE when spread ≥ 4 PSI), temperature range legend |
| TEMPS | Animated Ready-to-Race banner, 14 temperature cards each with a colour indicator bar (oil, coolant, intake, ambient, RDU, PTU, charge air, manifold charge, catalytic, cabin, battery, clutch L, clutch R, trans oil) |
| MAP | Live drive tracking with Google Maps — 6 colour modes (speed, drive mode, boost, throttle, lateral G, temp), start/finish markers, peak event markers (RPM/boost/lat-G/speed), pause markers, route stats HUD, weather card, custom recenter / zoom controls. Drive history grouped by date with status badges, swipe-to-delete, per-drive export (GPX/CSV/summary). |
| DIAG | DTC Scanner (full-module scan including GFM 0x7D2, count badges, freeze-frame, clear), DID Prober (scan any ECU for valid Mode 22 DIDs), PID Browser (1,149 FORScan PIDs across 8 modules, searchable), frame inventory with per-ID change tracking, SLCAN raw log, live button input feedback (drive mode toggle, suspension, ASS, ESC defeat), battery row with charge/discharge current, one-tap ZIP export (SavvyCAN/Kayak compatible) |
| MORE | Drive mode (N/S/T/D, tap-to-change with openrs-fw firmware), ESC status, firmware version display, firmware-gated features (Launch Control, Auto S/S Kill), Module Status (RDU/PDC/FENG live OBD), passive VIN decode (CAN 0x40A), connection & snapshot, in-app update checker (stable + beta channels) |
A frosted-glass bottom nav bar with vector icons + spring-animated indicator persists across all tabs. Quick Mode Dock drops down from the status bar's MODE pill from any tab for one-tap drive mode changes.
The TEMPS tab shows a warming-up / race-ready banner based on oil, coolant, RDU, PTU, and clutch temperatures. Thresholds are preset-based — configurable via Street / Track / Race in Settings:
| Sensor | Street | Track | Race |
|---|---|---|---|
| Engine Oil | ≥ 70 °C | ≥ 80 °C | ≥ 85 °C |
| Coolant | ≥ 70 °C | ≥ 75 °C | ≥ 80 °C |
| RDU | ≥ 30 °C | ≥ 30 °C | ≥ 30 °C |
| PTU | ≥ 40 °C | ≥ 40 °C | ≥ 40 °C |
The banner shows which sensors are still below threshold with live °C values. A value of −99 °C (not yet received) is treated as passing to avoid blocking warm cars on reconnect. Clutch temperatures are also factored into the race-ready check (cold threshold: < 25 °C).
| Setting | Options | Default |
|---|---|---|
| Speed unit | MPH / KPH | MPH |
| Temperature | °F / °C | °F |
| Boost pressure | PSI / BAR / kPa | PSI |
| Tire pressure | PSI / BAR | PSI |
| Low tire threshold | PSI (any value) | 30 PSI |
| Threshold preset | Street / Track / Race | Street |
| Theme | RS paint colours (Nitrous Blue, Frozen White, etc.) | Nitrous Blue |
| Keep screen on | on / off | on |
| Auto-reconnect | on / off | on |
| Reconnect interval | seconds | 10 s |
| Adapter | MeatPi USB (C3) / MeatPi Pro (S3) | MeatPi USB |
| Connection method | Wi-Fi / Bluetooth Low Energy | Wi-Fi |
| BLE device | scan + pick from in-app dialog | — |
| Update channel | Stable / Beta | Stable |
| Auto-record drives | on / off | off |
| Max saved drives | count | 50 |
| Visibility | Night / Day / Sun preset + brightness slider (0.0–1.0) | Night |
| MicroSD logging reminder | on / off | off (MeatPi Pro only) |
| Max saved ZIP exports | count | 5 |
| Odometer display | Miles / km | Miles |
| Edge shift light | on / off | off |
| Shift light colour | Accent / White / Progressive | Accent |
| Shift light intensity | Low / Medium / High | High |
| Shift light RPM | RPM threshold | 6800 |
| Tire warn threshold | PSI | 32 PSI |
| Tire high threshold | PSI | 40 PSI |
Live CAN Parameters — WebSocket SLCAN (passive at full bus speed)
All data is received passively from the CAN bus via WebSocket SLCAN at ~2100 fps. No OBD polling windows or header switching required for primary gauges.
| CAN ID | Parameters | Source |
|---|---|---|
| 0x010 | Steering wheel angle (°) with direction sign | RS_HS.dbc SASMmsg01 |
| 0x070 | Torque at transmission (Nm) | RS_HS.dbc |
| 0x076 | Throttle % | RS_HS.dbc |
| 0x080 | Accelerator pedal %, brake pedal, reverse | RS_HS.dbc |
| 0x090 | RPM, barometric pressure | RS_HS.dbc |
| 0x0C8 | Gauge brightness, e-brake, ignition status | RS_HS.dbc |
| 0x0F8 | Engine oil temp, boost pressure (gauge + baro), PTU temp | RS_HS.dbc PCMmsg07 |
| 0x130 | Vehicle speed kph | RS_HS.dbc |
| 0x138 | Clutch pedal position (%) — 10-bit Motorola × 0.1% | RS_HS.dbc PCMmsg10 |
| 0x160 | Longitudinal G-force | RS_HS.dbc |
| 0x180 | Lateral G-force, yaw rate, vertical G | RS_HS.dbc ABSmsg02 |
| 0x1E0 | Wheel rotation counts (4 × 8-bit rolling FL/FR/RL/RR) + average front wheel speed | RS_HS.dbc ABSmsg06 |
| 0x190 | 4-corner wheel speeds (15-bit Motorola × 0.011343 km/h) | RS_HS.dbc ABSmsg03 |
| 0x1A4 | Ambient temperature (MS-CAN bridged) | community research |
| 0x1B0 | Drive mode (Normal/Sport+Track/Drift) — byte 6 upper nibble, steady-state frames only (byte 4 == 0). Combined with 0x420 to resolve Sport vs Track. | RS_HS.dbc AWDmsg01 |
| 0x1C0 | ESC mode status (On/Off/Sport/Launch) | RS_HS.dbc |
| 0x420 | Track mode indicator, launch control status — byte 6: 0x10 = Normal/Sport, 0x11 = Track; bit 50 = LC active (~600 ms) |
RS_HS.dbc + empirical |
| 0x252 | Brake pressure (0–100% normalised, raw 0–4095 ADC counts) | RS_HS.dbc ABSmsg10 |
| 0x2C0 | AWD L/R rear torque (Nm) | RS_HS.dbc |
| 0x2F0 | Coolant temp, Intake Air Temp (IAT) | RS_HS.dbc PCMmsg16 |
| 0x340 | Ambient temperature only (byte 7 signed × 0.25 °C) — not TPMS | RS_HS.dbc PCMmsg17 |
| 0x360 | Odometer, engine status — odo: bytes [3:5] BE, 24-bit, 1 km/bit (~5 Hz); engine: byte 0 (0=Idle, 2=Off, 183=Running, 186=Kill, 191=RecentStart, 196=Warmup) | RS_HS.dbc + community #102 |
| 0x380 | Fuel level % (FuelLevelFiltered — Motorola 10-bit, factor 0.4 %) | RS_HS.dbc PCMmsg30 |
| 0x40A | Multiplexed VIN decode (3 pages × 6 bytes → 17-char VIN) + odometer | community (@adamsouthern) |
Note:
0x230(gear position) and0x3C0(battery voltage) do not broadcast on this vehicle. Battery voltage is polled via OBD. Gear display has been removed.
Polled via OBD Mode 22 (periodic, low-frequency):
| ECU | Request | Response | PIDs / Function | Interval |
|---|---|---|---|---|
| PCM | 0x7E0 | 0x7E8 | ETC actual (0x093C), ETC desired (0x091A), WGDC (0x0462), KR cyl 1–4 (0x03EC–0x03EF), OAR (0x03E8), Charge Air Temp (0x0461), Catalyst Temp (0xF43C), AFR actual (0xF434), AFR desired (0xF444), TIP actual (0x033E), TIP desired (0x0466), VCT intake (0x0318), VCT exhaust (0x0319), Oil Life (0x054B), HP Fuel Rail (0xF422), Fuel Level (0xF42F), Battery Voltage (0x0304) | 30 s |
| BCM | 0x726 | 0x72E | Battery SOC (0x4028), Battery temp (0x4029), Cabin temp (0xDD04), TPMS pressure LF/RF/LR/RR (0x2813–0x2816) (((256*A)+B)/3 + 22/3) * 0.145 PSI, TPMS last sensor (0x280B) temp = raw - 40 °C, pressure = (A*256+B)/20 PSI (multi-frame ISO-TP, matched by sensor ID) |
30 s |
| BCM (once) | 0x726 | 0x72E | Odometer (0xDD01) — extended session, TPMS sensor IDs (0x280F LF, 0x2810 RF, 0x2811 RR, 0x2812 LR) — 4-byte IDs for 0x280B matching | once |
| AWD module | 0x703 | 0x70B | RDU oil temp (0x1E8A), clutch temp L/R (0x1E8B/0x1E8C), trans oil temp (0x1E80), req torque L/R (0x1E90/0x1E91), demanded pressure (0x1E92), pump current (0x1E93) — all A − 40 °C or raw |
60 s |
| HVAC (candidate) | 0x733 | 0x73B | Blower %, interior temp, discharge air temp, blend doors L/R, defrost door — response handler wired, DIDs unconfirmed (use DID prober) | — |
| IPC (candidate) | 0x720 | 0x728 | Warning lamps (MIL/CEL, ABS, brake, charge, oil pressure, high temp) — response handler wired, DIDs unconfirmed (use DID prober) | — |
FORScan PID Catalog — data-driven decode: A bundled JSON catalog covers 1,149 PIDs across 8 ECU modules (PCM, OBDII, BCM, ABS, AWD, HVAC, IPC, PSCM). PidRegistry evaluates formula expressions from the catalog at runtime — new PIDs can be added via JSON without modifying Kotlin code. Decoded values accumulate in VehicleState.genericValues.
| Component | Details |
|---|---|
| MeatPi USB (C3) | Mouser — ESP32-C3, Wi-Fi + BLE, WebSocket SLCAN at ws://192.168.80.1:80/ws |
| MeatPi Pro (S3) | MeatPi — ESP32-S3, Wi-Fi + BLE + GPS + MicroSD, raw TCP SLCAN at tcp://192.168.0.10:35000 |
| Ford Focus RS MK3 | 2016–2018 (LZ platform, EcoBoost 2.3L) |
| Android phone | Android 9+ (API 28) with Wi-Fi or Bluetooth Low Energy |
- Plug the adapter into the OBD-II port (under the steering column)
- Choose your transport in Settings → Adapter and Settings → Connection method:
Wi-Fi — connect your phone to the adapter's Wi-Fi network:
| Adapter / Firmware | SSID | Password |
|---|---|---|
| Stock MeatPi USB | WiCAN_XXXXXX |
@meatpi# |
| openrs-fw USB | openRS_XXXXXX |
openrs_2026 |
| Stock MeatPi Pro | MeatPi_PRO_XXXXXX |
@meatpi# |
| openrs-fw Pro | openRS_PRO_XXXXXX |
openrs_2026 |
Bluetooth Low Energy — pair the adapter via the in-app BLE picker (BLUETOOTH_SCAN + CONNECT permissions requested at scan time, RSSI bars, filtered by service UUID 0xFFE0).
- Install openRS_ and tap the connection dot in the header.
Stock firmware works out of the box for passive monitoring. For full functionality (drive mode write, Launch Control, Auto S/S Kill, ESC control), flash openrs-fw to the adapter — v1.61 (USB) or v1.2 (Pro). See the firmware update guide.
MeatPi Pro users: The Pro defaults to ELM327 mode. You must open the Pro's web UI (
http://192.168.0.10), set the protocol to SLCAN, CAN speed to 500 kbps, and TCP port to 35000, then reboot. See the hardware setup guide for full instructions.
BLE vs Wi-Fi: BLE leaves your phone's Wi-Fi free for internet (weather, maps, in-app updates). Wi-Fi delivers slightly higher CAN throughput. The app supports both transports for both adapters.
- Android Studio Ladybug (2024.2) or newer
- JDK 17+
- Android SDK 35
git clone https://github.com/klexical/openRS_.git
cd openRS_/android
./gradlew assembleRelease
# Output: app/build/outputs/apk/release/openRS_v2.2.6.apk
# (Requires keystore — see android/docs/signing-setup.md)Open android/browser-emulator/index.html in any browser, or visit the live version:
klexical.github.io/openRS_/emulator
- All tabs animate with simulated Focus RS data (RPM, boost, speed, AWD, temps, TPMS)
- MORE tab shows drive mode, ESC, features, diagnostics
- ⚙ Settings button demonstrates the settings dialog
Drop an export ZIP from the app into the web dashboard to explore your session data:
- Dashboard KPIs, session peaks, drive mode breakdown
- 8 time-series charts (RPM, boost, speed, temps, G-force, fuel, wheel speeds, AWD torque)
- CAN frame inventory, session events, DID probe results, decode trace
- Persistent session library in your browser (IndexedDB)
System diagram
┌──────────────────────────────────────────────────────────────────────┐
│ Phone UI — Jetpack Compose + Material 3 + Haze (frosted glass) │
│ ┌──────┬───────┬─────────┬───────┬─────┬──────┬──────┐ │
│ │ DASH │ POWER │ CHASSIS │ TEMPS │ MAP │ DIAG │ MORE │ │
│ └──────┴───────┴─────────┴───────┴─────┴──────┴──────┘ │
│ HorizontalPager swipe · BottomNavBar overlay · Quick Mode Dock │
│ Compact status bar: MODE pill · ESC · conn dot · ⚙ │
│ Visibility: Night/Day/Sun brightness lerp (live recompose) │
├──────────────────────────────────────────────────────────────────────┤
│ UserPrefsStore (StateFlow) — units, thresholds, brightness, BLE │
├──────────────────────────────────────────────────────────────────────┤
│ VehicleState (StateFlow) │
│ Immutable data class · ~110 fields · genericValues map · RTR │
│ + DriveState (live recording, peaks, fuel economy) │
├──────────────────────────────────────────────────────────────────────┤
│ CanDataService (Foreground Service) │
│ Decodes CAN → VehicleState → notifies UI │
│ Auto-reconnect (Wi-Fi + BLE) · DriveRecorder · CrashReporter │
│ Auto-record on connect (if enabled) │
├──────────────────────┬───────────────────────────────────────────────┤
│ CanDecoder │ DiagnosticLogger / Exporter / DtcScanner │
│ 22+ HS-CAN decoders │ DriveExportBuilder · DiagnosticReportBuilder │
│ has420Arrived gate │ Per-ID first/last/Δ tracking + JSON export │
│ RS_HS.dbc-verified │ GFM (0x7D2) DTC scan support │
├──────────────────────┼───────────────────────────────────────────────┤
│ DriveRecorder │ FirmwareApi (Wi-Fi REST + BLE AT+FRS) │
│ Room DB · 1 Hz GPS │ DriveCommand: settle → poll → auto-correct │
│ Peak tracking │ Network.socketFactory pins Wi-Fi route │
├──────────────────────┴───────────────────────────────────────────────┤
│ PidRegistry — data-driven decode from FORScan catalog (JSON) │
│ 1,149 PIDs · 8 modules · formula evaluator · genericValues output │
├──────────────────────────────────────────────────────────────────────┤
│ ObdConstants / ObdResponseParser / SlcanParser / IsoTpBuffer │
│ PCM/BCM/AWD/PSCM/FENG/RSProt response parsers │
├──────────────────────────────────────────────────────────────────────┤
│ SlcanConnection — shared connection lifecycle │
│ Constructor: transportFactory: () -> SlcanTransport │
│ 3s SLCAN handshake · firmware probe · ISO-TP reassembly │
├─────────────────┬─────────────────────┬──────────────────────────────┤
│ WebSocketSlcan │ TcpSlcanTransport │ BleSlcanTransport │
│ Transport │ raw TCP │ BLE GATT (FFE0/FFE1/FFE2) │
│ ws://.80.1/ws │ tcp://.0.10:35000 │ MTU 247 · auto-reconnect │
│ MeatPi USB Wi-Fi│ MeatPi Pro Wi-Fi │ Both adapters │
├─────────────────┴─────────────────────┴──────────────────────────────┤
│ PCM (0x7E0/30s) · BCM (0x726/30s) · AWD (0x703/60s) │
│ STFT/LTFT · AWD clutch hydraulics · battery current · spark adv │
│ GFM (0x7D2) DTC scan · IPC/HVAC scaffolded │
├──────────────────────┬───────────────────────────────────────────────┤
│ MeatPi USB (C3) │ MeatPi Pro (S3) │
│ ESP32-C3 · Wi-Fi+BLE│ ESP32-S3 · Wi-Fi+BLE+GPS+MicroSD │
│ openrs-fw v1.61 │ openrs-fw v1.2 │
├──────────────────────┴───────────────────────────────────────────────┤
│ HS-CAN 500k │ MS-CAN 125k (bridged via GWM) │
│ 0x010–0x420 frames │ Ambient 0x340/0x1A4 (GWM bridged) │
└──────────────────────┴───────────────────────────────────────────────┘
Key design decisions
Why WebSocket SLCAN instead of ELM327 TCP? ELM327's ATMA command is not fully implemented in WiCAN firmware. WebSocket SLCAN bypasses ELM327 entirely — the app does a manual HTTP 101 Upgrade handshake, sends C / S6 / O (close/500kbps/open), and receives raw SLCAN frames. This delivers the full HS-CAN bus at ~2100 fps vs ~12 fps with polled OBD.
How does TPMS work? Tire pressures are polled from the BCM via Mode 22 (PIDs 0x2813–0x2816) every 30 seconds using the formula (((256*A)+B)/3 + 22/3) * 0.145 PSI. Tire temperatures use a two-step approach: on connect, the app polls sensor IDs from PIDs 0x280F–0x2812 (4-byte ID per tire position). Then every 30s it polls PID 0x280B ("last received TPMS sensor") — a 12-byte multi-frame ISO-TP response containing sensor ID, pressure ((A*256+B)/20 PSI), and temperature (raw − 40 °C). The sensor ID is matched against the stored map to assign the temperature to the correct tire. Per-tire temperature PIDs 0x2823–0x2826 were confirmed unsupported by the BCM (7F 22 31).
How does firmware detection work? After SLCAN initialisation, the app sends OPENRS?\r. openRS_ firmware responds with OPENRS:<version>; stock WiCAN ignores it. Every incoming CAN frame for the first 3 seconds is scanned for the probe response — this time-based window ensures the probe reply is not missed even on high-throughput buses (~1700 fps). After 3 seconds without a response, firmware latches as "WiCAN stock" for the session. MORE tab feature buttons unlock when openRS_ firmware is confirmed.
How does the FORScan PID catalog work? PidRegistry lazy-loads forscan_modules.json at first use and indexes 1,149 PIDs by (ecuResponseId, did) tuple. Each PID entry carries a formula expression string (e.g. "signed(A*256+B)/-512.0"). A custom recursive-descent parser evaluates the formula at runtime using bytes from the OBD response. All four OBD parsers (PCM, AWD, HVAC, IPC) fall through to PidRegistry for unrecognised DIDs, storing results in VehicleState.genericValues. New PIDs can be onboarded via JSON without modifying Kotlin code.
How does the DID prober work? DidProberSection (DIAG tab) sends Mode 22 UDS queries across module-specific DID ranges using sendRawQuery() — a new infrastructure method on both WiCanConnection and MeatPiConnection. Responses are classified as FOUND (0x62 positive), NRC (0x7F negative, NRC code shown), or TIMEOUT. A live progress bar, counters, and expandable result list with raw hex data make it easy to discover new ECU parameters.
How does the diagnostic system work? DiagnosticLogger (singleton) accumulates three layers throughout the session: (1) a per-ID frame inventory with firstRawHex, lastRawHex, a hasChanged flag, and up to 10 periodic raw-hex snapshots per ID sampled every 30 s; (2) a rolling 10 000-entry decode trace; (3) a real-time SLCAN log in standard candump format. On export, DiagnosticExporter bundles all three artefacts into a ZIP via FileProvider, compatible with SavvyCAN, Kayak, and python-can.
Project structure
web/ # Sapphire — post-session analytics dashboard
├── src/
│ ├── components/
│ │ ├── layout/ # Shell, NavRail, Header
│ │ ├── panels/ # Dashboard, Trip, Diagnostics, Sessions, Import
│ │ ├── charts/ # TimeSeriesChart (Recharts)
│ │ └── ui/ # MetricCard, SectionLabel, DataCell, EmptyState
│ ├── store/ # Zustand state (sessions, panels, UI)
│ ├── lib/ # ZIP import, IndexedDB, formatting
│ ├── styles/ # Design tokens (openRS_ palette)
│ └── types/ # Session, trip, diagnostic types
├── package.json # Vite + React 19 + Tailwind 4 + Recharts + Zustand
└── vite.config.ts # base: '/openRS_/'
android/
├── app/src/main/
│ ├── assets/pids/
│ │ ├── forscan_modules.json # Bundled FORScan catalog (1,149 PIDs, 8 modules)
│ │ └── combined_catalog.json # Merged PID catalog
│ ├── java/com/openrs/dash/
│ │ ├── OpenRSDashApp.kt # App singleton + driveDb/Recorder/State
│ │ ├── can/
│ │ │ ├── AdapterState.kt # Disconnected/Connecting/Connected/Idle/Error
│ │ │ ├── BleDeviceScanner.kt # BLE discovery filtered to 0xFFE0
│ │ │ ├── BleSlcanTransport.kt # BLE GATT transport (FFE0/FFE1/FFE2 + MTU 247)
│ │ │ ├── CanDecoder.kt # 22+ CAN decoders + has420Arrived gate
│ │ │ ├── DriveCommand.kt # Shared drive mode command flow (settle/poll/auto-correct)
│ │ │ ├── FirmwareApi.kt # WiFiFirmwareApi (REST) + BleFirmwareApi (AT+FRS)
│ │ │ ├── IsoTpBuffer.kt # ISO-TP SF/FF/CF reassembly
│ │ │ ├── ObdConstants.kt # Shared OBD query strings + CAN IDs + timing
│ │ │ ├── ObdResponseParser.kt # Mode 22 dispatch (PCM/BCM/AWD/PSCM/FENG/RSProt)
│ │ │ ├── PidRegistry.kt # Data-driven catalog decoder (formula evaluator)
│ │ │ ├── SlcanConnection.kt # Shared connection lifecycle (transportFactory)
│ │ │ ├── SlcanParser.kt # Shared SLCAN frame parser
│ │ │ ├── SlcanTransport.kt # Transport interface (open/read/write/close)
│ │ │ ├── TcpSlcanTransport.kt # MeatPi Pro raw TCP transport
│ │ │ └── WebSocketSlcanTransport.kt # MeatPi USB WebSocket transport
│ │ ├── data/
│ │ │ ├── DashLayout.kt # Custom dashboard cell grid
│ │ │ ├── DriveDatabase.kt # Room DB v3: drives + drive points
│ │ │ ├── DriveState.kt # Live drive state (recording/peaks/fuel)
│ │ │ ├── DtcModuleSpec.kt # ECU descriptor for DTC scan
│ │ │ ├── DtcResult.kt # DTC + status enum
│ │ │ ├── ForscanCatalog.kt # JSON catalog data classes
│ │ │ ├── FuelEconomy.kt # Real-time fuel economy (60s rolling window)
│ │ │ ├── PerformanceTimer.kt # 0-60 / 0-100 timer state machine
│ │ │ ├── SessionDatabase.kt # Room DB for session snapshots
│ │ │ ├── TripState.kt # Peak event types
│ │ │ ├── VehicleState.kt # Immutable state (~110 fields, genericValues)
│ │ │ └── WeatherData.kt # OpenWeatherMap snapshot
│ │ ├── diagnostics/
│ │ │ ├── CrashReporter.kt # UncaughtExceptionHandler + persistence
│ │ │ ├── CrashTelemetryBuffer.kt # 100-snapshot VehicleState ring buffer
│ │ │ ├── DiagnosticExporter.kt # ZIP orchestrator + share intents
│ │ │ ├── DiagnosticLogger.kt # Session-scoped collector + SLCAN log
│ │ │ ├── DiagnosticReportBuilder.kt # Summary text + JSON detail (JSONObject)
│ │ │ ├── DriveExportBuilder.kt # GPX / CSV / drive summary builders
│ │ │ ├── DtcDatabase.kt # Bundled 873-code Ford DTC lookup
│ │ │ └── DtcScanner.kt # DTC scan/clear (PCM/BCM/ABS/AWD/PSCM/GFM)
│ │ ├── service/
│ │ │ ├── CanDataService.kt # Foreground service + auto-reconnect (Wi-Fi+BLE)
│ │ │ ├── DriveRecorder.kt # Room-backed 1 Hz GPS + telemetry recorder
│ │ │ ├── HudOverlayService.kt # Floating HUD overlay
│ │ │ └── WeatherRepository.kt # OpenWeatherMap integration
│ │ ├── update/
│ │ │ ├── AppVersion.kt # Semver + RC parsing (incl. APK filename)
│ │ │ ├── UpdateChecker.kt # GitHub Releases API client
│ │ │ ├── UpdateManager.kt # Check/download/install orchestrator
│ │ │ └── UpdateState.kt # Sealed state class
│ │ └── ui/
│ │ ├── MainActivity.kt # 7-tab Compose host (HorizontalPager + BottomNavBar)
│ │ ├── DashPage.kt # DASH tab
│ │ ├── PowerPage.kt # POWER tab (incl. STFT/LTFT)
│ │ ├── ChassisPage.kt # CHASSIS tab (incl. clutch hydraulics)
│ │ ├── TempsPage.kt # TEMPS tab
│ │ ├── DiagPage.kt # DIAG tab
│ │ ├── MorePage.kt # MORE tab
│ │ ├── CustomDashPage.kt # Custom dashboard
│ │ ├── BottomNavBar.kt # Frosted-glass nav (Haze + spring indicator)
│ │ ├── DriveModeDock.kt # Quick Mode Dock dropdown
│ │ ├── PidBrowserSection.kt # PID browser composable
│ │ ├── DidProberSection.kt # DID prober composable
│ │ ├── BleDevicePickerDialog.kt # BLE device picker (RSSI + permissions)
│ │ ├── SettingsSheet.kt # Settings drawer (units/visibility/adapter)
│ │ ├── WhatsNewDialog.kt # Version changelog dialog
│ │ ├── Theme.kt # Brightness-scaled color tokens
│ │ ├── DesignTokens.kt # Spacing/shape tokens
│ │ ├── Components.kt # HeroCard/DataCell/BarCard/TireCard/AfrCard
│ │ ├── AppSettings.kt # SharedPreferences wrapper
│ │ ├── UserPrefs.kt # Observable prefs + UserPrefsStore
│ │ ├── trip/
│ │ │ ├── DrivePage.kt # MAP tab (live + history)
│ │ │ └── DriveMap.kt # Google Maps Compose wrapper (6 color modes)
│ │ └── anim/
│ │ ├── CarDiagram.kt # Focus RS wireframe (TPMS)
│ │ ├── EdgeShiftLight.kt # Peripheral shift light overlay
│ │ ├── GForcePlot.kt # 2D scatter w/ trail + peak labels
│ │ ├── GlowModifiers.kt # cardGlow + bloomGlow modifiers
│ │ ├── InteractionModifiers.kt # pressClick (clickable + scale)
│ │ ├── RingBuffer.kt # Generic fixed-capacity ring buffer
│ │ ├── ShiftLightBar.kt # Horizontal shift LED indicator
│ │ ├── Sparkline.kt # Inline trend chart
│ │ └── StaggeredEntrance.kt # Staggered fade+slide-up
│ └── res/
│ ├── font/ # Embedded fonts
│ │ ├── orbitron_regular.ttf # Hero gauge values
│ │ ├── orbitron_bold.ttf
│ │ ├── jetbrains_mono_regular.ttf # Secondary numeric readouts
│ │ ├── jetbrains_mono_bold.ttf
│ │ ├── share_tech_mono.ttf # Raw data / diagnostics
│ │ ├── barlow_condensed_regular.ttf # UI labels
│ │ ├── barlow_condensed_medium.ttf
│ │ ├── barlow_condensed_semibold.ttf
│ │ └── barlow_condensed_bold.ttf
│ ├── raw/dtc_database.json # Bundled 873-code Ford DTC lookup
│ ├── values/strings.xml
│ ├── values/themes.xml
│ ├── xml/file_paths.xml # FileProvider path config
│ └── mipmap-*/ic_launcher*.png # App icon (all densities)
├── browser-emulator/
│ └── index.html # Standalone browser emulator
├── docs/
│ ├── images/focus-rs-a4.png # Focus RS wireframe source (TPMS UI)
│ ├── hardware-setup.md
│ ├── firmware-update.md
│ ├── pid-reference.md
│ └── signing-setup.md
├── scripts/
│ └── gen_forscan_catalog.py # Parses FORScan CSV export → forscan_modules.json
└── README.md
Brand tokens
| Token | Hex | Usage |
|---|---|---|
| Nitrous Blue | #0091EA |
Accent colour — gauges, highlights, active states, "RS" in logo |
| Frost White | #E8F4FF |
Primary text — labels, readouts, "open" and "_" in logo |
| Bg | #05070A |
Background |
| Surface | #0A0D12 |
Cards, tab bar |
| Surface 2 | #0F141C |
Inset cards, hero RPM gauge |
| Surface 3 | #141B26 |
Elevated surfaces |
| Dim | #547A96 |
Muted text (WCAG AA ≥ 4.5:1) |
| Mid | #7A9AB8 |
Medium emphasis text |
| Border | #162030 |
Card/section borders |
| Ok | #00FF88 |
Neon green — good/ready |
| Warn | #FFCC00 |
Gold — attention/warm |
| Orange | #FF4D00 |
Orange-red — hot/aggressive |
Fonts (offline-embedded):
- Orbitron — hero gauge values (RPM, speed, boost)
- JetBrains Mono — secondary numeric readouts
- Share Tech Mono — raw data values and diagnostic output
- Barlow Condensed — all UI labels, section headers, and button text
Complete decode formulas, byte-level breakdowns, and all Mode 22 PIDs: android/docs/pid-reference.md
- Phase 8.6 — Tier 2 PIDs (Apr 2026 research): MAP/baro/engine load, oil pressure, PTU oil temp, panel illumination, COBB-style WG canister pressure + knock counters (v2.3.x)
- Phase 9 — Track day intelligence: lap timer with geofence, track map overlay enhancements, drive comparison enhancements (v2.4.x)
- Phase 10 — Hardware expansion: MeatPi Pro on-board GPS integration, MS-CAN second adapter (v2.5.x)
- Phase 11 — High-frequency telemetry: UDS Fast Rate Session via DDDI 0x2C (~100 Hz) (v3.x)
- Android Auto — official AndroidX Car App, unofficial aauto-sdk, or hybrid (see
android/docs/android-auto-custom-ui-research.md) - Data streaming to external apps (RaceChrono, Harry's LapTimer) via local broadcast or content provider
- Video overlay export — combine trip data with dashcam footage
Full vision: See Feature Roadmap for 35+ planned features beyond the current release phases.
Completed phases
- Phase 1 — CAN sniffing + basic OBD (v1.0)
- Phase 2 — Hybrid ATMA+OBD (v2.0)
- Phase 2.5 — TPMS+, AFR, ETC/TIP/WGDC, VCT, multi-ECU polling
- Phase 2.6 — Nitrous Blue/Frost White theme, openRS_ branding, live browser emulator
- Phase 2.7 — WebSocket SLCAN rewrite (~2100 fps), user settings, diagnostics export, firmware detection (fw-v1.1.0)
- Phase 2.8 — DBC-verified signal corrections, BCM/AWD polling, IAT, ambient, wheel speeds, SLCAN raw log + per-ID sampling (v1.1.1–v1.1.5)
- Phase 2.9 — TPMS formula fix, firmware detection timing fix, app version in logs (v1.1.6)
- Phase 3 — Full UI redesign: 6-tab layout, new fonts, new CAN signals (steering, yaw, brake), PCM Mode 22 polling, updated RTR thresholds (v1.2.0)
- Phase 4 — Trip recording: GPS trip page with OSM map, weather overlay, peak markers, live HUD, trip summary (v2.1.0)
- Phase 5 — UI architecture split, per-tab composables, share trip export (v2.2.0)
- Phase 6 — DTC scanning: 873-code Ford DTC database, full-module scan + clear via UDS 0x19/0x14 (v2.2.1)
- Phase 7 — Data export + MeatPi Pro: trip ZIP (GPX/CSV/TXT), diagnostics ZIP, raw TCP SLCAN adapter support (v2.2.1)
- Phase 7.5 — Sensor data + polish: GPS permission fix, Module Status/LC/ASS live OBD, full diagnostic export (~24 new fields), SLCAN OBD frame capture, code review fixes (v2.2.3)
- Phase 8.0 — Car test fixes + signal expansion: ESC decode fix, throttle fallback, battery voltage OBD, crash telemetry ring buffer, passive odometer (CAN 0x360), tap-to-change drive mode/ESC, RS MK3 theme colour correction, MeatPi default fix, free CAN signal extraction (vertical G, launch control, engine status, ignition status, ESC Launch mode), full repo audit hardening (v2.2.4)
- Phase 8.1 — FORScan PID catalog (1,149 PIDs, 8 modules), data-driven decode (PidRegistry + formula evaluator), DID prober, PID browser, AWD expansion (clutch temps L/R, trans oil temp, req torques, demanded pressure, pump current), per-cylinder knock correction (KR C1–C4), HVAC/IPC scaffolding, warning lamp banner (v2.2.5)
- Phase 8.2 — Drive mode reliability (pre-flight logging, auto-correction on overshoot), 0-60/0-100 performance timer, real-time fuel economy (idle/instant/average/DTE), clutch pedal (CAN 0x138), passive VIN decode (CAN 0x40A), wheel rotation counts (CAN 0x1E0), 3-tier TPMS thresholds, TPMS + temps in trip CSV exports, peripheral edge shift light, crash telemetry ring buffer (v2.2.5)
- Phase 8.3 — MAP tab + drive recorder: Google Maps integration with 6 colour modes (speed/mode/boost/throttle/lat-G/temp), Room-backed
DriveRecorderwith 1 Hz GPS + telemetry, drive history with date grouping, per-drive GPX/CSV/summary export, weather card, hoisted camera state, peak markers (v2.2.6 rc.1–rc.6) - Phase 8.4 — BLE GATT transport:
BleSlcanTransport(FFE0/FFE1/FFE2, MTU 247, auto-reconnect),BleDeviceScanner, in-app device picker with RSSI bars, adapter naming refactor (MeatPi USB / Pro × Wi-Fi / BLE), Wi-Fi coexistence banner,BleFirmwareApi(AT+FRS=),Network.socketFactoryWi-Fi route pinning (v2.2.6 rc.7) - Phase 8.5 — Drive mode cold-start gate (
has420Arrived), corrective auto-loop in MorePage, hybrid scroll-then-wait firmware (USB v1.61 / Pro v1.2 withAT+FRSBLE channel), drive mode reliability under reconnect (v2.2.6 rc.7) - Phase 8.6 — Tier 1 PIDs (Apr 2026 research): STFT/LTFT, AWD clutch actuator current L/R, AWD hydraulic pressure L/R, battery current; new CAN decoders for
0x305mode toggle button,0x260ASS/ESC defeat buttons,0x225LC engaged,0x070suspension button; spark advance + charging voltage desired; GFM (0x7D2) DTC scanning; criticalmergeObdState()allowlist fix (v2.2.6 rc.8) - Phase 8.7 — UI overhaul: bottom nav bar (Haze frosted glass + spring indicator), compact status bar (78dp → 34dp), Quick Mode Dock, G-Force plot redesign, visibility presets (Night/Day/Sun + brightness slider with live
lerprecompose), hybrid glow system (ambientcardGlow+ accent glow gates),AggressiveNumtypography, 0.5dp card borders (v2.2.6 rc.9) - Phase 8.8 — In-app updater: GitHub Releases API client, stable + beta channels,
Network.socketFactoryto bypass adapter Wi-Fi for download, rolling-RC tag bug fix (UpdateCheckerreads version from APK filename, not git tag), CI build provenance attestation via SLSA, lint advisory in CI, 344 unit tests across 11 files, Sapphire V2 web dashboard rewrite (129 vitest tests) (v2.2.6 stable)
Pull requests welcome. See CONTRIBUTING.md for guidelines.
If you have a Focus RS and FORScan/OBDLink, we'd love help verifying:
- Brake pressure bar calibration (raw ADC 0–4095 from
0x252, need known-pressure reference) - HVAC / IPC ECU address and DID confirmation (use built-in DID prober)
- MS-CAN parameters (requires second adapter)
MIT — see LICENSE for details.
- FORScan — Ford enhanced PID discovery
- MeatPi — WiCAN hardware
- Focus RS community — Testing and feedback





