[ Sniffing CAN bus with the ESP32 Bit-Pirate ]

The ESP32 Bit-Pirate is a Bus-Pirate-style multi-tool firmware for the
ESP32-S3 — a web-based CLI that speaks a pile of protocols, CAN among
them. Bolt an MCP2515 module onto it and you have a pocket CAN sniffer
that captures to its own flash and hands the log off over Wi-Fi. No
laptop tethered to a SocketCAN adapter, no dashboard torn apart.

This walks the whole loop: wire it to the bus, sniff, pull the capture
off, and open it in SavvyCAN for decoding — using the two small
converter scripts from the Scripts section. New to CAN itself? Read
the generic CAN bus introduction first; this guide assumes you know
what an arbitration ID and a data frame are.

[ What you need ]

An ESP32-S3 board with at least 8 MB of flash — a plain S3
    DevKit, LILYGO T-Embed, M5 Cardputer, Seeed Xiao S3, etc. (any S3
    that Bit-Pirate supports).
  — An MCP2515 CAN module — the common blue SPI board pairing an
    MCP2515 controller with a TJA1050 transceiver, CANH/CANL screw
    terminals, and an INT pin. Bit-Pirate drives CAN through this,
    not the ESP32's built-in controller.
  — Jumper wires and a way onto the bus — an OBD-II breakout, or
    a tap on the vehicle connector you're after.
  — A host machine with Python 3 and SavvyCAN for the analysis end.
The 3.3 V / 5 V gotcha. Most MCP2515 modules are 5 V parts, and
the ESP32 is 3.3 V. Powering the whole module at 5 V feeds 5 V back
out of the MCP2515's SO (MISO) pin into a 3.3 V ESP32 input — over
spec. The clean fixes are a logic-level shifter on the SPI lines, or
the well-documented module mod that runs the MCP2515 at 3.3 V while
keeping the TJA1050 at 5 V. Bench-test on a spare bus before you trust
it on a vehicle.

[ Wiring the MCP2515 ]

The module talks to the ESP32 over SPI, and to the car over CANH / CANL.

SPI side — module → ESP32-S3

    VCC    3.3 V / 5 V   (see the gotcha above)
    GND    GND
    CS     chip-select GPIO
    SO     MISO GPIO
    SI     MOSI GPIO
    SCK    SCK GPIO
    INT    interrupt GPIO   (optional but recommended)

Pick free, SPI-capable GPIOs on your board — the exact pins vary by
S3 model, and you tell Bit-Pirate which ones you used with config
(next section), so there's no fixed mapping to memorize. Keep the SPI
leads short.

Bus side — module → vehicle

    CANH   →  CAN High
    CANL   →  CAN Low

On the OBD-II connector that's pin 6 (CAN-H) and pin 14 (CAN-L) for
the powertrain bus. On a JEEP you can also tap CAN-C or CAN-IHS behind
the glovebox — see the Jeep CAN primer for those connectors.

Termination. These modules ship with a 120 Ω resistor across
CANH/CANL. A vehicle bus is already terminated at both ends, so a third
120 Ω tap loads it down — for a short read-only sniff it's usually
tolerated, but if the bus gets cranky, lift that resistor (cut the jumper
or desolder it).

[ Flash & connect ]

Flash ESP32-Bit-Pirate onto the S3 the same way as any S3 firmware —
follow the install steps in the project's repo. (For a refresher on
browser-based ESP32 flashing in general, the ESP32 web flasher
write-up covers the mechanics.)

Once it's running, reach the web CLI one of two ways:

  — Its own Wi-Fi AP — join the Bit-Pirate access point, then point
    a browser at http://192.168.4.1/ (the ESP32 SoftAP default).
  — USB serial — open the serial console at the firmware's baud for
    the same command prompt.

Everything below is typed at that Bit-Pirate prompt.

[ Configure the CAN mode ]

Enter CAN mode, then use config to set the SPI pins you wired and
the bus bitrate. Bit-Pirate supports 125, 250, 500, and 1000 kbps and
snaps whatever you give it to the nearest one.

Match the bitrate to the bus you're on — guess wrong and you'll see
nothing (or error frames):

    500 kbps    OBD-II powertrain / most modern CAN-C
    125 kbps    JEEP CAN-IHS (interior / comfort bus)
    250 kbps    a lot of J1939 / medium-duty

The on-site Bus & Message Reference lists which JEEP bus runs at
which speed. When in doubt, try 500 first; a correctly-clocked idle bus
still shows steady broadcast traffic within a second or two.

    config        set SPI pins (CS / SO / SI / SCK) and CAN speed
    status        show MCP2515 controller state and error flags

Run status after config — if the MCP2515 isn't wired right or the
bitrate is wrong, its error flags light up here before you waste time
staring at an empty sniff.

[ Sniff the bus ]

With pins and speed set, start the capture:

    sniff         capture and print every CAN frame
    send [id]     transmit a frame        (writes to the bus — see Safety)
    receive [id]  wait for a specific ID

sniff is passive — it only listens, so it's the safe place to start.
Frames print one per line, and the format is exactly what the converter
script downstream expects:
📥 26 | ID: 0x123 | DLC: 8 | Data: AA BB 01 02 03 04 05 06
— a running counter, the arbitration ID, the data length, and the
payload bytes. Let it run while you exercise the thing you're chasing
(press a button, open a door, blip the throttle) so the frames that
change get captured. Bit-Pirate writes the session to its LittleFS
flash, browsable and downloadable from the web interface.

[ Get the capture onto your machine ]

You could copy the serial text into a file by hand, but the tidy way is
to pull it straight off the device's flash over Wi-Fi with
bp_fetch.py — stdlib Python, nothing to install:

    python3 bp_fetch.py 192.168.4.1 --list                 list the flash
    python3 bp_fetch.py 192.168.4.1 --file can_capture.txt  download it

It hits Bit-Pirate's /littlefs/list and /littlefs/download HTTP
endpoints, so run it on the same network as the device (or joined to its
AP). Both scripts live in the Scripts section below and in the
bitpirate-to-savvycan repo.

[ Convert to SavvyCAN ]

SavvyCAN doesn't read Bit-Pirate's log directly, so run it through
bitpirate_to_savvycan.py, which rewrites it as a SavvyCAN-native
(GVRET) CSV:

    python3 bitpirate_to_savvycan.py can_capture.txt

That writes can_capture_savvy.csv. Or skip a step — bp_fetch.py
with --convert downloads and converts in one shot:

    python3 bp_fetch.py 192.168.4.1 --file can_capture.txt --convert

One catch — timing is synthetic. Bit-Pirate's sniff log carries no
per-frame timestamp, so the converter spaces frames a fixed
--interval apart (default 1 ms). Frame order is preserved, but the
absolute and relative timing is fabricated — perfect for decoding IDs
and payloads, DBC work, and ASCII scans; do not read bus-timing or
inter-frame gaps off it.

Pass a DBC with --dbc car.dbc and it also drops a
<out>_decoded.txt sidecar of human-readable signals (scalar signals,
Intel or Motorola byte order, signed/unsigned, factor/offset).

[ Analyze in SavvyCAN ]

Open SavvyCAN and load the CSV with File → Load Logs — pick the
GVRET/CSV format. From there the usual moves:

  — Filter by ID to isolate one module's chatter from the flood.
  — Sort / group to spot which IDs change when you actuated something.
  — Load a .dbc (File → Load DBC) to decode raw bytes into named
    signals — SavvyCAN handles the full DBC spec, multiplexing and all,
    where the sidecar decoder above only does plain scalars.
  — Graph a signal over the capture to watch it move.

Cross-reference anything you find against the Bus & Message Reference
for IDs that are already decoded, and remember the timing caveat — the
X-axis is frame order, not real seconds.

[ Safety ]

sniff is read-only and safe on a live vehicle. send and receive
are not — send puts real frames on the bus, and blasting arbitration
IDs at a moving car is how you set faults, drop modules into limp mode,
or worse. Capture and understand first; only transmit once you know what
an ID does and you're parked with nothing depending on the bus.

Writing the wrong bytes can require a dealer reflash to undo. The
CAN bus intro covers the general safe-practice rules; they all apply here.

[ Scripts ]

[ See Also ]