An open API service indexing awesome lists of open source software.

https://github.com/klexical/openrs_

Real-time telemetry dashboard for the Ford Focus RS MK3 β€” CAN bus + OBD-II over Wi-Fi, Android & Android Auto
https://github.com/klexical/openrs_

android android-auto automotive can-bus focus-rs ford-focus-rs jetpack-compose kotlin meatpi obd2 telemetry wican

Last synced: 2 months ago
JSON representation

Real-time telemetry dashboard for the Ford Focus RS MK3 β€” CAN bus + OBD-II over Wi-Fi, Android & Android Auto

Awesome Lists containing this project

README

          


openRS_

Open-source real-time telemetry dashboard for the Ford Focus RS MK3


πŸ”· Sapphire Dashboard β€’
πŸš€ Live Emulator β€’
Screenshots β€’
Features β€’
Hardware β€’
Quick Start β€’
Architecture β€’
Roadmap


Version
Platform
Kotlin
Compose
License
CAN Bus


"Built for the car Ford should have given us an app for."

---

## What is openRS_?

**openRS_** is a native Android app that turns your phone into a full telemetry dashboard for the Ford Focus RS MK3. It connects wirelessly to a [MeatPi WiCAN](https://www.mouser.com/ProductDetail/MeatPi/WICAN-USB-C3?qs=rQFj71Wb1eVDX2eEy0FC7A%3D%3D) adapter over Wi-Fi 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](https://klexical.github.io/openRS_/emulator/) β€” live browser emulator with animated demo data, no hardware required.
>
> **Analyse your data:** [klexical.github.io/openRS_](https://klexical.github.io/openRS_/) β€” **Sapphire**, the post-session analytics dashboard. Drop an export ZIP to explore charts, CAN data, and diagnostics in your browser.

---

## Screenshots


DASH tab
POWER tab
CHASSIS tab
TEMPS tab
DIAG tab
MORE tab


DASH Β Β·Β  POWER Β Β·Β  CHASSIS Β Β·Β  TEMPS Β Β·Β  DIAG Β Β·Β  MORE

---

## Features

### 6 Tabs

| Screen | Description |
|--------|-------------|
| **DASH** | Hero boost/RPM/speed gauges (with session peak values), throttle/brake/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, fuel trims |
| **CHASSIS** | AWD detail (4 wheel speeds, torque bar, F/R delta, L/R delta, rear bias, clutch temps L/R, trans oil temp), G-force + yaw + steering with peak reset, TPMS Focus RS wireframe (pressure + temp, colour-coded), 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) |
| **DIAG** | DTC Scanner (full-module scan, 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, one-tap ZIP export (SavvyCAN/Kayak compatible) |
| **MORE** | Drive mode (N/S/T/D, tap-to-change with openRS_ firmware), ESC status, firmware version display, firmware-gated features (Launch Control, Auto S/S Kill), Module Status (RDU/PDC/FENG live OBD), connection & snapshot, Trip GPS recording |

### Ready-to-Race Thresholds

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

### Settings

| 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 | WiCAN / MeatPi Pro | WiCAN |
| MicroSD logging reminder | on / off | off (MeatPi Pro only) |
| Max saved ZIP exports | count | 5 |

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 |
| 0x160 | Longitudinal G-force | RS_HS.dbc |
| 0x180 | Lateral G-force, **yaw rate**, **vertical G** | RS_HS.dbc ABSmsg02 |
| 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](https://github.com/klexical/openRS_/discussions/102) |
| 0x380 | Fuel level % (FuelLevelFiltered β€” Motorola 10-bit, factor 0.4 %) | RS_HS.dbc PCMmsg30 |

> **Note:** `0x230` (gear position) and `0x3C0` (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`.

---

## Hardware

### Required

| Component | Details |
|-----------|---------|
| **MeatPi WiCAN** | [Mouser](https://www.mouser.com/ProductDetail/MeatPi/WICAN-USB-C3?qs=rQFj71Wb1eVDX2eEy0FC7A%3D%3D) β€” Wi-Fi ELM327-compatible OBD-II adapter |
| **MeatPi WiCAN Pro** (optional) | [MeatPi](https://www.meatpi.com/) β€” Wi-Fi + GPS + MicroSD, raw TCP SLCAN |
| **Ford Focus RS MK3** | 2016–2018 (LZ platform, EcoBoost 2.3L) |
| **Android phone** | Android 9+ (API 28) with Wi-Fi |

### Setup

1. Plug the WiCAN into the OBD-II port (under the steering column)
2. Connect your phone to the WiCAN's Wi-Fi network:

| Firmware | SSID | Password |
|----------|------|----------|
| **Stock WiCAN** | `WiCAN_XXXXXX` | `@meatpi#` |
| **openrs-fw** | `openRS_XXXXXX` | `openrs_2026` |

3. Install openRS_ and tap the connection dot in the header

> **Stock firmware** works out of the box β€” openRS_ connects via WebSocket SLCAN (`ws://192.168.80.1:80/ws`) with no configuration changes needed. For full functionality (drive mode write, Launch Control, Auto S/S Kill, ESC control), flash **openrs-fw** to the WiCAN. See the [firmware update guide](android/docs/firmware-update.md) for instructions.

> **WiCAN 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. In the openRS_ app, switch the adapter to **MeatPi Pro** in Settings. See the [hardware setup guide](android/docs/hardware-setup.md#wican-pro-adapter) for full instructions.

---

## Quick Start

### Prerequisites

- Android Studio Ladybug (2024.2) or newer
- JDK 17+
- Android SDK 35

### Build

```bash
git clone https://github.com/klexical/openRS_.git
cd openRS_/android
./gradlew assembleRelease
# Output: app/build/outputs/apk/release/openRS_v2.2.5.apk
# (Requires keystore β€” see android/docs/signing-setup.md)
```

### Browser Emulator (no hardware required)

Open `android/browser-emulator/index.html` in any browser, or visit the live version:

**[klexical.github.io/openRS_/emulator](https://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

### Sapphire β€” Post-Session Analytics

Drop an export ZIP from the app into the web dashboard to explore your session data:

**[klexical.github.io/openRS_](https://klexical.github.io/openRS_/)**

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

---

## Architecture

System diagram

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Phone UI β€” Jetpack Compose + Material 3 β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ DASH β”‚ POWER β”‚ CHASSIS β”‚ TEMPS β”‚ DIAG β”‚ MORE β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ Header: logo Β· connection pill Β· TRIP Β· βš™ β”‚
β”‚ F1 Telemetry Strip: MODE Β· ESC Β· CONN/FPS Β· IGN Β· E-BRK β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ UserPrefsStore (StateFlow) β€” units, thresholds, reconnect settings β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ VehicleState (StateFlow) β”‚
β”‚ Immutable data class Β· 90+ fields Β· genericValues map Β· RTR β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ CanDataService (Background) β”‚
β”‚ Decodes CAN β†’ VehicleState β†’ notifies UI β”‚
β”‚ Hooks DiagnosticLogger (frame inventory, trace, FPS, SLCAN log) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ CanDecoder β”‚ DiagnosticLogger / Exporter / DtcScanner β”‚
β”‚ 22 CAN frame IDs β”‚ Per-ID first/last/Ξ” tracking β”‚
β”‚ RS_HS.dbc-verified β”‚ Periodic samples (30 s), SLCAN candump log β”‚
β”‚ Motorola extraction β”‚ Validation engine, ZIP export via FileProviderβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PidRegistry β€” data-driven decode from FORScan catalog (JSON) β”‚
β”‚ 1,149 PIDs Β· 8 modules Β· formula evaluator Β· genericValues output β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ObdConstants / ObdResponseParser / SlcanParser (shared layer) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ WiCanConnection (WebSocket) β”‚ MeatPiConnection (TCP) β”‚
β”‚ ws://192.168.80.1:80/ws β”‚ tcp://192.168.0.10:35000 β”‚
β”‚ SLCAN: C / S6 / O Β· ~2100 fpsβ”‚ Raw SLCAN + OBD polling β”‚
β”‚ Firmware probe (OPENRS?) β”‚ sendRawQuery() for DID prober β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PCM (0x7E0/30s) Β· BCM (0x726/30s) Β· AWD (0x703/60s) β”‚
β”‚ HVAC (0x733, candidate) Β· IPC (0x720, candidate) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ MeatPi WiCAN USB-C3 β”‚ MeatPi WiCAN Pro (optional) β”‚
β”‚ Wi-Fi AP Β· WS :80/wsβ”‚ Wi-Fi AP Β· TCP :35000 Β· GPS Β· MicroSD β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ HS-CAN 500k β”‚ MS-CAN 125k (bridged via GWM) β”‚
β”‚ 0x010–0x3C0 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:`; 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.

---

## Technical Reference

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)
β”‚ β”œβ”€β”€ java/com/openrs/dash/
β”‚ β”‚ β”œβ”€β”€ OpenRSDashApp.kt # Application singleton + isOpenRsFirmware flag
β”‚ β”‚ β”œβ”€β”€ can/
β”‚ β”‚ β”‚ β”œβ”€β”€ AdapterState.kt # Shared connection state sealed class
β”‚ β”‚ β”‚ β”œβ”€β”€ CanDecoder.kt # 22 CAN frame decoders (RS_HS.dbc-verified)
β”‚ β”‚ β”‚ β”œβ”€β”€ MeatPiConnection.kt # MeatPi Pro raw TCP SLCAN + OBD polling
β”‚ β”‚ β”‚ β”œβ”€β”€ ObdConstants.kt # Shared OBD query strings + CAN IDs + timing
β”‚ β”‚ β”‚ β”œβ”€β”€ ObdResponseParser.kt # Shared OBD Mode 22 response parsers
β”‚ β”‚ β”‚ β”œβ”€β”€ PidRegistry.kt # Data-driven PID decode via JSON catalog + formula evaluator
β”‚ β”‚ β”‚ β”œβ”€β”€ SlcanParser.kt # Shared SLCAN frame parser
β”‚ β”‚ β”‚ └── WiCanConnection.kt # WiCAN WebSocket SLCAN + firmware probe
β”‚ β”‚ β”œβ”€β”€ data/
β”‚ β”‚ β”‚ β”œβ”€β”€ DtcModuleSpec.kt # ECU module descriptor for DTC operations
β”‚ β”‚ β”‚ β”œβ”€β”€ DtcResult.kt # DTC result + status enum
β”‚ β”‚ β”‚ β”œβ”€β”€ ForscanCatalog.kt # Data classes for JSON catalog deserialization
β”‚ β”‚ β”‚ β”œβ”€β”€ TripPoint.kt # GPS waypoint with telemetry
β”‚ β”‚ β”‚ β”œβ”€β”€ TripState.kt # Trip accumulator
β”‚ β”‚ β”‚ └── VehicleState.kt # Immutable state (90+ fields, genericValues, peaks, RTR)
β”‚ β”‚ β”œβ”€β”€ diagnostics/
β”‚ β”‚ β”‚ β”œβ”€β”€ DiagnosticExporter.kt # ZIP builder + FileProvider share + CSV
β”‚ β”‚ β”‚ β”œβ”€β”€ DiagnosticLogger.kt # Session-scoped collector + SLCAN log
β”‚ β”‚ β”‚ β”œβ”€β”€ DtcDatabase.kt # Bundled 873-code Ford DTC lookup
β”‚ β”‚ β”‚ └── DtcScanner.kt # DTC scan/clear orchestrator
β”‚ β”‚ β”œβ”€β”€ service/
β”‚ β”‚ β”‚ └── CanDataService.kt # Background service + DiagnosticLogger hooks
β”‚ β”‚ └── ui/
β”‚ β”‚ β”œβ”€β”€ MainActivity.kt # Compose entry (6 tabs + header)
β”‚ β”‚ β”œβ”€β”€ DashPage.kt # DASH tab
β”‚ β”‚ β”œβ”€β”€ PowerPage.kt # POWER tab
β”‚ β”‚ β”œβ”€β”€ ChassisPage.kt # CHASSIS tab
β”‚ β”‚ β”œβ”€β”€ TempsPage.kt # TEMPS tab
β”‚ β”‚ β”œβ”€β”€ DiagPage.kt # DIAG tab (DTC scanner + DID prober + PID browser)
β”‚ β”‚ β”œβ”€β”€ DidProberSection.kt # DID prober composable
β”‚ β”‚ β”œβ”€β”€ PidBrowserSection.kt # FORScan PID browser composable
β”‚ β”‚ β”œβ”€β”€ MorePage.kt # MORE tab
β”‚ β”‚ β”œβ”€β”€ Theme.kt # Design tokens, fonts, colors
β”‚ β”‚ β”œβ”€β”€ Components.kt # Shared composables
β”‚ β”‚ β”œβ”€β”€ AppSettings.kt # SharedPreferences wrapper
β”‚ β”‚ β”œβ”€β”€ UserPrefs.kt # Observable preferences (StateFlow)
β”‚ β”‚ └── SettingsSheet.kt # Settings dialog
β”‚ └── 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** | `#F5F6F4` | Primary text β€” labels, readouts, "open" and "_" in logo |
| **Deep Black** | `#0A0A0A` | Background |
| **Surface** | `#141414` | Cards, tab bar |
| **Surface 2** | `#1C1C1C` | Inset cards, hero RPM gauge |

**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

---

## Full PID Reference

Complete decode formulas, byte-level breakdowns, and all Mode 22 PIDs: [`android/docs/pid-reference.md`](android/docs/pid-reference.md)

---

## Roadmap

### Planned

- [ ] Phase 8.5 β€” Polish and sensor gaps: BLE transport in app, ESC write fix ([#125](https://github.com/klexical/openRS_/issues/125)), brake pressure calibration (v2.3.x)
- [ ] Phase 9 β€” Track day intelligence: lap timer with geofence, track map overlay enhancements, trip comparison (v2.4.x)
- [ ] Phase 10 β€” Hardware expansion: MeatPi Pro GPS integration, MS-CAN support (v2.5.x)
- [ ] Phase 11 β€” High-frequency telemetry: UDS Fast Rate Session via DDDI 0x2C (~100 Hz) (v3.x)

### Future / Exploratory

- 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](docs/feature-roadmap.md) for 35+ planned features beyond the current release phases.

Completed phases

- [x] Phase 1 β€” CAN sniffing + basic OBD (v1.0)
- [x] Phase 2 β€” Hybrid ATMA+OBD (v2.0)
- [x] Phase 2.5 β€” TPMS+, AFR, ETC/TIP/WGDC, VCT, multi-ECU polling
- [x] Phase 2.6 β€” Nitrous Blue/Frost White theme, openRS_ branding, live browser emulator
- [x] Phase 2.7 β€” WebSocket SLCAN rewrite (~2100 fps), user settings, diagnostics export, firmware detection (fw-v1.1.0)
- [x] 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)
- [x] Phase 2.9 β€” TPMS formula fix, firmware detection timing fix, app version in logs (v1.1.6)
- [x] 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)
- [x] Phase 4 β€” Trip recording: GPS trip page with OSM map, weather overlay, peak markers, live HUD, trip summary (v2.1.0)
- [x] Phase 5 β€” UI architecture split, per-tab composables, share trip export (v2.2.0)
- [x] Phase 6 β€” DTC scanning: 873-code Ford DTC database, full-module scan + clear via UDS 0x19/0x14 (v2.2.1)
- [x] Phase 7 β€” Data export + MeatPi Pro: trip ZIP (GPX/CSV/TXT), diagnostics ZIP, raw TCP SLCAN adapter support (v2.2.1)
- [x] 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)
- [x] 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)
- [x] 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)

---

## Contributing

Pull requests welcome. See [CONTRIBUTING.md](android/CONTRIBUTING.md) for guidelines.

If you have a Focus RS and FORScan/OBDLink, we'd love help verifying:
- 12V battery voltage β€” CAN ID 0x3C0 does not broadcast; needs alternative source (BCM PID or other CAN ID)
- 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)

---

## License

MIT β€” see [LICENSE](LICENSE) for details.

---

## Acknowledgments

- **FORScan** β€” Ford enhanced PID discovery
- **MeatPi** β€” WiCAN hardware
- **Focus RS community** β€” Testing and feedback