diff --git a/diagnostics/2026-06-29-no-start/README.md b/diagnostics/2026-06-29-no-start/README.md new file mode 100644 index 0000000..9b01dd7 --- /dev/null +++ b/diagnostics/2026-06-29-no-start/README.md @@ -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. diff --git a/diagnostics/2026-06-29-no-start/mode22-scan-full.log b/diagnostics/2026-06-29-no-start/mode22-scan-full.log new file mode 100644 index 0000000..a95bc18 --- /dev/null +++ b/diagnostics/2026-06-29-no-start/mode22-scan-full.log @@ -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.) diff --git a/diagnostics/2026-06-29-no-start/mode22-scan-hits.txt b/diagnostics/2026-06-29-no-start/mode22-scan-hits.txt new file mode 100644 index 0000000..ff6da9f --- /dev/null +++ b/diagnostics/2026-06-29-no-start/mode22-scan-hits.txt @@ -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 diff --git a/diagnostics/2026-06-29-no-start/watch-cranking.log b/diagnostics/2026-06-29-no-start/watch-cranking.log new file mode 100644 index 0000000..973814e --- /dev/null +++ b/diagnostics/2026-06-29-no-start/watch-cranking.log @@ -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.) diff --git a/handoff.md b/handoff.md index 02ef20d..e3e5c35 100644 --- a/handoff.md +++ b/handoff.md @@ -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. diff --git a/obd_reader.py b/obd_reader.py index 53a8524..bd30710 100644 --- a/obd_reader.py +++ b/obd_reader.py @@ -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 # 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 ' 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 + 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 + 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)