Add --watch/--ford/--pid/--scan modes + 2026-06-29 session diagnostics
obd_reader.py: - Mode 22 plumbing: ELM.mode22() sends a 16-bit PID request, parses both positive (62 ..) and negative (7F 22 NRC) responses. - --ford runs a small TENTATIVE table of community-sourced Ford 6.0 PIDs (ICP/IPR/FICM/EBP/EOT). All printed with raw bytes for verification. - --pid XXXX probes a single PID and prints multiple candidate decodings (u8, u16, mV, temp, duty) so we can eyeball the right scaling. - --watch [N] streams ATRV + module voltage (PID 0142) for N seconds. Designed for capturing voltage sag during cranking. - --scan AAAA-BBBB brute-force scans Mode-22 PIDs with --scan-log PATH for output. Uses fast ELM timing (ATAT2, ATST19) for ~3.5 PIDs/sec. diagnostics/2026-06-29-no-start/: - Captured cranking voltage trace, full Mode-22 scan (1000-14FF -> 46 hits), and a session writeup. Working hypothesis: not batteries, not fuel -- ICP / FICM / CMP. FICM meter test still owed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# 2026-06-29 — 6.0 Power Stroke no-start session
|
||||
|
||||
In-cab diagnostic session using the `ford-obd` tool on the truck.
|
||||
Charger on "12V Hi" was attached for most of the session.
|
||||
|
||||
## Symptoms
|
||||
|
||||
- Cranks **fast** (starter spins the engine normally)
|
||||
- **Zero combustion event** — no sputter, no cough, no kickback
|
||||
- HFCM lift pump heard priming on key-RUN and shutting off normally
|
||||
- Battery at OBD port: 11.2 – 12.7V key-RUN (varied with charger state)
|
||||
|
||||
## Codes pulled (multiple reads, slight variation between reads)
|
||||
|
||||
| Mode | Code | Description |
|
||||
|---|---|---|
|
||||
| Stored (03) | `C0300` | (chassis — not engine, not in our DB) |
|
||||
| Pending (07) | `C0700` / `C0701` | (chassis — alternated between reads) |
|
||||
| Pending (07) | `P0113` | IAT circuit HIGH — sensor wiring, not a no-start |
|
||||
| Permanent (0A) | `C3F0A` | (chassis — not engine) |
|
||||
|
||||
No engine P-codes, no FICM codes, no injector codes, no CMP/CKP codes.
|
||||
|
||||
## What we ruled out
|
||||
|
||||
- **Batteries as primary cause** — cranks fast and voltage held adequately
|
||||
(see `watch-cranking.log`). Bus sagged to 10.8–11.2V during ~11s crank,
|
||||
borderline-low but not catastrophic. Healthy starter spin means batteries
|
||||
have current to give.
|
||||
- **Fuel supply** — HFCM lift pump audibly primes and shuts off correctly
|
||||
on key-RUN.
|
||||
- **Compression / mechanical** — fast even cranking, would feel different
|
||||
with a low-compression cylinder.
|
||||
|
||||
## Working hypothesis (in order of suspicion)
|
||||
|
||||
1. **ICP / high-pressure oil leak** — STC fitting is the classic 6.0 culprit
|
||||
for "cranks great, won't fire, no codes". Need ≥500 psi ICP to fire even
|
||||
one injector.
|
||||
2. **FICM weakness** — should boost to ~48V. Sometimes doesn't throw a code
|
||||
even when failing. **Not yet measured** — see open items.
|
||||
3. **CMP sensor** — would usually throw P0340/P0341 but not always.
|
||||
|
||||
## What we did with the tool this session
|
||||
|
||||
- Pulled DTCs across all 3 modes (multiple times).
|
||||
- Captured cranking voltage with `--watch 30` (see `watch-cranking.log`):
|
||||
- Pre-crank: bus 11.7→12.1V, VPCM 12.39→12.71V
|
||||
- Cranking (~11s): bus 10.8–11.2V, VPCM 11.32–11.70V
|
||||
- Recovery: bus 11.8–12.2V, VPCM stopped responding (`?`) — possible
|
||||
PCM brief recovery state after the dip.
|
||||
- Ran first attempt at Mode-22 probes (`--ford`) with community-sourced
|
||||
PIDs (ICP=1209, FICM=1228/1229/122A, IPR=120B, etc.). **All returned
|
||||
"no response."** Either the PCM uses different PID numbers, or these
|
||||
values live on Ford's SCP bus and aren't gatewayed to OBD-CAN.
|
||||
- Brute-force scanned PIDs `0x1000-0x14FF` (1280 PIDs, ~6 min, 46 hits).
|
||||
See `mode22-scan-hits.txt`. Data is noisy because **multiple modules on
|
||||
the bus answer the same query** — positive responses from PCM mixed
|
||||
with negative-response bytes (`7F 22 12`, `7F 22 31`) from other modules.
|
||||
|
||||
## Interesting PIDs from the scan to verify next session
|
||||
|
||||
Cleanest-looking PCM-side responses worth re-probing individually
|
||||
(with normal timing, `python obd_reader.py --pid XXXX`):
|
||||
|
||||
```
|
||||
1310 2 1C 92 (was 1C 69 earlier — value changes; maybe a counter?)
|
||||
1440 2 01 89 = 393
|
||||
1442 2 01 88 = 392 ← three closely-spaced ~390 values
|
||||
1445 2 01 8F = 399 suggests a related sensor cluster
|
||||
1446 2 00 16 = 22
|
||||
1447 2 00 34 = 52
|
||||
1448 2 00 11 = 17
|
||||
11B3 1 02
|
||||
11B4 2 00 00
|
||||
11BA 2 FF 5C
|
||||
```
|
||||
|
||||
Likely scaling guesses:
|
||||
- raw / 100 → fits if these are voltage-tenths or pressure-tenths
|
||||
- raw / 1000 → if mV (0.393V plausible for sensor outputs)
|
||||
- raw - some offset → temperature
|
||||
|
||||
To know what each one *is*, compare against a known measurement:
|
||||
- Run `--pid 1440` with engine off, write down value.
|
||||
- Get engine running (eventually), write down value.
|
||||
- Change condition, watch which PIDs move.
|
||||
|
||||
## Open items / what's next
|
||||
|
||||
1. **FICM meter test was started but no readings reported back yet.**
|
||||
Measure M / S / L on the FICM body, key-ON and during a (short) crank:
|
||||
- M (Main) target ~48V cranking, <45V suspect, <40V dead
|
||||
- S (Sync) target ~48V cranking
|
||||
- L (Logic) target ~12V (mirrors battery)
|
||||
2. **Visual under-engine check** for STC-fitting oil leak (wet/oily mess
|
||||
at the rear of the engine, around the turbo / bell housing).
|
||||
3. **Verify scan-hit PIDs.** Re-probe the clean ones above with
|
||||
`--pid XXXX` to get uncontaminated readings, log them against known
|
||||
conditions.
|
||||
4. **Try addressing other modules with `ATSH`** — particularly the FICM
|
||||
on its CAN address if it has one (uncertain on 6.0 — likely on SCP).
|
||||
5. Try **FORScan** when forscan.org / CyanLabs mirror is back, on the
|
||||
same CH340 adapter, to capture FORScan's actual PID requests for
|
||||
ICP/FICM/IPR. That gives us the ground truth.
|
||||
|
||||
## Tool state at end of session
|
||||
|
||||
`obd_reader.py` now has:
|
||||
- `--ford` — try the (tentative) Ford 6.0 Mode-22 PID table
|
||||
- `--pid XXXX` — one-shot probe of any 16-bit Mode-22 PID
|
||||
- `--watch [N]` — stream ATRV + VPCM for N seconds (default 20)
|
||||
- `--scan AAAA-BBBB` + `--scan-log PATH` — brute-force scan a PID range
|
||||
|
||||
All work on the CH340 ELM327 v1.5 clone at 38400 baud, HS-CAN switch.
|
||||
@@ -0,0 +1,121 @@
|
||||
================================================================
|
||||
OBD-II Code Reader - ELM327 / 6.0 Power Stroke triage
|
||||
================================================================
|
||||
|
||||
Auto-selected port: COM5 (USB-SERIAL CH340 (COM5))
|
||||
(other ports seen: COM4, COM3 -- pass one as an argument if wrong)
|
||||
Opening COM5 @ 38400 baud ...
|
||||
Adapter : ELM327 v1.5
|
||||
Battery : 12.5V (key ON; healthy 6.0 KOEO ~12.4-12.7V)
|
||||
|
||||
Connecting to vehicle (turn key to ON / RUN, engine off is fine)...
|
||||
Protocol: A6 (CAN=True)
|
||||
|
||||
----------------------------------------------------------------
|
||||
TROUBLE CODES
|
||||
----------------------------------------------------------------
|
||||
|
||||
STORED (mode 03):
|
||||
C0300 - (look up this code)
|
||||
|
||||
PENDING (mode 07):
|
||||
P0113 - Intake air temp (IAT) circuit HIGH
|
||||
C0700 - (look up this code)
|
||||
|
||||
PERMANENT (mode 0A):
|
||||
C3F0A - (look up this code)
|
||||
|
||||
----------------------------------------------------------------
|
||||
KEY LIVE VALUES (key ON)
|
||||
----------------------------------------------------------------
|
||||
Engine RPM 0 rpm
|
||||
Coolant temp 33 C
|
||||
Intake air temp 35 C
|
||||
Intake MAP 98 kPa
|
||||
Engine load 0 %
|
||||
Accel/throttle pos n/a
|
||||
Module voltage 13.1 V
|
||||
|
||||
================================================================
|
||||
SCAN Mode-22 PIDs 1000 - 14FF (1280 PIDs)
|
||||
================================================================
|
||||
Logging hits to: C:\Users\justin\AppData\Local\Temp\ford-scan-hits.txt
|
||||
Safe: Mode 22 is read-only. Will print every PID that answers.
|
||||
|
||||
... 100/1280 scanned in 28s (3.6/s), ~330s remaining, 0 hits so far
|
||||
... 200/1280 scanned in 55s (3.6/s), ~298s remaining, 0 hits so far
|
||||
HIT 1100 len= 7 raw=[01 00 40 05 7F 22 12]
|
||||
HIT 1101 len= 5 raw=[00 62 11 01 00]
|
||||
HIT 1102 len= 1 raw=[00]
|
||||
HIT 1103 len= 5 raw=[00 62 11 03 00]
|
||||
HIT 1104 len= 5 raw=[00 62 11 04 00]
|
||||
HIT 1105 len= 1 raw=[00]
|
||||
HIT 1106 len= 5 raw=[00 62 11 06 04]
|
||||
HIT 1107 len= 5 raw=[00 62 11 07 00]
|
||||
HIT 110C len= 4 raw=[00 7F 22 12]
|
||||
HIT 1123 len= 5 raw=[1E 62 11 23 30]
|
||||
HIT 1125 len= 1 raw=[FF]
|
||||
HIT 1126 len= 5 raw=[00 62 11 26 00]
|
||||
HIT 1127 len= 1 raw=[E7]
|
||||
... 300/1280 scanned in 82s (3.6/s), ~269s remaining, 13 hits so far
|
||||
HIT 1135 len= 2 raw=[FF FF]
|
||||
HIT 1139 len= 5 raw=[2E 62 11 39 2E]
|
||||
HIT 114A len= 5 raw=[7E 00 7F 22 12]
|
||||
HIT 114B len= 5 raw=[A6 56 7F 22 12]
|
||||
HIT 114D len= 2 raw=[81 40]
|
||||
HIT 1155 len= 2 raw=[FF FF]
|
||||
HIT 1165 len= 7 raw=[00 00 62 11 65 00 00]
|
||||
HIT 1169 len= 2 raw=[00 00]
|
||||
HIT 1172 len= 5 raw=[D1 62 11 72 D3]
|
||||
HIT 1177 len= 5 raw=[00 C8 7F 22 12]
|
||||
... 400/1280 scanned in 110s (3.6/s), ~241s remaining, 23 hits so far
|
||||
HIT 11B3 len= 1 raw=[02]
|
||||
HIT 11B4 len= 2 raw=[00 00]
|
||||
HIT 11B5 len= 5 raw=[00 00 7F 22 31]
|
||||
HIT 11B6 len= 4 raw=[8C 7F 22 31]
|
||||
HIT 11B7 len= 5 raw=[00 00 7F 22 31]
|
||||
HIT 11B8 len= 5 raw=[00 00 7F 22 31]
|
||||
HIT 11BA len= 2 raw=[FF 5C]
|
||||
HIT 11BD len= 5 raw=[87 1E 7F 22 31]
|
||||
HIT 11C1 len= 7 raw=[00 00 62 11 C1 00 00]
|
||||
... 500/1280 scanned in 138s (3.6/s), ~215s remaining, 32 hits so far
|
||||
... 600/1280 scanned in 165s (3.6/s), ~187s remaining, 32 hits so far
|
||||
... 700/1280 scanned in 192s (3.6/s), ~159s remaining, 32 hits so far
|
||||
HIT 1310 len= 2 raw=[1C 92]
|
||||
... 800/1280 scanned in 220s (3.6/s), ~132s remaining, 33 hits so far
|
||||
... 900/1280 scanned in 247s (3.6/s), ~104s remaining, 33 hits so far
|
||||
... 1000/1280 scanned in 274s (3.6/s), ~77s remaining, 33 hits so far
|
||||
HIT 1410 len= 2 raw=[00 00]
|
||||
HIT 1411 len= 5 raw=[00 00 7F 22 12]
|
||||
HIT 1412 len= 2 raw=[00 00]
|
||||
HIT 1434 len= 1 raw=[26]
|
||||
HIT 1440 len= 2 raw=[01 89]
|
||||
HIT 1441 len= 5 raw=[00 00 7F 22 12]
|
||||
HIT 1442 len= 2 raw=[01 88]
|
||||
HIT 1445 len= 2 raw=[01 8F]
|
||||
HIT 1446 len= 2 raw=[00 16]
|
||||
HIT 1447 len= 2 raw=[00 34]
|
||||
HIT 1448 len= 2 raw=[00 11]
|
||||
... 1100/1280 scanned in 303s (3.6/s), ~50s remaining, 44 hits so far
|
||||
HIT 1450 len= 2 raw=[00 00]
|
||||
HIT 1451 len= 2 raw=[00 00]
|
||||
... 1200/1280 scanned in 331s (3.6/s), ~22s remaining, 46 hits so far
|
||||
|
||||
Done. 46 responding PIDs in 353s.
|
||||
|
||||
================================================================
|
||||
6.0 POWER STROKE -- NO-START QUICK TRIAGE
|
||||
================================================================
|
||||
|
||||
NO-CODE no-start basics to check by hand on a 6.0:
|
||||
1. BATTERIES: both must be strong. Low voltage -> FICM won't boost ->
|
||||
no injector fire. Load-test both; ~12.5V+ at rest, hold while cranking.
|
||||
2. FICM voltage while cranking (~48V). The #1 6.0 cold no-start cause.
|
||||
3. ICP (Injection Control Pressure): needs ~500 psi to fire. Big leaks =
|
||||
STC fitting, oil rail O-rings, high-pressure oil hoses.
|
||||
4. FUEL: lift pump (HFCM) priming, fuel filters, water-in-fuel.
|
||||
5. CMP/CKP sensors (see codes above).
|
||||
6. Glow plugs/relay if it's cold out (won't stop start, but hard start).
|
||||
|
||||
|
||||
Done. (Re-run any time; codes persist until cleared.)
|
||||
@@ -0,0 +1,48 @@
|
||||
# Mode-22 scan 1000-14FF
|
||||
# pid_hex len raw_bytes
|
||||
1100 7 01 00 40 05 7F 22 12
|
||||
1101 5 00 62 11 01 00
|
||||
1102 1 00
|
||||
1103 5 00 62 11 03 00
|
||||
1104 5 00 62 11 04 00
|
||||
1105 1 00
|
||||
1106 5 00 62 11 06 04
|
||||
1107 5 00 62 11 07 00
|
||||
110C 4 00 7F 22 12
|
||||
1123 5 1E 62 11 23 30
|
||||
1125 1 FF
|
||||
1126 5 00 62 11 26 00
|
||||
1127 1 E7
|
||||
1135 2 FF FF
|
||||
1139 5 2E 62 11 39 2E
|
||||
114A 5 7E 00 7F 22 12
|
||||
114B 5 A6 56 7F 22 12
|
||||
114D 2 81 40
|
||||
1155 2 FF FF
|
||||
1165 7 00 00 62 11 65 00 00
|
||||
1169 2 00 00
|
||||
1172 5 D1 62 11 72 D3
|
||||
1177 5 00 C8 7F 22 12
|
||||
11B3 1 02
|
||||
11B4 2 00 00
|
||||
11B5 5 00 00 7F 22 31
|
||||
11B6 4 8C 7F 22 31
|
||||
11B7 5 00 00 7F 22 31
|
||||
11B8 5 00 00 7F 22 31
|
||||
11BA 2 FF 5C
|
||||
11BD 5 87 1E 7F 22 31
|
||||
11C1 7 00 00 62 11 C1 00 00
|
||||
1310 2 1C 92
|
||||
1410 2 00 00
|
||||
1411 5 00 00 7F 22 12
|
||||
1412 2 00 00
|
||||
1434 1 26
|
||||
1440 2 01 89
|
||||
1441 5 00 00 7F 22 12
|
||||
1442 2 01 88
|
||||
1445 2 01 8F
|
||||
1446 2 00 16
|
||||
1447 2 00 34
|
||||
1448 2 00 11
|
||||
1450 2 00 00
|
||||
1451 2 00 00
|
||||
@@ -0,0 +1,117 @@
|
||||
================================================================
|
||||
OBD-II Code Reader - ELM327 / 6.0 Power Stroke triage
|
||||
================================================================
|
||||
|
||||
Auto-selected port: COM5 (USB-SERIAL CH340 (COM5))
|
||||
(other ports seen: COM4, COM3 -- pass one as an argument if wrong)
|
||||
Opening COM5 @ 38400 baud ...
|
||||
Adapter : ELM327 v1.5
|
||||
Battery : 11.7V (key ON; healthy 6.0 KOEO ~12.4-12.7V)
|
||||
|
||||
Connecting to vehicle (turn key to ON / RUN, engine off is fine)...
|
||||
Protocol: A6 (CAN=True)
|
||||
|
||||
----------------------------------------------------------------
|
||||
TROUBLE CODES
|
||||
----------------------------------------------------------------
|
||||
|
||||
STORED (mode 03):
|
||||
C0300 - (look up this code)
|
||||
|
||||
PENDING (mode 07):
|
||||
C0701 - (look up this code)
|
||||
P0113 - Intake air temp (IAT) circuit HIGH
|
||||
|
||||
PERMANENT (mode 0A):
|
||||
C3F0A - (look up this code)
|
||||
|
||||
----------------------------------------------------------------
|
||||
KEY LIVE VALUES (key ON)
|
||||
----------------------------------------------------------------
|
||||
Engine RPM 0 rpm
|
||||
Coolant temp 33 C
|
||||
Intake air temp 35 C
|
||||
Intake MAP 98 kPa
|
||||
Engine load 0 %
|
||||
Accel/throttle pos n/a
|
||||
Module voltage 12.39 V
|
||||
|
||||
================================================================
|
||||
WATCH MODE (30s) -- Ctrl-C to stop early
|
||||
================================================================
|
||||
ATRV = battery V at OBD port, VPCM = PCM/module V (PID 0142).
|
||||
TURN KEY TO START NOW (or whenever you're ready).
|
||||
|
||||
t(s) ATRV VPCM
|
||||
-------- -------- --------
|
||||
0.00 11.7 12.39
|
||||
0.54 11.7 12.38
|
||||
1.09 11.7 12.36
|
||||
1.64 12.0 12.55
|
||||
2.17 11.9 12.52
|
||||
2.70 12.0 12.49
|
||||
3.25 12.1 12.66
|
||||
3.79 12.1 12.69
|
||||
4.33 12.0 12.55
|
||||
4.84 12.1 12.71
|
||||
5.35 12.0 12.60
|
||||
5.91 10.8 11.65
|
||||
6.42 11.2 11.59
|
||||
6.93 11.0 11.59
|
||||
7.45 11.1 11.48
|
||||
7.95 10.9 11.43
|
||||
8.47 10.8 11.70
|
||||
8.97 11.2 11.67
|
||||
9.49 11.0 11.70
|
||||
10.01 11.0 11.46
|
||||
10.52 11.0 11.59
|
||||
11.02 11.0 11.48
|
||||
11.54 11.0 11.62
|
||||
12.04 10.9 11.48
|
||||
12.55 11.0 11.40
|
||||
13.07 11.0 11.48
|
||||
13.57 10.9 11.40
|
||||
14.08 11.1 11.43
|
||||
14.59 10.9 11.46
|
||||
15.08 11.0 11.48
|
||||
15.59 11.0 11.59
|
||||
16.09 10.9 11.32
|
||||
16.61 11.5 12.17
|
||||
17.11 11.8 ?
|
||||
17.74 11.8 ?
|
||||
18.37 12.0 ?
|
||||
19.02 11.8 ?
|
||||
19.65 12.0 ?
|
||||
20.27 11.9 ?
|
||||
20.90 12.0 ?
|
||||
21.54 12.0 ?
|
||||
22.18 12.0 ?
|
||||
22.81 12.1 ?
|
||||
23.45 12.2 ?
|
||||
24.10 12.1 ?
|
||||
24.74 12.2 ?
|
||||
25.38 12.0 ?
|
||||
26.01 12.0 ?
|
||||
26.66 12.0 ?
|
||||
27.31 12.1 ?
|
||||
27.95 12.2 ?
|
||||
28.59 12.1 ?
|
||||
29.23 12.2 ?
|
||||
29.87 12.2 ?
|
||||
|
||||
================================================================
|
||||
6.0 POWER STROKE -- NO-START QUICK TRIAGE
|
||||
================================================================
|
||||
|
||||
NO-CODE no-start basics to check by hand on a 6.0:
|
||||
1. BATTERIES: both must be strong. Low voltage -> FICM won't boost ->
|
||||
no injector fire. Load-test both; ~12.5V+ at rest, hold while cranking.
|
||||
2. FICM voltage while cranking (~48V). The #1 6.0 cold no-start cause.
|
||||
3. ICP (Injection Control Pressure): needs ~500 psi to fire. Big leaks =
|
||||
STC fitting, oil rail O-rings, high-pressure oil hoses.
|
||||
4. FUEL: lift pump (HFCM) priming, fuel filters, water-in-fuel.
|
||||
5. CMP/CKP sensors (see codes above).
|
||||
6. Glow plugs/relay if it's cold out (won't stop start, but hard start).
|
||||
|
||||
|
||||
Done. (Re-run any time; codes persist until cleared.)
|
||||
+32
-5
@@ -18,8 +18,14 @@ Pick-up notes for diagnosing the truck in the cab. Repo: `git.jpaul.io/justin/fo
|
||||
## What the tool does / doesn't
|
||||
- DOES: read stored/pending/permanent codes, decode them (6.0 codes flagged as
|
||||
no-start suspects), clear codes (`--clear`), show battery voltage + key live PIDs.
|
||||
- DOESN'T: Ford-enhanced diesel PIDs — **ICP, FICM main/sync voltage, IPR%**.
|
||||
Those need FORScan. For those numbers tonight, measure at the FICM with a meter.
|
||||
- EXPERIMENTAL (`--ford`): tries a small table of community-sourced Ford 6.0
|
||||
Mode-22 PIDs (ICP, IPR%, FICM main/sync/logic V, EBP, EOT). **PID numbers and
|
||||
scaling are TENTATIVE** — raw bytes are always printed; cross-check against a
|
||||
meter or FORScan before trusting a value.
|
||||
- `--pid XXXX`: one-shot probe of any 16-bit Mode-22 PID; prints raw bytes plus
|
||||
a few common decodings so you can eyeball the right scaling.
|
||||
- DOESN'T (yet): a verified Ford-enhanced PID set the way FORScan has. We're
|
||||
building toward that — see follow-ups below.
|
||||
|
||||
## 6.0 no-start priority checklist (from the triage)
|
||||
The 6.0 needs, to fire: **good batteries → FICM ~48V → ICP ~500 psi → fuel → cam/crank signal.**
|
||||
@@ -42,14 +48,35 @@ The 6.0 needs, to fire: **good batteries → FICM ~48V → ICP ~500 psi → fuel
|
||||
- Tool built + tested against the real adapter (init, all 3 DTC modes, live PIDs, clear flow).
|
||||
- DTC parser unit-tested incl. a fixed bug: legacy ISO/PWM multi-frame responses repeat the
|
||||
`43` header (was producing phantom codes) — fixed + regression-tested.
|
||||
- **2026-06-29 in-cab session:** added `--watch`, `--ford`, `--pid`, and `--scan`
|
||||
modes. Captured cranking voltages + ran a Mode-22 brute scan (46 PIDs hit).
|
||||
Full session writeup + raw data in [diagnostics/2026-06-29-no-start/](diagnostics/2026-06-29-no-start/README.md).
|
||||
**Headline:** not batteries, not fuel — almost certainly ICP / FICM / CMP.
|
||||
**Unfinished:** FICM meter test on M/S/L was started but readings never logged.
|
||||
- Pushed to `git.jpaul.io/justin/ford-obd`, branch `main`. Files: `obd_reader.py`,
|
||||
`RUN_OBD.bat`, `README.md`, `README.txt`, `handoff.md`.
|
||||
`RUN_OBD.bat`, `README.md`, `README.txt`, `handoff.md`, `diagnostics/`.
|
||||
|
||||
## To resume with Claude from the cab
|
||||
Mention: "6.0 Power Stroke no-start, using the ford-obd tool (git.jpaul.io/justin/ford-obd)".
|
||||
Then **paste the tool's full output** (codes + live values). Useful to also say: cranks vs.
|
||||
no-crank, hot vs. cold, what changed before it died, and FICM/ICP readings if you metered them.
|
||||
|
||||
## To resume from the desktop (after the 2026-06-29 session)
|
||||
Read [diagnostics/2026-06-29-no-start/README.md](diagnostics/2026-06-29-no-start/README.md)
|
||||
first — that's the full state. Top open items:
|
||||
1. Get FICM M/S/L meter readings (key-ON and during a short crank).
|
||||
2. Re-probe the clean scan hits (1310, 1440-1451, 11Bx) with `--pid XXXX`
|
||||
for uncontaminated data, then map them to known engine conditions.
|
||||
3. Look at FORScan via the CyanLabs mirror to capture ground-truth PIDs.
|
||||
|
||||
## Open follow-ups (when off the truck)
|
||||
- FORScan from the CyanLabs mirror once forscan.org is back, for ICP/FICM/IPR PIDs on the same adapter.
|
||||
- Optional: add a few known Ford Mode-22 enhanced PIDs to the tool (verify PID numbers first).
|
||||
- FORScan from the CyanLabs mirror once forscan.org is back — useful as the
|
||||
ground truth for verifying our Mode-22 PID table.
|
||||
- **Verify** every entry in `FORD_60_PIDS` (obd_reader.py) against either a
|
||||
meter reading or FORScan, one at a time. As each is confirmed, drop the
|
||||
"TENTATIVE" warning for that row.
|
||||
- **Option 3**: brute-force scan of the Mode-22 PID space (e.g. `0x1000`-
|
||||
`0x14FF`) and log which PIDs return data, so we can fingerprint what this
|
||||
PCM exposes without external references. Hook it in as `--scan`.
|
||||
- Add `--watch` mode that streams ATRV + PID 0142 (and the FICM PIDs once
|
||||
verified) at ~5 Hz so we can watch the voltage sag during cranking.
|
||||
|
||||
+335
-8
@@ -11,14 +11,20 @@ Scope (what a generic ELM327 CAN do, engine off / KOEO):
|
||||
* Read key KOEO live PIDs (coolant, IAT, MAP, module voltage, RPM...)
|
||||
* Battery voltage at the OBD port (ATRV)
|
||||
|
||||
What it CANNOT do: Ford-enhanced diesel PIDs (ICP, FICM sync/main volts, IPR%).
|
||||
Those need FORScan's Ford Mode-22 PID set. This tool covers the generic basics
|
||||
so you can read codes and triage tonight.
|
||||
Experimental: with --ford it also tries a handful of community-sourced
|
||||
Ford-enhanced Mode-22 PIDs (ICP, IPR%, FICM main/sync/logic voltage, EBP,
|
||||
EOT). The PID numbers and scaling are TENTATIVE and need to be verified
|
||||
against a known-good reading (meter or FORScan). Raw bytes are always
|
||||
printed so you can sanity-check. Wrong PID = "no response", nothing
|
||||
written to the truck.
|
||||
|
||||
Usage (Windows):
|
||||
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
|
||||
python obd_reader.py --ford # + try Ford 6.0 Mode-22 PIDs
|
||||
python obd_reader.py --pid 1430 # probe one Mode-22 PID, show raw
|
||||
python obd_reader.py --clear # erase stored + pending DTCs
|
||||
|
||||
Requires: pip install pyserial
|
||||
"""
|
||||
@@ -110,6 +116,61 @@ NO_START_CODES = {
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ford 6.0L Power Stroke Mode-22 ("enhanced") PIDs.
|
||||
#
|
||||
# A generic ELM327 CAN'T natively decode Ford-enhanced data (ICP, FICM, IPR%)
|
||||
# the way FORScan can, but the wire protocol is the same: we send Mode 22
|
||||
# ("read data by identifier") with a 16-bit PID, and the PCM replies with
|
||||
# raw bytes that we scale into engineering units.
|
||||
#
|
||||
# WARNING: every entry below is TENTATIVE. The PID numbers and scaling
|
||||
# formulas are drawn from public 6.0 community references (FORScan extended
|
||||
# PID dumps, diesel forum threads) and have NOT been verified against this
|
||||
# specific truck. Treat decoded values as suspect until they're cross-
|
||||
# checked against a meter or a known-good FORScan reading. To help with
|
||||
# that, the tool ALWAYS prints the raw response bytes next to the decode.
|
||||
#
|
||||
# Wrong PIDs are safe -- the PCM just answers "no data" or a negative
|
||||
# response (0x7F). Nothing is written to the truck.
|
||||
#
|
||||
# Each entry: (pid_hex, short_name, units, decoder|None, notes)
|
||||
# pid_hex : 4 hex chars (the 16-bit Mode-22 PID)
|
||||
# decoder : callable(list[int]) -> float, or None to show raw only
|
||||
# ---------------------------------------------------------------------------
|
||||
def _u16(b):
|
||||
return (b[0] << 8) + b[1]
|
||||
|
||||
|
||||
FORD_60_PIDS = [
|
||||
# --- Injection Control Pressure (need ~500+ psi to fire) ---
|
||||
("1209", "ICP", "psi", lambda b: _u16(b),
|
||||
"Injection Control Pressure (need ~500+ psi to fire)"),
|
||||
("121A", "ICP_DES", "psi", lambda b: _u16(b),
|
||||
"ICP desired (commanded)"),
|
||||
("1430", "ICP_V", "V", lambda b: _u16(b) / 1000.0,
|
||||
"ICP sensor raw voltage"),
|
||||
|
||||
# --- Injection Pressure Regulator duty ---
|
||||
("120B", "IPR", "%", lambda b: round(b[0] * 100 / 255, 1),
|
||||
"IPR duty (high = trying hard to make ICP)"),
|
||||
|
||||
# --- FICM voltages -- THE 6.0 no-start metric (~48V cranking) ---
|
||||
("1228", "FICM_MPWR", "V", lambda b: _u16(b) / 1000.0,
|
||||
"FICM Main Power -- want ~48V cranking, <45V = suspect"),
|
||||
("1229", "FICM_SYNC", "V", lambda b: _u16(b) / 1000.0,
|
||||
"FICM Sync Power"),
|
||||
("122A", "FICM_LPWR", "V", lambda b: _u16(b) / 1000.0,
|
||||
"FICM Logic Power (~12V)"),
|
||||
|
||||
# --- Exhaust back pressure / oil temp (handy, not no-start critical) ---
|
||||
("121C", "EBP", "psi", lambda b: _u16(b) / 100.0,
|
||||
"Exhaust Back Pressure"),
|
||||
("1310", "EOT", "C", lambda b: b[0] - 40,
|
||||
"Engine Oil Temperature"),
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ELM327 plumbing
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -187,6 +248,32 @@ class ELM:
|
||||
d = self.protocol.replace("A", "").strip()
|
||||
return d[:1] in ("6", "7", "8", "9")
|
||||
|
||||
def mode22(self, pid_hex, timeout=3.0):
|
||||
"""Send a Mode 22 ("read data by identifier") request.
|
||||
pid_hex: 4 hex chars (e.g. '1430') -- accepts '0x', spaces, lowercase.
|
||||
timeout: serial-read timeout in seconds (lower for scanning).
|
||||
Returns the data bytes AFTER the '62 <hi> <lo>' echo, or None on
|
||||
no-data / error / negative response (0x7F)."""
|
||||
pid_hex = pid_hex.replace("0x", "").replace("0X", "").replace(" ", "").strip().upper()
|
||||
if len(pid_hex) != 4 or any(c not in "0123456789ABCDEF" for c in pid_hex):
|
||||
return None
|
||||
lines = self.cmd("22" + pid_hex, read_timeout=timeout)
|
||||
joined = "".join(lines).upper().replace(" ", "")
|
||||
if any(s in joined for s in ("NODATA", "ERROR", "UNABLE", "STOPPED", "CANERROR", "BUSERROR")):
|
||||
return None
|
||||
data = hexbytes(lines)
|
||||
p_hi = int(pid_hex[:2], 16)
|
||||
p_lo = int(pid_hex[2:], 16)
|
||||
# Positive response: 62 <pid_hi> <pid_lo> <data...>
|
||||
for i in range(len(data) - 2):
|
||||
if data[i] == 0x62 and data[i + 1] == p_hi and data[i + 2] == p_lo:
|
||||
return data[i + 3:]
|
||||
# Negative response: 7F 22 <nrc>
|
||||
for i in range(len(data) - 1):
|
||||
if data[i] == 0x7F and data[i + 1] == 0x22:
|
||||
return None
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.ser.close()
|
||||
@@ -315,6 +402,163 @@ def live_data(elm):
|
||||
return rows
|
||||
|
||||
|
||||
def read_ford_pids(elm, pids=None):
|
||||
"""Run a batch of Ford 6.0 Mode-22 PIDs and print decoded + raw bytes.
|
||||
pids: list of FORD_60_PIDS entries to query (default: all)."""
|
||||
if pids is None:
|
||||
pids = FORD_60_PIDS
|
||||
print("\n" + "-" * 64)
|
||||
print(" FORD 6.0 ENHANCED (Mode 22) -- TENTATIVE / UNVERIFIED")
|
||||
print("-" * 64)
|
||||
print(" PID numbers and scaling are community-sourced and NOT yet")
|
||||
print(" verified on this truck. Raw bytes shown for sanity-checking.")
|
||||
print(" 'no response' = wrong PID or this PCM doesn't expose it.")
|
||||
print()
|
||||
any_response = False
|
||||
for pid, name, unit, decode, notes in pids:
|
||||
raw = elm.mode22(pid)
|
||||
if raw is None or len(raw) == 0:
|
||||
print(f" {name:10} ({pid}) no response -- {notes}")
|
||||
continue
|
||||
any_response = True
|
||||
raw_hex = " ".join(f"{b:02X}" for b in raw)
|
||||
decoded = "(no decoder)"
|
||||
if decode is not None:
|
||||
try:
|
||||
val = decode(raw)
|
||||
decoded = f"{val:>8.2f} {unit}"
|
||||
except Exception:
|
||||
decoded = "(decode err)"
|
||||
print(f" {name:10} ({pid}) {decoded:>14} raw=[{raw_hex}]")
|
||||
if not any_response:
|
||||
print("\n >> Nothing answered. Either none of these PID numbers")
|
||||
print(" match this PCM, or the bus isn't accepting Mode 22 in")
|
||||
print(" the current state. Try with the key in RUN and the")
|
||||
print(" ECU connected (protocol negotiated above).")
|
||||
|
||||
|
||||
def probe_pid(elm, pid_hex):
|
||||
"""One-shot Mode-22 probe for manual exploration. Prints raw + a few
|
||||
common decodings so you can eyeball which scaling fits."""
|
||||
print("\n" + "-" * 64)
|
||||
print(f" Mode-22 probe: PID {pid_hex.upper()}")
|
||||
print("-" * 64)
|
||||
raw = elm.mode22(pid_hex)
|
||||
if raw is None:
|
||||
print(" no response (wrong PID, or module doesn't expose it).")
|
||||
return
|
||||
raw_hex = " ".join(f"{b:02X}" for b in raw)
|
||||
print(f" raw bytes : [{raw_hex}] (len={len(raw)})")
|
||||
if len(raw) >= 1:
|
||||
print(f" as u8 : {raw[0]} ({raw[0] * 100 / 255:.1f}% if duty)")
|
||||
print(f" as temp : {raw[0] - 40} C (raw-40, OBD-II convention)")
|
||||
if len(raw) >= 2:
|
||||
u = (raw[0] << 8) + raw[1]
|
||||
print(f" as u16 : {u} ({u/1000:.3f} V if mV, {u/100:.2f} if x100)")
|
||||
if len(raw) >= 4:
|
||||
u32 = (raw[0] << 24) + (raw[1] << 16) + (raw[2] << 8) + raw[3]
|
||||
print(f" as u32 : {u32}")
|
||||
|
||||
|
||||
def watch_loop(elm, seconds=20, with_ford=False):
|
||||
"""Stream battery + module voltage as fast as we can for N seconds.
|
||||
Designed for cranking: start it, then turn the key.
|
||||
|
||||
with_ford=True also streams Mode-22 FICM/ICP PIDs -- only enable once
|
||||
those PIDs are verified for this PCM, otherwise each 'no response'
|
||||
timeout starves the sample rate."""
|
||||
stream_pids = []
|
||||
if with_ford:
|
||||
stream_pids = [
|
||||
("1228", "FICM_M", "V", lambda b: (b[0] << 8 | b[1]) / 1000.0),
|
||||
("1229", "FICM_S", "V", lambda b: (b[0] << 8 | b[1]) / 1000.0),
|
||||
("122A", "FICM_L", "V", lambda b: (b[0] << 8 | b[1]) / 1000.0),
|
||||
("1209", "ICP", "psi", lambda b: (b[0] << 8 | b[1])),
|
||||
("120B", "IPR", "%", lambda b: round(b[0] * 100 / 255, 1)),
|
||||
]
|
||||
print("\n" + "=" * 64)
|
||||
print(f" WATCH MODE ({seconds}s) -- Ctrl-C to stop early")
|
||||
print("=" * 64)
|
||||
print(" ATRV = battery V at OBD port, VPCM = PCM/module V (PID 0142).")
|
||||
if with_ford:
|
||||
print(" Mode-22 PIDs included -- '-' means no response.")
|
||||
print(" TURN KEY TO START NOW (or whenever you're ready).\n")
|
||||
|
||||
headers = ["t(s)", "ATRV", "VPCM"] + [f"{n}({u})" for _, n, u, _ in stream_pids]
|
||||
print(" " + " ".join(f"{h:>8}" for h in headers))
|
||||
print(" " + " ".join("-" * 8 for _ in headers))
|
||||
|
||||
start = time.time()
|
||||
end = start + seconds
|
||||
try:
|
||||
while time.time() < end:
|
||||
t = time.time() - start
|
||||
atrv = " ".join(elm.cmd("ATRV", read_timeout=1.0)).replace("V", "").strip()
|
||||
vpcm_raw = read_pid(elm, "42", 2)
|
||||
vpcm = "?" if vpcm_raw is None else f"{((vpcm_raw[0] << 8) + vpcm_raw[1]) / 1000:.2f}"
|
||||
row = [f"{t:6.2f}", atrv or "?", vpcm]
|
||||
for pid, _, _, decode in stream_pids:
|
||||
raw = elm.mode22(pid)
|
||||
if raw is None or len(raw) < 2:
|
||||
row.append("-")
|
||||
else:
|
||||
try:
|
||||
row.append(f"{decode(raw):.2f}")
|
||||
except Exception:
|
||||
row.append("err")
|
||||
print(" " + " ".join(f"{c:>8}" for c in row), flush=True)
|
||||
except KeyboardInterrupt:
|
||||
print("\n (stopped)")
|
||||
|
||||
|
||||
def scan_mode22(elm, start, end, log_path=None):
|
||||
"""Brute-force scan Mode-22 PIDs over [start, end] (inclusive, ints).
|
||||
Logs every PID that returns data. Safe: Mode 22 is read-only by
|
||||
definition. Writes hits to log_path as it goes so we don't lose them
|
||||
if the bus drops mid-scan."""
|
||||
# Tune ELM for fast scanning: max adaptive timing, short response wait
|
||||
elm.cmd("ATAT2")
|
||||
elm.cmd("ATST19") # 25 * 4ms = 100ms ECU response wait
|
||||
total = end - start + 1
|
||||
print("\n" + "=" * 64)
|
||||
print(f" SCAN Mode-22 PIDs {start:04X} - {end:04X} ({total} PIDs)")
|
||||
print("=" * 64)
|
||||
print(f" Logging hits to: {log_path}" if log_path else " (no log file)")
|
||||
print(" Safe: Mode 22 is read-only. Will print every PID that answers.\n")
|
||||
|
||||
if log_path:
|
||||
with open(log_path, "w") as f:
|
||||
f.write(f"# Mode-22 scan {start:04X}-{end:04X}\n")
|
||||
f.write("# pid_hex len raw_bytes\n")
|
||||
|
||||
hits = 0
|
||||
t0 = time.time()
|
||||
for i, pid in enumerate(range(start, end + 1)):
|
||||
pid_hex = f"{pid:04X}"
|
||||
raw = elm.mode22(pid_hex, timeout=0.6)
|
||||
if raw is not None and len(raw) > 0:
|
||||
raw_hex = " ".join(f"{b:02X}" for b in raw)
|
||||
line = f" HIT {pid_hex} len={len(raw):2d} raw=[{raw_hex}]"
|
||||
print(line, flush=True)
|
||||
hits += 1
|
||||
if log_path:
|
||||
with open(log_path, "a") as f:
|
||||
f.write(f"{pid_hex} {len(raw)} {raw_hex}\n")
|
||||
if (i + 1) % 100 == 0:
|
||||
elapsed = time.time() - t0
|
||||
rate = (i + 1) / elapsed if elapsed > 0 else 0
|
||||
remain = (total - i - 1) / rate if rate > 0 else 0
|
||||
print(f" ... {i+1}/{total} scanned in {elapsed:.0f}s ({rate:.1f}/s), "
|
||||
f"~{remain:.0f}s remaining, {hits} hits so far", flush=True)
|
||||
|
||||
elapsed = time.time() - t0
|
||||
print(f"\n Done. {hits} responding PIDs in {elapsed:.0f}s.")
|
||||
# Restore default-ish timing
|
||||
elm.cmd("ATAT1")
|
||||
elm.cmd("ATST32")
|
||||
return hits
|
||||
|
||||
|
||||
def clear_codes(elm):
|
||||
"""OBD-II mode 04: clear DTCs + freeze frame, reset monitors.
|
||||
Returns True if the ECU acknowledged ('44')."""
|
||||
@@ -348,12 +592,79 @@ def find_ports():
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
def main():
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("-")]
|
||||
verbose = "-v" in sys.argv or "--verbose" in sys.argv
|
||||
do_clear = "--clear" in sys.argv
|
||||
raw_args = sys.argv[1:]
|
||||
verbose = "-v" in raw_args or "--verbose" in raw_args
|
||||
do_clear = "--clear" in raw_args
|
||||
do_ford = "--ford" in raw_args or "--diesel" in raw_args
|
||||
|
||||
port = args[0] if len(args) >= 1 else None
|
||||
baud = int(args[1]) if len(args) >= 2 else 38400
|
||||
# --pid XXXX : one-shot Mode-22 probe of an arbitrary 16-bit PID
|
||||
probe = None
|
||||
if "--pid" in raw_args:
|
||||
i = raw_args.index("--pid")
|
||||
if i + 1 < len(raw_args):
|
||||
probe = raw_args[i + 1]
|
||||
|
||||
# --watch [N] : stream voltages + FICM/ICP PIDs for N seconds (default 20)
|
||||
do_watch = "--watch" in raw_args
|
||||
watch_secs = 20
|
||||
if do_watch:
|
||||
i = raw_args.index("--watch")
|
||||
if i + 1 < len(raw_args):
|
||||
try:
|
||||
watch_secs = int(raw_args[i + 1])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# --scan START-END : brute-force scan Mode-22 PIDs. Default 1000-14FF.
|
||||
do_scan = "--scan" in raw_args
|
||||
scan_start, scan_end = 0x1000, 0x14FF
|
||||
scan_log = None
|
||||
if do_scan:
|
||||
i = raw_args.index("--scan")
|
||||
if i + 1 < len(raw_args) and "-" in raw_args[i + 1] and not raw_args[i + 1].startswith("-"):
|
||||
try:
|
||||
a, b = raw_args[i + 1].split("-", 1)
|
||||
scan_start, scan_end = int(a, 16), int(b, 16)
|
||||
except ValueError:
|
||||
pass
|
||||
if "--scan-log" in raw_args:
|
||||
i = raw_args.index("--scan-log")
|
||||
if i + 1 < len(raw_args):
|
||||
scan_log = raw_args[i + 1]
|
||||
|
||||
# Positional args: [port] [baud] -- skip flags and their values
|
||||
pos = []
|
||||
i = 0
|
||||
while i < len(raw_args):
|
||||
a = raw_args[i]
|
||||
if a == "--pid" and i + 1 < len(raw_args):
|
||||
i += 2 # always consumes next arg
|
||||
continue
|
||||
if a == "--scan-log" and i + 1 < len(raw_args):
|
||||
i += 2
|
||||
continue
|
||||
if a == "--watch":
|
||||
if i + 1 < len(raw_args) and raw_args[i + 1].isdigit():
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
if a == "--scan":
|
||||
# Consume next arg only if it looks like a range "AAAA-BBBB"
|
||||
if (i + 1 < len(raw_args) and "-" in raw_args[i + 1]
|
||||
and not raw_args[i + 1].startswith("-")):
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
if a.startswith("-"):
|
||||
i += 1
|
||||
continue
|
||||
pos.append(a)
|
||||
i += 1
|
||||
|
||||
port = pos[0] if len(pos) >= 1 else None
|
||||
baud = int(pos[1]) if len(pos) >= 2 else 38400
|
||||
|
||||
print("=" * 64)
|
||||
print(" OBD-II Code Reader - ELM327 / 6.0 Power Stroke triage")
|
||||
@@ -431,6 +742,22 @@ def main():
|
||||
for label, val in live_data(elm):
|
||||
print(f" {label:22} {val}")
|
||||
|
||||
# ---- Ford 6.0 enhanced PIDs (Mode 22) ----
|
||||
if do_ford:
|
||||
read_ford_pids(elm)
|
||||
|
||||
# ---- One-shot manual Mode-22 probe ----
|
||||
if probe:
|
||||
probe_pid(elm, probe)
|
||||
|
||||
# ---- Watch mode: stream voltages for N seconds ----
|
||||
if do_watch:
|
||||
watch_loop(elm, seconds=watch_secs)
|
||||
|
||||
# ---- Brute-force Mode-22 scan ----
|
||||
if do_scan:
|
||||
scan_mode22(elm, scan_start, scan_end, log_path=scan_log)
|
||||
|
||||
# ---- Optional clear (mode 04), only with --clear ----
|
||||
if do_clear:
|
||||
run_clear(elm, all_codes)
|
||||
|
||||
Reference in New Issue
Block a user