[ Car Hacking ]
Platform note: Most of the scripts, message IDs, and byte layouts here — and most of the FCA-specific reference guides linked above (Bus & Message Reference, UDS Read / Write Operations, Secure Gateway Module, In-Vehicle Event Handlers) — assume JEEP-platform vehicles (Wrangler / Gladiator / Grand Cherokee / similar). FCA / Stellantis reuses some IDs across its lineup but reassigns others aggressively between platforms and model years — especially on CAN-IHS. Always candump on YOUR vehicle before assuming an ID does what it does here. For vendor-neutral CAN material, start with the CAN bus introduction guide.
[ Guides & Tutorials → ]
Two flavors of long-form material — Tutorials (hands-on labs) and
Reference Guides (read-once protocol / architecture material):
Tutorials (ordered beginner -> applied)
— CAN bus — a generic introduction
vendor-neutral primer: physical layer, arbitration, frame
anatomy, what rides on top (ISO-TP / UDS / OBD-II / J1939 /
CANopen), tooling shortlist, where to physically tap the bus
(OBD-II port + behind-glovebox SGW bypass), safety practice
— Sniffing CAN bus with the ESP32 Bit-Pirate
ESP32-S3 + MCP2515 as a pocket sniffer: wiring, bitrate, sniff
to flash, pull over Wi-Fi, convert to a SavvyCAN GVRET CSV
— Jeep CAN bus primer
hands-on first-time-users walkthrough on the JL / JT
platform: Waveshare HAT setup, can0 vs can1, listen-only
safety, TTCAN matrix-cycle scheduling, safe-to-send sample
cansend
— Python Canbus Lab Guide
set up a virtual CAN lab with python-can / can-utils, replay a
drive log, watch traffic in tkcan.py / pyJeepCan.py
— Reverse Engineering UDS with JScan
sniff UDS traffic from a known-good scan tool and decode it
byte-by-byte
Reference Guides
— FCA CAN Bus & Message Reference
CAN-C vs CAN-IHS topology, decoded message IDs with byte-level
evidence, JEEP live-data message map
— UDS Read Operations on FCA / Stellantis
Service 0x22 ReadDataByIdentifier, module catalog, DID discovery
patterns, ISO-TP framing, SGW pass-through — the read-side
counterpart to UDS Write Operations below
— UDS Write Operations on FCA / Stellantis
Services 0x2F / 0x2E / 0x31 / 0x27, two cleanup patterns, known
writable targets, SGW interaction, safety discipline
— Secure Gateway Module (SGW / SGM)
what it is, what it gates, AutoAuth, bypass trade-offs
— In-Vehicle Event Handlers
candump state-machine loop, edge triggering, three cleanup
disciplines, hardware lifecycle, bus-quiet reconnect
— Sniffing ISO-TP Messages over CAN
SF / FF / CF / FC frame types, isotpdump walkthrough
For the full landing page covering all of the above with descriptions
and cross-links, visit guides.html.
[ Hardware ]
In-vehicle Raspberry Pi 4 rig used to run the scripts on this page. Designed for permanent JEEP installation: two CAN buses captured simultaneously (CAN-IHS + CAN-C via Waveshare 2-channel HAT), graceful sleep / wake on the vehicle's 12V, survives crank brown-outs via Zero2Go Omni supercaps, software-controlled cooling for the parked-in-sun-at-130°F case. Total parts roughly $200-250 new. Full parts list with rationale for each piece, plus the why-Zero2Go deep-dive on crank brown-outs, sleep-state cuts, and the autocollect engineshutdown hook pairing, lives on the dedicated page: dev-stack.html.
DIY OBD-II Diagnostic Cable 05.2026
My preferred way to access the CAN bus on JEEPs: the unused ports behind the glovebox, which expose two of the vehicle's 13-way CAN connectors with full read AND write access on both CAN-C and CAN-IHS. There's always a free port in this location, it's out of the way of anything plugged into the OBD-II port, and there's plenty of room to add Pi / adapter hardware for testing. Bonus: you can isolate which module owns which ID by unplugging components at the connector while sniffing. Only real downside, aside from taking the dash apart, is no direct 12V supply in the area — power has to be routed in from elsewhere. On 2018+ FCA / Stellantis vehicles this access point matters more than ever: the 13-way connectors behind the glovebox sit inside the Secure Gateway Module boundary. The SGW gates traffic arriving from the OBD-II port (and the Uconnect head unit) but does NOT see traffic from the internal-side taps. Direct behind-the-glovebox access talks straight to CAN-C and CAN-IHS BEFORE the SGW filters anything — no AutoAuth subscription, no certified scan tool, no $50/year fee, no aftermarket bypass cable required. Full read AND write at this entry point. If pulling the glovebox is too much effort, the OBD-II port works as a fallback — with caveats. On a 2018+ vehicle without an SGW bypass, OBD-II is effectively read-only: VIN, stored DTCs (Diagnostic Trouble Codes — the standardised fault codes like P0420 or U0101 that ECUs store when something goes wrong, what scan tools report and what makes the MIL light up), live PIDs, freeze-frame, readiness monitors. Anything that writes (clear DTCs, bi-directional actuator tests, ECU coding, key learning, module configuration) returns NRC 0x33 (securityAccessDenied) or silently drops. The cable design in this guide works in both modes: pre-2018 vehicles get full access through OBD-II; 2018+ vehicles get the read-only subset unless you've already installed a bypass. Writing the wrong bytes to the CAN bus — from either entry point — can have real-world consequences (stuck immobilizer, bricked module, ABS fault that needs a dealer reflash). Always cross-reference IDs against the Bus & Message Reference and verify with candump before any write. Informational guide only; can definitely void warranties.
Mirror of the JL Wrangler factory wiring-diagram pack — 181 PDFs, ~64 MB zipped — covering every documented electrical system on the platform. Useful for physical RE work that the CAN-bus side of the site doesn't cover: figuring out which connector pin carries which signal, which module a wire actually runs to, what fuse / relay sits in the path, and which connector body is which. A non-exhaustive sample of what's in the pack: CAN-C-BUS-LAYOUT.pdf bus wiring + module location map AUTOMATIC-SWAY-BAR.pdf sway-bar disconnect actuator circuit AXLE-LOCKER-SYSTEM.pdf e-locker motor + control circuit TRANSFER-CASE.pdf 4WD / Rock-Trac / Command-Trac wiring BACK-UP-CAMERA-SYSTEM.pdf camera + radio interconnect VEHICLE-THEFT-SECURITY-SYSTEM.pdf RKE / immobilizer wiring TIRE-PRESSURE-MONITORING-(TPM)-SYSTEM.pdf TPMS module + RF interface GROUND-LOCATION-LAYOUT.pdf chassis ground-stud map (~5 MB, a full vehicle ground reference) AUDIO-SYSTEM-BASE / -PREMIUM, BRAKE-SYSTEM, CABIN-HEATER-SYSTEM, BACK-UP-LAMPS, BLIND-SPOT-MONITOR, MIRROR-EXTERIOR, PARK-LAMPS, PARK-ASSIST, plus ~165 more. Pairs naturally with the Bus & Message Reference: when a BMR entry says "$26F is the sway-bar request packet," the diagram pack tells you where the sway-bar actuator physically sits in the wiring tree and which connector to probe with a scope. Sway-bar specific PDF isAUTOMATIC-SWAY-BAR.pdf; locker isAXLE-LOCKER-SYSTEM.pdf; etc. Source: rehosted from the JL Wrangler Forums wiring-diagrams thread (community share, original poster credited on jlwranglerforums.com). Best-effort applies to JL Wrangler (2018+) and most of it transfers directly to the JT Gladiator since the two share most of their electrical architecture — refer to the JT vs JL deltas section for the small set of JT-specific differences. Not official Stellantis service information; treat as a reference, not a wiring guarantee.
[ Scripts ]
The cards below highlight the main scripts by function — read, analyze, then write. The full set (40+ bash and Python tools, each with a one-line description) lives in the scripts directory →.
[ Live data readers — request-response & broadcast ]
Two scripts that issue OBD-II Service 0x01 (ShowCurrentData) reads against the ECM on $7E0 / $7E8, returning the same sensor data via two different language stacks. Pair them with obd-sweep.sh below, which wraps either as its underlying reader. OBD-II Mode 01 lives on the same CAN-C bus and same ECM as the UDS diagnostic services but uses a different service set: OBD-II Mode 01 read sensor data by PID, no session unlock needed, every car since ~1996 supports it UDS session control, IOControl, RoutineControl, etc -- manufacturer-specific, write-capable (see UDS Read / Write Operations) SGW note: Mode 01 reads pass through the Secure Gateway Module on 2018+ FCA vehicles without needing AutoAuth — read-only and standardised. Mode 04 (Clear DTCs) and the UDS write services are still gated. Path 1 — obd.sh — bash + can-utils Sends one PID at a time and prints the response as space-separated decimal bytes; you apply the OBD-II decode formula yourself. Two-stage retry (0.2 s then 0.5 s) handles transient capture misses; multi-frame (PCI 0x10) responses reassembled automatically. Example — read engine RPM (PID 0x0C; formula((A*256)+B)/4): $ ./obd.sh 0c 17 184 ((17*256)+184)/4 = 1134 RPM Read intake air temp (PID 0x0F, formula A-40 °C): $ ./obd.sh F 78 78 - 40 = 38 °C CLI: --wait SEC, --retry-wait SEC, --verbose, --help. Polish by magikh0e fixed real bugs (ineffective2>/dev/nullon variable assignments, REQUIIRES typo, etc); original preserved as obd.legacy.txt. Path 2 — obd2.py — Python + python-can Same job, richer ergonomics: — ~55 PIDs catalogued with descriptions, units, and per-PID decoders (engine RPM, coolant temp, fuel trim, MAP, MAF, O2-sensor voltage+trim, ambient temp, control-module voltage, odometer, fuel type enum, OBD-standard enum, monitor-status bitfields, charge-air-cooler temp, ...). — Batch mode: pass multiple PIDs in one invocation (obd2 05 0c 0dreads coolant, RPM, speed in one bus-up cycle). — Discovery mode:obd2 -plists every PID the script knows how to decode. — Bitfield PID-support decoders for the OBD-II "what's supported here" queries (0x00 [01-20], 0x20 [21-40], 0x40 [41-60], 0x60 [61-80], 0x80 [81-A0], 0xA0 [A1-C0]). Sample output (engine running, idle): $ obd2 04 05 06 07 0C 0D 0F 42 A6 Calculated Engine Load: 21.57 % Engine Coolant Temperature: 82 C Short Term Fuel Trim - Bank 1: -0.78 % Long Term Fuel Trim - Bank 1: -2.34 % Engine RPM: 951.0 rpm Vehicle Speed: 0 km/h Intake Air Temperature: 32 C Control module voltage: 14.159 V Odometer: 28655.4 km Polish by magikh0e fixed several real bugs in the dispatch table: a duplicateelif pid == 0x01(one unreachable; the second clearly intended for Fuel System Status / PID 0x03 — renamed and added to pid_info); PID 0x60 had an unreachablereturnshadowing the real bit-decoded logic; PIDs 0x15, 0x41, 0x4F were returning non-2-tuple values that broke the main loop's unpack; thebit_encoded_pid_support"skip A1-A6 reserved bits" filter was applying to ALL PIDs-supported queries (dropping legitimate data for 0x00 / 0x20 / 0x40 / 0x60 / 0x80) when it should only fire for 0xA0;send_obd_querywas re-creating the SocketCAN bus on every call; deprecatedbustype=updated tointerface=. Originally by jmccorm (2023, with ChatGPT assist). When to use which — obd.sh for piping raw decimal bytes into another tool, shell integration, or a minimal-deps Pi rig where Python is overkill. — obd2.py for live monitoring with human-readable values and units, batch reads in one bus-up cycle, or as a starting template when adding new PIDs with custom decoders. — obd-sweep.sh below wraps either: default calls obd.sh for raw decimal bytes; pass--decodedto use obd2.py for human-readable values. PID references — Wikipedia OBD-II PIDs full standardised PID list with formulas. — JL Wrangler RE spreadsheet (tab 2) FCA / JEEP-specific PIDs not in the public spec.
obd-sweep.sh 05.2026
Companion wrapper that loops through every PID jmccorm has seen on a JEEP platform and prints each response with a separator banner. Useful for spotting PIDs that return broken / nonsensical / silent data on YOUR vehicle — running a known set in fixed order makes it trivial to scan the output by eye for outliers (NO RESPONSE entries, out-of-range values like 240°C coolant temp, fields that don't track vehicle state changes). By default calls obd.sh for raw decimal bytes; pass--decodedto use obd2.py for human-readable values with units instead. The list of ~56 PIDs is curated — not the full 0x00-0xFF range, just the ones jmccorm's Wrangler responds to plus a few common ones worth checking. Each is annotated in-source with what it's supposed to return and the unit, so you can correlate observed bytes with field meaning while reading the output. Quote from jmccorm: "You'll find that some of [these PIDs] are broken in a really obvious way." Hunting which ones is left as an exercise for the reader's own vehicle — results vary by year, trim, and module software revision. Original by jmccorm (he called the underlying query toolobd2ain his own setup); this site's version wires it to the on-siteobd.sh/obd2.pyand adds the PID-list annotations.
dtc.sh 05.2026
Reads OBD-II Diagnostic Trouble Codes — the codes lighting the MIL (Malfunction Indicator Lamp; also called CEL / check engine light). Sends Service 0x03 ShowDTCs and prints each stored code in canonical SAE format (P0420,C0561,B1320,U0101, etc.). Three modes selected by--mode: confirmed Service 0x03 — MIL-on codes (default) pending Service 0x07 — detected but not yet confirmed across enough drive cycles permanent Service 0x0A — emissions codes that can't be cleared by tool action alone Handles ISO-TP multi-frame responses automatically — sends a Flow Control frame and reassembles consecutive frames in sequence order, so it works on 4+ DTCs (where the response spills past one CAN frame). Decodes the 2-byte DTC encoding (top-2-bit letter prefix, then 14-bit hex code) per SAE J2012. Vehicle must be powered on (ignition Run is enough; engine doesn't need to be running). Sends on CAN-C ($7E0) and listens on $7E8. Read-only, passes the SGW on 2018+ FCA vehicles without AutoAuth. This script does NOT clear codes — Service 0x04 ClearDTCs is a destructive write, handled by the dedicated dtc-clear.sh below (requires explicit--yesto actually write, previews current DTCs via this script before clearing). OBD-II Mode 03 framing is documented at Bus & Message Reference #obd-ii; the 569-code JK Wrangler catalog with grouped subsystem listings lives at JK Wrangler — DTC Code Reference.
battery.sh 05.2026
Reads battery voltage from CAN-IHS message $2C2 (broadcast frequently while the vehicle is awake). Byte 2 of the 8-byte payload is the voltage in tenths-of-a-volt — raw 0x78 prints as12.0 vdc, 0x86 as13.4 vdc, etc. 2-second candump window with an ID filter (candump -L can0,02C2:0fff); if the bus is asleep, the script sends one $2D3 NM-wake frame and retries. All-FF payload is treated as a hard error (bus disconnected / sentinel value). Useful for cron-driven battery health checks on a parked vehicle, or as a quick "is the alternator sagging under load?" probe during ignition or accessory transitions. $2C2 full layout (and the locker- state-vs-battery-voltage history that initially confused community RE work) is documented at Bus & Message Reference #id-2c2.
getVehicleTime.sh 05.2026
Reads the JEEP dashboard clock from CAN message $350 on CAN-IHS (broadcast once per second on both buses). Default output is ISO 8601 (e.g.2026-05-16 09:21:05);--format us|epoch|jsonswitches to legacy m/d/yyyy, Unix epoch, or one-line JSON for piping into jq. The all-FF sentinel (uninitialised clock after battery disconnect) gets its own error message distinct from "no frame seen". $350's full byte layout — seconds / minutes / hours / 2-byte year / month / day, stored as raw hex NOT BCD — is documented in detail at Bus & Message Reference #id-350. CLI: --format iso|us|epoch|json, --retries N (1Hz $350 doesn't always hit a 1s capture window), --verbose, --help.
Two scripts that return the same 17-character VIN via completely different CAN-bus mechanisms. Worth pairing — together they illustrate the core split between passive bus sniffing and active UDS request/response. Path 1 — getVIN.sh — passive sniff Listens to CAN-IHS message $3E0 (broadcast at ~0.1s per frame, three frames per sweep). Byte 0 of each frame is the sequence id (0, 1, 2); bytes 1-7 carry ASCII VIN characters; the last frame's tail is 0x00 padding. Reassembled by sequence index, not arrival order, so out-of-order receipt on a busy bus still produces the right VIN.--strictvalidates against ISO 3779 (17 chars, A-Z minus I/O/Q, 0-9). CLI: --retries N, --strict, --verbose, --help. The tidied version drops thebindechexasciidependency (uncommon on modern distros), replaces the brittle cut/fold/paste reassembly with parameter expansion, and reassembles by sequence id so out-of-order frames work. Original jmccorm-era recipe preserved as getVIN.legacy.txt. Path 2 — read_vin_uds.py — active UDS request Sends UDS Service 0x22 (ReadDataByIdentifier) on DID 0xF190 (the ISO-standardised VIN identifier) directly to the BCM ($620 / $504), using the python-can + python-can-isotp + udsoncan Python stack. ISO-TP handles multi-frame reassembly at the library layer; udsoncan exposes the full ISO 14229 service menu (0x11 ECUReset, 0x22 RID, 0x2E WID, 0x23 ReadMemoryByAddress, 0x2F IOControl, 0x31 RoutineControl, 0x34 / 0x36 / 0x37 Download / Upload, ...) so the same scaffold serves as a starting template for any other DID. The motivating WRITE use case (per jmccorm's notes accompanying this script): update the JL Wrangler's door-configuration DID via Service 0x2E so the BCM thinks no doors are installed — which would re-enable Remote Start with the doors removed, without the sense-voltage workarounds at the door switches. When to use which — Bus sniffing is faster to script and works without any session setup, but it waits for the next broadcast cycle (~0.3 s) and only works for values the vehicle voluntarily broadcasts. — UDS request is instant and works for ANY DID the BCM exposes — software version, serial number, configuration bytes, not just VIN. Requires ISO-TP setup and a healthy session. The SGW lets Service 0x22 through on 2018+ FCA without AutoAuth only because 0x22 is read-only; the write services beside it don't get the same treatment. — Pick getVIN.sh for "what's parked at this connector right now" without touching the bus; pick read_vin_uds.py when you need a starting template for richer UDS work on any module. See also — UDS Read Operations full architecture for the Path 2 stack — module catalog, DID discovery patterns, ISO-TP framing, SGW pass-through. — BMR: Service 0x22 worked example the equivalent UDS walkthrough using bare can-utils (isotpsend+isotprecv) with byte-by-byte decode. Polish by magikh0e on both: getVIN.sh tidied per above; read_vin_uds.py had deprecated python-canbustype=replaced withinterface=and CAN_IFACE / TX_ID / RX_ID / DID pulled to top-level constants. Originals by jmccorm (read_vin_uds.py with acknowledged ChatGPT-4 assist, 03.2023).
mute.sh 05.2026
Reads or sets the radio mute state via CAN broadcast. With no argument, prints the current state (ON / OFF / UNKNOWN); withonoroff, drives the radio to the requested state and verifies the change took effect. Read path listens on $25D (CAN-C) and decodes byte 3 (zero-indexed): 0x00 = unmuted, 0x03 = muted. Write path sends the steering-wheel "MUTE pressed" signal on the wake bus (2D3#0700010000000000— same arbitration ID as the NM wake frame, byte 2 = 0x01 instead of 0x00). The radio treats the frame as a hardware button press and toggles its mute state; the script re-reads $25D to confirm and toggles again if the state still isn't right, up to MAXTRIES total toggles. Bus-quiet handling: if the first listen sees no $25D traffic the script sends an NM wake frame ($2D3 with byte 2 = 0x00) and retries with a longer collect window; a second silent retry extends the window further before giving up withFAILURE: Could not read the mute status. Original by jmccorm. Polish by magikh0e: inlined the previously- external/home/pi/bin/wakehelper so the script is self-contained, and documented the byte-offset arithmetic in the header.
[ Display & analysis tools ]
Two terminal dashboards that render live vehicle stats from two different protocol families. Same goal — one screen of streaming numbers from the car — opposite ends of the broadcast-vs-polling axis. Trades richness for portability; read on for which way each leans. Path 1 — pyJeepCan.py — passive broadcast sniff Curses TUI rendering one row per monitored field, decoded from CAN-IHS broadcasts that the JEEP modules emit on their own schedule. Monitored fields: battery voltage, roll / tilt / yaw, RPM / MPH, gear (PRNDL), transfer case, steering angle / rate, IAT, coolant temp, oil temp / pressure, power-steering temp / PSI — ~12 distinct values lifted from JEEP-specific message IDs documented at Bus & Message Reference #live-data-map. Multi-channel architecture: one python-can Bus per interface, threaded readers feeding a shared queue, curses display loop dispatches by (channel, arbitration_id). Press q to quit. Adding new fields is one entry inbuild_monitors()plus a small decode helper if needed. The--experimentalflag enables the unverified candidate IDs from candidate-IDs (fuel %, ambient temp, throttle, cruise, 4-tire TPMS, odometer) — expect false readings on any platform but the one the legacy script was tuned for; verify with candump first. CLI: --ihs IFACE, --c IFACE (default vcan0 / vcan1 for bench rigs), --experimental, --log-decode FILE (append every decoded frame for offline analysis), --help. Pair with the Python CAN Bus Lab guide below to run against a replayed log without a vehicle present. Path 2 — engine-dash.sh — active OBD-II Mode 01 polling Polls 11 standardised OBD-II PIDs (RPM, speed, throttle, engine load, coolant / oil / intake / ambient temps, fuel level, run time, control-module voltage) in a loop and redraws a fixed-size ASCII frame each cycle. Refresh is ~2.5 s on a typical ECU; values shown in both unit systems where they have them (mph + km/h, °F + °C). Tested against the JEEP Gladiator (JT); per the JT vs JL deltas section, every PID in the script is confirmed-working on that platform — but the underlying protocol (OBD-II Service 0x01 on $7E0 / $7E8) is standardised, so the script works on any 2007+ OBD-II vehicle. Standalone — no dependencies on other on-site scripts. Issues OBD-II Service 0x01 requests directly viacansend/candump, parses single-frame and first-frame PCI responses inline, mirrors the same two-stage retry logic that obd.sh uses for its single-PID queries. Only runtime dep iscan-utils.--onceprints one frame and exits (useful for cron / log-ingestion pipes); default is the live loop with Ctrl+C clean exit. Single-PID timeout shows "--" for that field but doesn't kill the dashboard; a total-blackout first poll exits with a clear "is the vehicle powered on?" message and a one-liner curl-style reproducer to test request / response by hand. When to use which — pyJeepCan.py for the rich JEEP-specific data — transfer-case state, steering angle, roll / tilt / yaw, oil pressure — that the OBD-II spec doesn't expose. Passive sniff so it adds zero bus load. Tied to the JEEP message-ID catalog; retargeting to a non-JEEP platform means rewritingbuild_monitors(). — engine-dash.sh for portable standardised data on any OBD-II vehicle. Smaller field set (11 PIDs) but the script runs unchanged on a Ford, Toyota, VW, anything 2007+. Actively polls $7E0 / $7E8 so it adds bus load (one request per PID per cycle). Minimal-deps — just can-utils, no Python. — On a JEEP rig where you want both, run them side-by-side in separate tmux panes: pyJeepCan watches CAN-IHS broadcasts, engine-dash polls CAN-C OBD-II. Different buses, different protocols, no contention.
log_reader.sh 05.2026
Replay a candump-L-format log file and render decoded vehicle state as one-line-per-tick text output. The text-mode counterpart to pyJeepCan.py's curses dashboard, but designed for OFFLINE replay rather than live capture — useful for inspecting a recorded drive at human-readable speed without the vehicle present. Decoded per line: TIME timestamp from log KEY ignition state from $122 -- named codes (Kill, Acc, Off, Strt, Crnk, RRun, RAcc, Run). Full state-code table at BMR #id-122. BRK brake pedal % from $079 ACCL accelerator pedal % from $07B (with --alternates also showing throttle valve %) RPM engine RPM from $322 WHEEL steering angle + direction from $023 DIR compass heading (8 cardinal directions) from $358 GEAR PRNDL from $340 ODOM odometer (miles) from $3D2 MPH vehicle speed from $322 Plus three user-configurable "flag" slots (set hex IDs at the top of the script) for ad-hoc exploration -- great for "what does $XYZ change during this maneuver?" diff-against-a-known-log work. Real bug fix in the polish pass: the legacy version wrote per-message state to/run/tmpfiles.d/— systemd's directory for declarative tmpfiles configuration, NOT a place for app state. Polished version usesmktemp -d /tmp/log_reader.XXXXXXwith an EXIT trap for cleanup. Original preserved as log_reader.legacy.txt. Original by jmccorm; polish by magikh0e. USAGE:./log_reader.sh drive.log
bitpirate_to_savvycan.py — Bit-Pirate CAN log to SavvyCAN CSV 07.2026
Converts an ESP32 Bit-Pirate CAN sniff log into a SavvyCAN-native (GVRET) CSV, with optional DBC decoding — drop the result straight into SavvyCAN for filtering, decode, and graphing. Bit-Pirate prints one frame per line, which the parser keys off of: 26 | ID: 0x123 | DLC: 8 | Data: AA BB 01 02 03 04 05 06 The match is deliberately loose — it skips the leading inbox emoji and any non-frame status lines, trusts the bytes actually present (clamping a lying DLC down to what's really there), and flags any ID above0x7FFas extended. Output is SavvyCAN's native V2 format:Time Stamp,ID,Extended,Dir,Bus,LEN,D1..D8, timestamp in microseconds. Timing is synthetic. Bit-Pirate emits no per-frame timestamp, so frames are spaced--intervalmicroseconds apart (default 1000 = 1 ms). Frame ORDER is preserved but absolute and relative timing is fabricated — fine for decoding IDs and payloads, DBC work, and ASCII scans; do NOT trust it for bus-timing or inter-frame-gap analysis.--dbc car.dbcalso writes a<out>_decoded.txtsidecar of human-readable signal values — scalar signals in Intel or Motorola byte order, signed/unsigned, with factor/offset. Multiplexed signals and CAN-FD are out of scope; for those, load the CSV plus the .dbc into SavvyCAN, which decodes the full spec. USAGE:bitpirate_to_savvycan.py capture.txtwritescapture_savvy.csv; add--dbc car.dbcfor the decoded sidecar. To pull the log off the device first, see bp_fetch.py below. Source: github.com/magikh0e/bitpirate-to-savvycan
bp_fetch.py — pull a Bit-Pirate capture over Wi-Fi 07.2026
Pulls a capture straight off an ESP32 Bit-Pirate's LittleFS over its Wi-Fi web interface, and can hand it to bitpirate_to_savvycan.py in one step. It talks to the device's/littlefs/listand/littlefs/downloadHTTP endpoints — standard-libraryurllibonly, nothing to install. Run it on the same network as the device, or joined to its own AP: bp_fetch.py 192.168.4.1 --list bp_fetch.py 192.168.4.1 --file cap.txt -o cap.txt bp_fetch.py 192.168.4.1 --file cap.txt --convert--listdumps the LittleFS listing;--filedownloads one capture;--convertchains straight into the converter (add--dbcto pass a DBC through for the decoded sidecar), so a capture goes from device to SavvyCAN-ready CSV in one command. Source: github.com/magikh0e/bitpirate-to-savvycan
nibble_extract.py 2023
Small importable helper for pulling nibble-aligned bitfields out of a CAN payload (or any byte sequence). Lets you extract bytes, 12-bit words, 4-bit flags, or any other non-byte-aligned signal without writing per-field shift / mask arithmetic. Payload: [ 0xA5, 0xB7, 0xC3, 0x12 ] Nibble index: 0 1 2 3 4 5 6 7 Nibble value: A 5 B 7 C 3 1 2 extract_by_nibble(0, 3, payload) -> 0xA5B7 (bytes 0-1, 16-bit) extract_by_nibble(0, 2, payload) -> 0xA5B (12-bit word) extract_by_nibble(6, 6, payload) -> 0x1 (byte 3 high nibble) The 12-bit and 4-bit cases are where this earns its keep — they come up constantly in CAN reverse engineering (RPM packed as 12 bits, gear position as 4 bits, turn signals as a single nibble, etc.) and the byte-aligned arithmetic for them is tedious and error-prone by hand. Pairs naturally with the per-byte signal specs on the Bus & Message Reference — once you know "byte 3 high nibble = blinker state" you can spell it asextract_by_nibble(6, 6, payload)and stop counting shifts on your fingers. The script also runs as a self-test if you invoke it directly (python3 nibble_extract.py) — 10 cases covering single nibbles, whole bytes, 12-bit and 16-bit words, and the three invalid-input paths. Originally by jmccorm (2023). Polish by magikh0e: fixed a realNameError— the parameter was namednibble_startbut the function body referencedstarting_nibble(the original would have crashed on the first call). Added a negative-start bounds check (nibble // 2rounds toward negative infinity in Python, giving wrap-around byte indices that yielded plausible-but-wrong results), type hints, docstring, and the runnable self-test.
[ Broadcast actuator controls — non-UDS button & display writes ]
These scripts cause vehicle state changes by emitting CAN broadcast frames that mimic hardware button presses or display updates, rather than going through the UDS diagnostic path. No session unlock, no SGW interaction — just the same frames real steering-wheel buttons and the radio head unit emit during normal use.
evic.sh 05.2026
Displays arbitrary text on the JEEP EVIC music-information page (the
three-line metadata region of the cluster display between the
speedometer and tachometer). Writes via $328 broadcast on CAN-IHS;
takes a LINE number (1 = input name, 2 = artist, 3 = title) and a
free-form string.
Examples:
$ ./evic.sh 2 "magikh0e"
$ ./evic.sh 3 "Pi inside the dash"
Each character is encoded as a UTF-16BE pair (00 followed by the
ASCII code); strings are right-padded with 00 00 pairs to a multiple
of three characters; the resulting frames are sent in reverse order
(remaining-frames counter counts down in byte 0) and committed with
an all-zero $328 terminator frame. Full byte layout at
Bus & Message Reference
#id-328.
The radio also natively broadcasts $328 whenever a real audio source
updates — SiriusXM track changes, Bluetooth metadata, USB next-song
presses. Hijacking the message means temporarily masking whatever the
radio was about to display; once the user changes track the radio
refreshes its own state and overwrites your text.
Original by jmccorm (01.2022). Polish by magikh0e: fixed a lowercase
$function debug typo that silently dropped the function
value, added line-number validation, documented the byte 0 / byte 1
/ UTF-16BE encoding in the header so the message can be reproduced
from the script's source.
More broadcast actuator scripts — same non-UDS, button-mimic mechanism as evic.sh above:
wake.sh # wake the bus with a simulated center-dash button press
autocool.sh # remote-start HVAC, driver temp to LOW
autoheat.sh # remote-start HVAC, driver temp to MAX
ventmode.sh # read or set the HVAC vent mode
[ OBD-II writes — diagnostic clears ]
Writes via the OBD-II diagnostic protocol — single Mode byte, ECM-targeted, standardised across every OBD-II-compliant vehicle. Operationally adjacent to the UDS write services (see UDS Write Operations) but uses the simpler OBD-II framing instead of full ISO 14229. SGW-gated on 2018+ FCA vehicles when sent through the OBD-II port; the behind-the-glovebox CAN connectors bypass that gate.
dtc-clear.sh 05.2026
Destructive-write counterpart to dtc.sh. Issues OBD-II Service 0x04 ClearDiagnosticInformation to the ECM on $7E0 / $7E8 and clears stored DTCs, pending DTCs, freeze-frame data, and readiness-monitor status in one shot. The MIL (Malfunction Indicator Lamp — also called CEL or the check engine light) turns off immediately if the clear is accepted. What it clears — Stored DTCs (Mode 0x03 codes — the ones lighting the MIL) — Pending DTCs (Mode 0x07 codes) — Freeze-frame data (Mode 0x02 snapshots) — Readiness monitors reset to "not ready" What it does NOT clear — Permanent DTCs (Mode 0x0A) — regulatory requirement; these clear only after the ECM's own monitor for that fault completes N times without recurrence. Service 0x04 cannot bypass. — DTCs on non-OBD-II modules (BCM / SCCM body and chassis codes) — those need UDS Service 0x14 against the module's address. Safety design — Requires --yes / -y to actually write. Without it, the script prints a summary and exits without sending. No interactive prompts — deliberate; an explicit flag is harder to fire by accident than a y/n that you might mash through. — Previews current DTCs first via dtc.sh (Mode 0x03 + Mode 0x07) so you can see what's about to be wiped.--skip-previewdisables this for faster operation. — --dry-run mode shows the exactcansendframe that would be sent without sending it, for sanity-checking before a real run. — Decodes NRC bytes on negative response — especially NRC 0x33 (securityAccessDenied) gets a specific "the SGW is blocking this" hint pointing at the behind-the-glovebox workaround. Example session (preview then clear): $ ./dtc-clear.sh # preview-only, refuses to write About to send: OBD-II Service 0x04 ... Currently stored DTCs (via dtc.sh): ---- P0420 - Catalyst Efficiency (Bank 1) ---- Refusing to clear without explicit confirmation. Re-run with --yes (or -y) to actually issue the clear. $ ./dtc-clear.sh --yes # actually clear Sending Service 0x04 ClearDiagnosticInformation... SUCCESS: ECM acknowledged the clear (positive response 0x44). SGW note: Service 0x04 is gated by the Secure Gateway Module on 2018+ FCA / Stellantis vehicles when sent through the OBD-II port. Expect NRC 0x33 securityAccessDenied unless you're sending from inside the SGW boundary (the behind-the-glovebox 13-way CAN connectors) or running on a pre-2018 vehicle. The defaultCAN_C=can1assumes the behind-the-glovebox wiring; override via theCAN_Cenvironment variable. Why the MIL might come back on: Service 0x04 clears the record, not the cause. If the underlying fault is still present, the next monitor run re-detects it and re-sets the DTC. Some emissions monitors take dozens of miles of mixed driving to re-complete after a clear, which is why "did you reset the codes?" is the first question inspection stations ask if your readiness monitors show "not ready."
[ Links & Resources → ]
Curated outbound links: foundational CLI utilities, Python libraries for UDS / security work, DBC data sources, GUIs, and open-source CAN hardware. Twelve entries across four sub-buckets, all on their own page now — same structure as pubHardwareHacking/HardwareHackinglinks.html. Foundational — can-utils SocketCAN userspace CLI (candump, cansend, isotpdump...) — python-can Foundational Python CAN library, swappable backends — can-isotp kernel module In-kernel ISO-TP framing, mainlined ~5.10 UDS toolkits — udsoncan Pure-Python UDS client (ISO 14229) — caringcaribou Automotive security testing framework (recon, scan, fuzz) — scapy Packet manipulation with automotive extensions DBC data & tooling — opendbc comma.ai-maintained DBC repo across many platforms — cantools Python lib + CLI for DBC files — JL Wrangler RE Spreadsheet Community CAN data for the 2018+ JL platform GUI / simulation / hardware — SavvyCAN Qt-based GUI for CAN RE + capture — ICSim Craig Smith's CAN RE training simulator — comma.ai panda Open-source 3-bus USB-CAN hardware For the full landing page with descriptions and per-entry cross-links, visit links.html.
