- Supports ESP32-C6 and ESP32-S3 modules
- OTA Update via HTTP-server form
- Receive (slave/sniff) and send (master) LIN data
- Baudrate dependant LIN BREAK detection/creation (UART)
- Supports "Classic" (LIN 1.3) and "Enhanced" (LIN 2.x) LIN checksum calculation (auto-detect)
- Adjustable baudrate (at compile time)
- WIFI AccessPoint (AP-Mode) fallback for (initial) configuration, if Router is out of range or password-failure
- Runtime WiFi mode toggle (STA ↔ AP) via BOOT button (hold 3 s) or
WIFIMODEcommand - LED status indication
- Web-Server terminal emulation
- Accessible as Telnet terminal on Port 23
- ID-Scan (0x00-0x3F) for unknown busses
- POLL mode to periodically poll slaves
- candump log format for SavvyCAN analysis
You only need an external LIN transceiver chip like TJA1020 or L9637 or MCP2003. The transceiver is connected to +12V, GND and LIN of the car. On the other side it is connected to the UART1 RX/TX pins, as well as 3.3V (Vcc) and GND of the ESP32.
A pre-built merged binary for ESP32-C6 is provided in the flash/ directory.
No ESP-IDF installation required.
- Python 3.7+
- esptool.py:
pip install esptool
Connect the ESP32-C6 via USB, then:
esptool.py --chip esp32c6 --port COM3 --baud 460800 \
write_flash 0x0 flash/esp32c6_esp32_lin_sniffer_merged.binLinux/macOS: Replace
COM3with your port, e.g./dev/ttyUSB0or/dev/tty.usbserial-*
Windows: Check Device Manager for the correct COM port.
After flashing, the device boots into AP mode if no WiFi credentials are stored:
- SSID:
LIN-Sniffer/ Password:lin12345 - Connect and run:
nc 192.168.4.1 23 - Set credentials:
WIFI <SSID> <PASSWORD>→ reboots and connects automatically.
After idf.py build, run from the project root:
esptool.py --chip esp32c6 merge_bin -o flash/esp32_lin_sniffer_merged.bin 0x0000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0xd000 build/ota_data_initial.bin 0x10000 build/esp32c6_esp32_lin_sniffer.binThe project was build on Visual Studio Code with ESP extension and uses Espressif ESP-IDF v5.5.2.
Some build-time configurations are provided as defines and/or sdkconfig. Use IDF Configuration and look for "LIN Sniffer" settings.
Configurable options are:
- UART Pins for RX (default: GPIO04) and TX (default: GPIO05)
- LIN Bit-Timings (BREAK duration, byte-spaces, timeouts)
- Telnet-Socket port (default: 23)
// GPIO-Pin for UART1
#define LIN_RX_GPIO GPIO_NUM_4
#define LIN_TX_GPIO GPIO_NUM_5
// LIN Baudrate
#define LIN_BAUDRATE LIN_BAUDRATE_9600 // or LIN_BAUDRATE_19200In tools/ subdir you find a analyzing tool, written as pure Web-App so you can open it in any browser you like. It can read CANDUMP logfiles and offer several filter and statistic functions.
Connect via Telnet on port 23 (default) using any terminal-program (e.g. PuTTY), or open any Web-Browser at http:// and use the command-inputfield to issue one of the following commands.
Sends a LIN header (BREAK + SYNC + PID) for the given frame ID and listens for a slave response. The response is printed in candump format.
HEADER 25
→ HEADER sent for ID 0x25 - watch for RX response
→ (1.234567) lin0 025#A1B2C3D4 # RX Classic
Sends a complete LIN frame (BREAK + SYNC + PID + data + checksum). Up to 8 data bytes. ID and data in hex.
SEND 05 00 FF 00
→ OK
→ (1.234567) lin0 005#00FF00 # TX Classic
Continuously sends a LIN header for the given ID at the specified interval. The slave response is printed in candump format for each cycle. An optional third argument limits the duration.
| Form | Behaviour |
|---|---|
POLL 25 100 |
Send ID 0x25 every 100 ms, endless |
POLL 25 100 50 |
Send 50 times, then stop automatically |
POLL 25 100 30s |
Send for 30 seconds, then stop automatically |
Minimum period: 10 ms. If a POLL is already running, it is stopped before the new one starts.
POLL 25 500
→ POLL started: ID=0x25 period=500ms (endless)
→ (1.500000) lin0 025#A1B2C3D4 # RX Classic
→ (2.000000) lin0 025#A1B2C3D4 # RX Classic
...
POLL 0D 200 10s
→ POLL started: ID=0x0D period=200ms (time limit)
→ (0.200000) lin0 00D#... # RX Classic
...
→ POLL stopped: 50 frames sent
Filters candump output to show only the specified frame IDs. Multiple IDs can be specified separated by spaces. Use STOP to deactivate.
FILTER 05 0D 25
→ FILTER active: 3 ID(s)
→ (1.500000) lin0 005#A1B2 # RX Classic
→ (1.600000) lin0 00D#C3D4 # RX Enhanced
→ (1.700000) lin0 025#E5F6 # RX Classic
FILTER 05
→ FILTER active: 1 ID(s)
Starts logging LIN bus traffic in the specified format. By default, no logging is active — frames are received but not output until you start logging.
CANDUMP format — compatible with standard Linux candump tools:
LOG CANDUMP
→ LOG started: CANDUMP format
→ (1.500000) lin0 005#400B # RX Classic
→ (1.600000) lin0 00D#CF1FC244 # RX Classic
→ (1.700000) lin0 000# # UNANSWERED
HUMAN format — human-readable table:
LOG HUMAN
→ LOG started: HUMAN format
→ 05 | 40 0B CLA | @. |
→ 0D | CF 1F C2 44 CLA | .... |
→ 00 | UNANSWERED --- | -------- |
→ 2B | 03 31 30 43 36 37 39 00 CLA | .10C6790 |
Format columns:
- ID: LIN frame ID (00-3F)
- Data Bytes: Up to 8 data bytes in hex (24 chars wide)
- CRC:
CLA(Classic) orENH(Enhanced),---for unanswered - ASCII: Printable characters or
.for non-printable
Use STOP to stop logging.
Stops a running POLL, SLAVE simulation, or FILTER (or any combination if multiple are active).
STOP
→ POLL stopped
STOP
→ SLAVE stopped
STOP
→ POLL and SLAVE and FILTER stopped
STOP
→ Nothing running
Scans all LIN frame IDs from 0x00 to 0x3F. For each ID that receives a slave response, the data is printed. Multi-frame responses (8 bytes with a counter in byte 0) are collected and assembled automatically. Ford part numbers are detected and formatted.
SCAN
→ Scanning IDs 0x00-0x3F ...
→ +++ LIN Bus Scan (0x00 - 0x3F) +++
→ 0x05 00 FF 00 AA [....]
→ 0x25 A1 B2 C3 D4 E5 F6 00 01 [......]
→ 0x0D MULTIFRAME (3 Blöcke)
→ Block 01: 41 47 39 4E 2D ...
→ Block 02: 31 30 43 36 37 ...
→ Block 03: 39 2D 44 45 00 ...
→ → Part#: "AG9N-10C679-DE"
→ ══════════════════════════════════════════
→ Scan abgeschlossen. 3 Frame-ID(s) aktiv.
⚠️ SCAN takes approximately 25 seconds (64 IDs × ~400 ms each).
Saves new WiFi credentials to NVS and reboots immediately to connect.
WIFI MyNetwork MyPassword123
→ OK: Rebooting...
If WiFi connection fails after 10 retries, the device falls back to AP mode automatically:
- SSID:
LIN-Sniffer - Password:
lin12345 - IP:
192.168.4.1
Switches the WiFi mode and reboots. The selected mode is stored in NVS and survives reboots.
| Command | Effect |
|---|---|
WIFIMODE AP |
Forces AP mode on next boot (ignores stored credentials) |
WIFIMODE STA |
Returns to STA mode (uses stored credentials, falls back to AP if none) |
WIFIMODE AP
→ OK: WiFi mode set to AP, rebooting...
WIFIMODE STA
→ OK: WiFi mode set to STA, rebooting...
BOOT button shortcut: Hold the BOOT button (GPIO9 on ESP32-C6) for 3 seconds to toggle the mode directly on the device — no terminal needed. The LED flashes red briefly before the reboot.
Prints frame counters since last boot.
STATUS
→ RX: 142 TX: 38 Valid: 130 Unanswered: 12
Reboots the device.
REBOOT
→ Rebooting...
Activates LIN slave simulation. Once enabled, the sniffer listens for the specified frame ID on the bus (sent by the BCM/master) and immediately responds with the given data bytes. Checksum is calculated automatically (Enhanced). Simulation runs until STOP is issued or a new SLAVE command replaces it.
SLAVE 05 A1 B2 C3 D4
→ SLAVE sim active: ID=0x05, 4 byte(s)
→ (1.500000) lin0 005#A1B2C3D4 # SIM Enhanced
→ (1.700000) lin0 005#A1B2C3D4 # SIM Enhanced
...
Replace the response without stopping first:
SLAVE 05 FF 00 00 00
→ SLAVE sim active: ID=0x05, 4 byte(s)
⚠️ Response timing depends on FreeRTOS scheduling (~0.5–2 ms after PID). Whether the master accepts the response depends on its timeout window.
Prints a one-line command summary.
HELP
→ HEADER <ID> | SEND <ID> <data> | POLL <ID> <ms> [<count>|<N>s] | SLAVE <ID> <data> | STOP | SCAN | WIFI <SSID> <PW> | WIFIMODE AP|STA | STATUS | REBOOT | HELP
- All IDs and data bytes are in hexadecimal (
25,0x25andA1are all valid). - Commands are case-insensitive (
poll 25 100=POLL 25 100). - Output is in candump format:
(timestamp) lin0 ID#DATA # label checksumtype - Unanswered frames are shown as:
(timestamp) lin0 ID# # UNANSWERED - Slave simulation output is labeled
SIM:(timestamp) lin0 ID#DATA # SIM Enhanced - Telnet IAC negotiation bytes are filtered automatically.
This project is under MIT-Licence and can be freely used, modified or distributet.