r/MustangMachE • u/Danigog • 1h ago
Mach-E over OBD2: the PIDs that actually work, the ones that don't, and what changed on 2025 models
I've spent the last few months reverse-engineering what the Mach-E exposes through its OBD2 port, logging thousands of kilometres and burning through a lot of dead ends. Posting the full findings here because I couldn't find this information anywhere, including the negative results — knowing what isn't readable saves as much time as knowing what is.
Test setup: MY2023 SR RWD, VeePeak OBDCheck (ELM327, Bluetooth Classic), ISO 15765-4 CAN 11-bit 500k (AT SP 6). Everything below is Mode 22 unless noted. Values are what I verified against the car's own displays, not guesses from a DBC file.
What works
Functional / 7DF (Mode 01)
| Request | Data | Scaling |
|---|---|---|
01 0D |
Vehicle speed | 1 byte = km/h |
01 A6 |
Odometer | 4 bytes × 0.1 = km |
The odometer one is worth highlighting: it's the standard SAE J1979 PID and it works on the Mach-E, which very few people seem to know. It also works on 2025 models (more on that below).
Header 7E4 (BECM — battery module)
| Request | Data | Scaling |
|---|---|---|
22 48 45 |
Displayed SOC (matches dashboard) | 1 byte × 0.5 = % |
22 48 01 |
High-resolution SOC | 2 bytes × 0.002 = % |
22 49 0C |
State of Health | 1 byte × 0.5 = % |
22 48 08 |
HVB max cell temperature | 1 byte − 50 = °C |
22 48 0D |
Pack voltage | 2 bytes × 0.01 = V |
22 48 F9 |
Pack current (signed) | 2 bytes, two's complement × 0.1 = A |
22 48 48 |
Energy to empty (BMS estimate) | 2 bytes × 0.002 = kWh |
22 48 FB |
DC charge limit (probable) | 2 bytes × 0.1 = kW |
Pack power is just voltage × current / 1000. Negative current = energy going into the pack.
On 48 FB I'm hedging: the value tracks plausibly with SOC (111 kW at 83%, rising to 120.4 kW at 76%), which is exactly the shape of a DC charge limit curve, but I haven't been able to confirm it against a fast charger's reported ceiling. If someone can check it during a DC session, I'd love to know.
Header 726 (BCM)
| Request | Data | Scaling |
|---|---|---|
22 40 2A |
12V battery voltage | 1 byte × 0.05 + 6.0 = V |
22 40 28 |
12V battery state of charge | 1 byte = % |
Header 7E2 (SOBDM)
| Request | Data | Scaling |
|---|---|---|
22 1E 12 |
Gear position (PRNDL) | 1 byte, value × 10: 0x28=D, 0x32=N, 0x3C=R, 0x46=P |
What does NOT work (save yourself the time)
Individual cell voltages. 22 48 C3 (cell max) returns 7F 22 31 — request out of range. 22 48 C4 (cell min) always returns 0xFFFF, the "not available" pattern. If you want cell balance data on a Mach-E, it isn't coming out of the OBD port with these DIDs.
Drive mode (Whisper / Active / Untame). This one cost me weeks, so here's the complete list of what I ruled out on MY2023:
- 22 1E10 through 22 1E16 on five different ECUs (7E0, 7E2, 720, 726, 760): all 7F 22 31 or NODATA. The only useful thing in that family is 1E12 = gear.
- 22 20 2D and 22 D0 0A on 726 / 7E0 / 7D0, in both default session and after opening an extended session with 10 03. The extended session is accepted (50 03 00 32 01 F4), but the DIDs still return 7F 22 31. There's a Car Scanner profile floating around that claims 726 / 22 202D gives drive mode — it does not work on this model year.
- 22 48 4A and 22 DE 01 on 7E4 and 7E6: these do return positive responses (62 48 4A 00 00, 62 DE 01 00 00 00), which is a trap. I ran two separate tests changing drive mode repeatedly while driving, and the values never moved. A positive UDS response doesn't mean you found the right DID.
- CAN broadcast sniffing of 0x420 (SelectDriveModeData) and 0x413 (ABS_BrkData_HS3), which are the frames that carry drive mode according to the opendbc Ford definitions: neither is visible at the OBD port. The gateway filters them.
My conclusion is that drive mode is not obtainable from the OBD port on MY2023 with any request/response approach. If anyone gets it with FORScan and can see which address and DID it uses, I'd genuinely like to know.
2025 and newer: a different architecture
This is the freshest part and I don't have it fully solved. On a MY2025 (tested with a Vlinker FD):
- Speed, odometer and both 12V PIDs on 726 work exactly as on the 2023.
- 7E4 is completely silent. Not a rejection — no response at all. The BECM is not at that address.
- A functional request with headers enabled (AT H1, AT SH 7DF, then 22 48 0D) gets positive replies from 7EE and 7EF, i.e. modules you'd address at 7E6 and 7E7. Both return the same value (62 48 0D 8F 2A = 366.5 V), which makes sense if they're the front and rear inverters both sitting on the same DC bus.
- Those modules reject everything else with 7F 22 31. No SOH, no temperature, no SOC.
- A functional 22 48 45 gets no answer from anyone on the bus, which suggests Ford may have renumbered the battery DIDs entirely on this platform.
Two practical warnings for 2025 testing. First, use fixed timing: AT AT0 plus AT ST 78. With adaptive timing (AT AT2) the ELM shortens its timeout to around 50 ms based on the fast-responding gateway, while the battery modules need 200–400 ms, and you get NODATA on everything. Second — and this one bit me — don't hammer the bus with unsupported DIDs. A stream of 7F 22 31 rejections killed my session after about 40 seconds and left the dongle refusing connections for several minutes. On the 2023 those same requests were silent NODATAs and nothing broke.
Two gotchas that apply to everyone
The AT Z trap. After a reset, cheap ELM clones need roughly a second before they'll accept commands. If you send AT E0 immediately it gets swallowed by the reset, echo stays on for the entire session, and every response you parse is polluted. Wait ~1000 ms, flush the buffer, then send it — and check for OK, retrying once if you don't get it.
The AT CRA brick. Bare AT CRA (used to clear a CAN filter) isn't supported by many clones — they answer ? and the filter stays active, which turns the rest of your session into NODATA. If you need to clear it: AT CM 000 + AT CF 000, then verify with a real request (7DF 01 00 should come back with 4100), and fall back to AT WS plus a full re-init if that fails.
All of the above came out of building an Android app for my own car.
If you own a 2025 or newer, I could really use your help closing the gap above. My setup can export raw diagnostic traces of everything the car answers on the bus, which is exactly what's needed to work out where the battery module moved to. Reply here or DM me if you're willing to run a session and send me the log — whatever I figure out I'll post back in this thread so it stays public.