[ 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 GuidesFCA 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.
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 is AUTOMATIC-SWAY-BAR.pdf; locker is
AXLE-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 ]

obd.sh & obd2.py — OBD-II PID reading two ways 2023
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 (ineffective 2>/dev/null on
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 0d reads coolant, RPM, speed in one
    bus-up cycle).
  — Discovery mode: obd2 -p lists 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 duplicate elif pid == 0x01 (one unreachable; the
second clearly intended for Fuel System Status / PID 0x03 —
renamed and added to pid_info); PID 0x60 had an unreachable
return shadowing the real bit-decoded logic; PIDs 0x15,
0x41, 0x4F were returning non-2-tuple values that broke the main
loop's unpack; the bit_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_query was
re-creating the SocketCAN bus on every call; deprecated
bustype= updated to interface=.  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
    --decoded to use obd2.py for human-readable values.

PID referencesWikipedia 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 --decoded to 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 tool
obd2a in his own setup); this site's version wires it
to the on-site obd.sh / obd2.py and 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 --yes to 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 as 12.0 vdc,
0x86 as 13.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.
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|json switches
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.
getVIN.sh & read_vin_uds.py — reading the VIN two ways 03.2023
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.
--strict validates against ISO 3779 (17 chars, A-Z minus
I/O/Q, 0-9).  CLI: --retries N, --strict, --verbose, --help.

The tidied version drops the bindechexascii dependency
(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 alsoUDS 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-can bustype= replaced with
interface= 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); with
on or off, 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 with
FAILURE: Could not read the mute status.

Original by jmccorm. Polish by magikh0e: inlined the previously-
external /home/pi/bin/wake helper so the script is
self-contained, and documented the byte-offset arithmetic in the
header.

[ Display & analysis tools ]

pyJeepCan.py & engine-dash.sh — live dashboards two ways 05.2026
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 in build_monitors()
plus a small decode helper if needed.

The --experimental flag 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 via
cansend / 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 is can-utils.

--once prints 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 rewriting
    build_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.
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 uses mktemp -d /tmp/log_reader.XXXXXX with 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 above 0x7FF as 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 --interval microseconds
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.dbc also writes a
<out>_decoded.txt sidecar 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.txt writes
capture_savvy.csv; add --dbc car.dbc for 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/list and
/littlefs/download HTTP endpoints — standard-library
urllib only, 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

--list dumps the LittleFS listing; --file
downloads one capture; --convert chains straight into the
converter (add --dbc to 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
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 as extract_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 real
NameError — the parameter was named
nibble_start but the function body referenced
starting_nibble (the original would have crashed on
the first call).  Added a negative-start bounds check
(nibble // 2 rounds 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:

[ 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 designRequires --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-preview
    disables this for faster operation.
  — --dry-run mode shows the exact
    cansend frame 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 default CAN_C=can1
assumes the behind-the-glovebox wiring; override via the
CAN_C environment 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."
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.

  Foundationalcan-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 toolkitsudsoncan
       Pure-Python UDS client (ISO 14229)
    — caringcaribou
       Automotive security testing framework (recon, scan, fuzz)
    — scapy
       Packet manipulation with automotive extensions

  DBC data & toolingopendbc
       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 / hardwareSavvyCAN
       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.

[ See Also ]