[ 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 ]