f2308cd4eb
gui/widgets.py: - MultiAxisPlot -- overlay with one Y axis PER UNIT (psi/V/rpm/C/%), linked ViewBoxes on X, so mixed-scale signals are readable at true values (base left axis + up to 4 right axes). - SinglePlot -- one shared axis for the Normalize (% of range) mode. - ArcGauge -- 270deg arc gauge with peak tick + numeric readout, own dark bg. - GaugeGrid -- scrollable grid of gauges. gui/main.py: - Graph page is now a multi-axis/single-axis sub-stack; Normalize toggles between true multi-axis (raw) and single-axis (%). curves map key->color; plot ops route to the active graph widget. - Gauge View menu enabled (3rd center page); gauges update on tick with peak. - Theme applies to both plot widgets; profile switch clears graphs/gauges. Fix: ArcGauge QPen built via setCapStyle (the QPen(...cap=...) kwarg segfaults PySide6). Validated headless: driving preset -> 6 unit groups across 5 axes, gauge view renders, normalize round-trips, profile-switch clears cleanly. obdcore + diagnostics tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
140 lines
6.1 KiB
Markdown
140 lines
6.1 KiB
Markdown
# ford-obd
|
||
|
||
Minimal **ELM327 OBD-II code reader** with a **Ford 6.0L Power Stroke no-start triage**,
|
||
built for a cheap CH340 ELM327 USB adapter. Works on any OBD-II vehicle for generic
|
||
codes/PIDs; the triage notes are 6.0-specific.
|
||
|
||
Created as a stopgap while [forscan.org](https://forscan.org) was offline — it covers
|
||
reading/clearing codes and the basics, not Ford-enhanced diesel PIDs (see Scope below).
|
||
|
||
## Features
|
||
|
||
- Read **stored** (mode 03), **pending** (mode 07), **permanent** (mode 0A) DTCs
|
||
- Decode P/C/B/U codes, with common **6.0 codes** described and **no-start suspects flagged**
|
||
- **Clear** codes (mode 04) — guarded behind `--clear` + a typed `CLEAR` confirmation,
|
||
then re-reads to show any code that returns immediately (active fault)
|
||
- Key **live values** (coolant, IAT, MAP, module voltage, RPM, load, throttle) + battery voltage
|
||
- 6.0 Power Stroke **no-start triage** checklist (FICM, ICP, cam/crank, batteries, fuel)
|
||
|
||
## Setup
|
||
|
||
Runs on **Windows, macOS, and Linux** (Python + pyserial). The only per-OS
|
||
difference is the CH340 USB driver:
|
||
|
||
- **Windows** — install WCH `CH341SER`; adapter shows as `USB-SERIAL CH340 (COMx)`
|
||
in Device Manager → Ports. Install Python from <https://www.python.org/downloads/>
|
||
(tick **Add Python to PATH**), or just double-click `RUN_OBD.bat`.
|
||
- **macOS** — install WCH `CH34xVCPDriver` (Mac App Store or wch.cn). Port appears
|
||
as `/dev/cu.wchusbserial*`. `pip install pyserial`.
|
||
- **Linux** — `ch341` driver is built into the kernel (no install). Port is
|
||
`/dev/ttyUSB0`; add yourself to the `dialout` group for access
|
||
(`sudo usermod -aG dialout $USER`, then re-login). `pip install pyserial`.
|
||
|
||
The tool auto-detects the port on all three; pass it explicitly if needed
|
||
(`COM5`, `/dev/cu.usbserial-1420`, `/dev/ttyUSB0`).
|
||
|
||
## Usage
|
||
|
||
```
|
||
python obd_reader.py # auto-detect the COM port
|
||
python obd_reader.py COM5 # force a port
|
||
python obd_reader.py COM5 9600 # force port + baud (default 38400)
|
||
python obd_reader.py COM5 --clear # read, then optionally clear (asks to confirm)
|
||
python obd_reader.py COM5 -v # verbose: show raw ELM327 traffic
|
||
```
|
||
|
||
### Crank monitor (dedicated no-start view) — `--crank`
|
||
|
||
The one to use for a crank-but-won't-start. Big ICP readout with a wide bar
|
||
(the `|` marks the 500-psi firing threshold), a **rolling ASCII trace** of the
|
||
ICP build-up, **peak-hold**, FICM/battery/RPM with sag tracking, and a pass/fail
|
||
verdict. Start it, then crank.
|
||
|
||
```
|
||
python obd_reader.py COM5 --crank # crank monitor
|
||
python obd_reader.py COM5 --crank --dash-log crank.csv # + record a CSV
|
||
```
|
||
|
||
```
|
||
ICP [#################################|##----] 539.8 psi
|
||
PEAK 540 psi FIRING PRESSURE REACHED
|
||
FICM Main 47.5V (min 47.5) [DOC] Batt 12.6V (min 10.7) RPM 200
|
||
|
||
ICP trace (psi vs time, last 16 samples)
|
||
600 |
|
||
500 |----------------------------------------------#### <- firing line
|
||
| ######
|
||
| ########
|
||
+--------------------------------------------------
|
||
```
|
||
|
||
**Read it:** ICP should climb **past 500 psi within 1–2 s** of cranking
|
||
(`FIRING PRESSURE REACHED`, green). If it **stalls below 500** (red, trace flat
|
||
under the line), that's the high-pressure oil bleed-off — STC fitting / oil-rail
|
||
O-rings. On exit it prints the peak and a verdict. `q` quits, `r` resets.
|
||
|
||
## Graphical app (preview)
|
||
|
||
A cross-platform desktop GUI (PySide6 + pyqtgraph). Vehicle-agnostic — all PIDs,
|
||
scaling, DTCs, and presets come from the JSON profiles in `profiles/`.
|
||
|
||
```
|
||
pip install -r requirements-gui.txt
|
||
python run_gui.py # tick "Mock" + Connect to explore with no adapter
|
||
```
|
||
|
||
Features so far:
|
||
- **PID browser** (left) grouped by system, live values, confidence badges
|
||
- **Graph view** with **true multi-axis** overlay — one Y scale per unit (psi/V/rpm/…),
|
||
or a Normalize (% of range) mode
|
||
- **Gauge view** — arc gauges with peak-hold, one per signal
|
||
- **Table view** — value + min/max + confidence
|
||
- **Diagnostics** — read/clear DTCs (guarded), no-start codes flagged
|
||
- **Profile menu** — switch/import/edit vehicles; **File menu** — record/replay/export captures
|
||
|
||

|
||

|
||
|
||
The whole app runs against simulated data (`MockLink`), so it can be developed
|
||
on any machine and only needs the vehicle for real captures. See
|
||
[ARCHITECTURE.md](ARCHITECTURE.md) for the roadmap.
|
||
|
||
---
|
||
|
||
### Live dashboard (real-time gauges)
|
||
|
||
Updates in place as you crank or run the engine — color-coded, with live
|
||
min/max so a crank's **peak ICP** is captured. No extra dependencies (ANSI;
|
||
works on any Windows 10+ terminal). `q` quits, `r` resets min/max.
|
||
|
||
```
|
||
python obd_reader.py COM5 --dash # vitals preset (ICP, FICM, IPR, batt, RPM, temps)
|
||
python obd_reader.py COM5 --dash crank # cranking preset: ICP / FICM main / batt / RPM (fastest)
|
||
python obd_reader.py COM5 --dash full # every PID
|
||
python obd_reader.py COM5 --dash crank --dash-log crank.csv # + write a CSV while you watch
|
||
```
|
||
|
||
**No-start use:** run `--dash crank`, then crank. A healthy 6.0 builds
|
||
**~500+ psi ICP within 1–2 s**; if ICP stalls below 500 (red), that confirms
|
||
the high-pressure oil bleed-off. FICM Main should hold ~48V. The `--dash-log`
|
||
CSV is your streaming log — paste it back for analysis.
|
||
|
||
Note: the FICM PIDs (`09xx`) are `[DOC]` (not yet confirmed on this truck); if
|
||
they read `--`, they auto-drop after a few frames so the refresh rate stays up.
|
||
|
||
Or just double-click **`RUN_OBD.bat`** on Windows (auto-installs `pyserial`).
|
||
|
||
On the truck: plug into the OBD port under the dash, key to **RUN** (engine off is fine
|
||
for codes), then run the tool.
|
||
|
||
## Scope / honesty
|
||
|
||
A generic ELM327 reads standard OBD-II only: codes, generic PIDs, port voltage. It does
|
||
**not** read Ford-enhanced diesel PIDs (ICP, FICM main/sync voltage, IPR%) — those need
|
||
FORScan. For FICM/ICP numbers, measure at the FICM with a meter, or use FORScan when it's
|
||
available. Default baud is 38400 (measured on the CH340 adapter); try 9600 if you get garbage.
|
||
|
||
## Requirements
|
||
|
||
`pyserial` (`pip install pyserial`). Tested against a QinHeng CH340 ELM327 v1.5 clone.
|