# K-Line — Multi-Protocol Automotive Bus Interface (ESP32) ## Attribution Circuit design and protocol library based on **[muki01/I-K_Bus](https://github.com/muki01/I-K_Bus)** by [@muki01](https://github.com/muki01), licensed under MIT. Optocoupler schematic, IbusSerial library, E46 command codes, and bus documentation are from that project. **R2 modified from 470Ω to 220Ω** for 3.3V ESP32 compatibility (original design targets 5V Arduino). ## Project Overview Multi-protocol automotive bus library for ESP32. Supports **BMW I/K-Bus** (optocoupler isolated) and **OBD-II K-line** (ISO 9141 / ISO 14230). Both protocols share the same physical layer (single-wire, half-duplex, open-collector/drain) but differ in framing, baud rate, checksum, and bus access model. **BMW I/K-Bus:** Module control on E31/E38/E39/E46/E53/E83/E85/E87 — lights, windows, locks, multimedia. Uses PC817 optocouplers for galvanic isolation from the vehicle's electrical system. **OBD-II K-line:** Standard diagnostic protocol (ISO 9141/14230) for reading PIDs, clearing DTCs, and ECU communication. Used on Ford Fiesta (Tucker project) and other ISO 9141/14230 vehicles. Can use either transistor or optocoupler interface. ### Protocol Comparison: BMW I/K-Bus vs OBD-II K-line | Parameter | BMW I/K-Bus (this project) | OBD-II K-line (Tucker) | |-----------|---------------------------|----------------------| | **Baud rate** | 9600 | 10400 | | **Framing** | 8E1 (even parity) | 8N1 (no parity) | | **Bus topology** | Multi-master (any module can talk) | Master/slave (tester initiates) | | **Checksum** | XOR of all bytes | Additive sum mod 256 | | **Init sequence** | None (always-on bus) | 5-baud wake-up or fast init pulse | | **Message format** | Source + Length + Dest + Data + XOR | Header + Source + Mode + PID + Sum | | **Idle detection** | ~1.5ms bus quiet before TX | 5.5s quiet before init | | **Isolation needed** | Yes (multi-master, risk to bus) | Nice to have (point-to-point) | | **Vehicles** | BMW E31/E38/E39/E46/E53/E83/E85/E87 | Ford and other ISO 9141/14230 | ### Why Different Hardware The transistor circuit (Tucker) shares ground with the car and directly drives the K-line. Fine for OBD-II because it's a dedicated diagnostic port — the tester is the only external device, and the ECU expects it. BMW I/K-Bus is a live multi-master network with dozens of modules (GM5, IKE, LCM, RAD, MFL, DSP, etc.) all communicating constantly. Injecting signals requires: 1. **Galvanic isolation** — a ground fault from your board can't propagate to the entire bus network 2. **Bus contention awareness** — you must listen for 1.5ms of silence before transmitting 3. **Proper framing** — even parity, XOR checksum, valid source/destination addresses The optocoupler design satisfies requirement #1. The IbusSerial library handles #2 and #3. ## Circuit Design — Optocoupler Interface Reference image: `reference/muki01-optocoupler-schematic.png` Alternative (non-isolated): `reference/muki01-transistor-schematic.png` ### Schematic Components ``` I/K-Bus Side (12V) MCU Side (3.3V/5V) ================== ==================== 12V ────── D1 (1N4007) ──────────────── 12V (to regulator) (reverse protection) I/K-Bus ─── R1 (2k) ──→ U1 pin 1 U1 pin 4 ←── 5V/3.3V (LED anode) (PC817) (collector) GND ←── U1 pin 2 U1 pin 3 ──→ RX_Pin (LED cathode) (emitter) R4 (1k) pull-down to GND ┊ photons ┊ I/K-Bus ←── U2 pin 4 U2 pin 1 ←── R2 (220) (collector) (LED anode) │ U2 pin 3 U2 pin 2 ←── Q1 collector (BC547) (emitter) (LED cathode) │ Q1 base ←── R3 (10k) ←── R5 (470) ←── TX_Pin GND Q1 emitter ──→ GND ``` ### RX Path (Bus -> MCU via U1) - Bus HIGH (12V idle): Current = (12V - 1.2V_LED) / 2k = **5.4mA** through U1 LED - PC817 CTR (Current Transfer Ratio) >= 50% at 5mA -> phototransistor saturates - U1 emitter (pin 3) pulled toward collector voltage (VCC) -> RX = HIGH - Bus LOW (module transmitting): No LED current -> phototransistor OFF -> RX pulled LOW by R4 **Signal inversion:** Bus HIGH -> RX HIGH, Bus LOW -> RX LOW. Polarity is preserved because the emitter-follower config doesn't invert. ### TX Path (MCU -> Bus via U2 + Q1) - TX HIGH (idle): Through R5 (470) + R3 (10k) -> Q1 base driven -> Q1 ON -> current through U2 LED via R2 (220) -> U2 phototransistor ON -> pulls bus node LOW - TX LOW (transmitting): Q1 OFF -> U2 LED OFF -> phototransistor OFF -> bus released (goes HIGH via bus pull-up) **Signal inversion:** TX HIGH -> bus LOW. This inverts the UART signal. The software must account for this, or use `SoftwareSerial` with inverted logic, or the bus idle state interpretation handles it. The muki01 library is written for this hardware — it works as-is. ### D1 (1N4007) Purpose Bridges the bus-side 12V to the MCU-side 12V rail. This powers the MCU (via a 3.3V regulator) from the car battery while maintaining signal isolation through the optocouplers. The diode blocks reverse current if the MCU has its own power source (USB during development). ## Bill of Materials | Ref | Value | Package | Qty | Notes | |-----|-------|---------|-----|-------| | U1, U2 | PC817A+ | DIP-4 | 2 | Optocoupler, any grade (A/B/C/D). PC817A works; B+ adds margin | | Q1 | BC547 | TO-92 | 1 | Or 2N3904 — TX LED driver | | R1 | 2k | 0805/TH | 1 | RX LED current limiter | | R2 | 220 | 0805/TH | 1 | TX LED current limiter (reduced from 470 for 3.3V ESP32) | | R3 | 10k | 0805/TH | 1 | Q1 base resistor | | R4 | 1k | 0805/TH | 1 | RX pull-down (defines LOW when opto OFF) | | R5 | 470 | 0805/TH | 1 | TX input series resistor | | D1 | 1N4007 | DO-41 | 1 | Reverse polarity protection, 12V bridge | | U3 | ESP32-C3 | Module | 1 | Any ESP32 with hardware UART | | J1 | OBD-II / roundel | — | 1 | Or direct wire to CD changer connector | **Optional for permanent install:** - AMS1117-3.3 or MP1584 buck: 12V -> 3.3V for ESP32 - 10uF + 100nF ceramic caps on 3.3V rail - TVS diode (SMBJ16A) on bus line - Status LED on spare GPIO (NOT in signal path) ## ESP32 Pin Assignments | Function | GPIO | UART | Notes | |----------|------|------|-------| | I/K-Bus TX | GPIO 17 | UART1 TX | Through R5 -> R3 -> Q1 -> U2 | | I/K-Bus RX | GPIO 16 | UART1 RX | From U1 emitter (pin 3) | | Debug TX | GPIO 1 | UART0 TX | USB serial monitor | | Debug RX | GPIO 3 | UART0 RX | USB serial monitor | | Status LED | GPIO 2 | — | Activity indicator | UART config: `Serial1.begin(9600, SERIAL_8E1, RX_PIN, TX_PIN);` ## BMW I-Bus / K-Bus Protocol ### Message Format ``` Byte 0: Source address (which module sent this) Byte 1: Length (count of remaining bytes: dest + data + checksum) Byte 2: Destination address (target module, 0xBF = broadcast) Byte 3: Command type Byte 4+: Data field(s) Last: Checksum (XOR of all preceding bytes) ``` **Example:** `50 04 68 32 11 1F` (steering wheel volume up) - `0x50` = MFL (Multi-Function steering wheel) - `0x04` = 4 bytes follow - `0x68` = RAD (radio) - `0x32` = volume control command - `0x11` = volume up - `0x1F` = XOR checksum (50 ^ 04 ^ 68 ^ 32 ^ 11 = 1F) ### Module Address Map | Addr | ID | Module | |------|-----|--------| | `0x00` | GM5 | Body control module | | `0x18` | CDC | CD Changer | | `0x3F` | DIA | Diagnostic computer | | `0x44` | EWS | Immobilizer | | `0x50` | MFL | Steering wheel controls | | `0x5B` | IHKA | Climate control | | `0x68` | RAD | Radio | | `0x6A` | DSP | Digital Sound Processor | | `0x80` | IKE | Instrument cluster | | `0xB0` | SES | Speed-dependent volume | | `0xBF` | ALL | Broadcast | | `0xC8` | TEL | Telephone | | `0xD0` | LCM | Light Control Module | | `0xE7` | ANZV | Display (phone status) | | `0xE8` | RLS | Rain/Light Sensor | | `0xFF` | LOC | Local (post-reset broadcast) | ### Bus Contention Protocol BMW I/K-Bus is multi-master. Before transmitting: 1. Monitor bus for activity (SEN/STA pin on TH3122, or RX line with optocoupler) 2. Wait for **1.5ms of bus silence** (no transitions) 3. Only then begin transmitting 4. Maintain **10ms minimum gap** between your own packets 5. If bus activity detected during your transmission, back off and retry The muki01 IbusSerial library uses AVR Timer2 in CTC mode (OCR2A=94, prescaler=256) to time the 1.5ms idle window. For ESP32, this needs porting to a hardware timer or `esp_timer`. ### Supported BMW Models | Chassis | Series | Years | I-Bus | K-Bus | |---------|--------|-------|-------|-------| | E31 | 8 Series | 1989-1999 | Yes | — | | E38 | 7 Series | 1999-2001 | Yes | Yes | | E39 | 5 Series | 1995-2004 | Yes | Yes | | E46 | 3 Series | 1997-2006 | — | Yes | | E52 | Z8 | 2000-2003 | — | Yes | | E53 | X5 | 1999-2006 | Yes | Yes | | E83 | X3 | 2003-2010 | — | Yes | | E85 | Z4 | 2002-2008 | — | Yes | | E87 | 1 Series | 2004-2013 | — | Yes | ### K-Bus Connection Points (E46) 1. **CD Changer Connector** (trunk, driver's side) — Connector X18180 - K-Bus wire: White/Red with Yellow dots - Ground: Brown - 12V: Red/Green 2. **K-Bus junction block** (above fuse box) - K-Bus wire: White/Red with Yellow dots - 12V and GND from a separate source ## Software Reference ### Primary: muki01/I-K_Bus Repository: https://github.com/muki01/I-K_Bus **Library files** (`I-K Bus Library/`): - `IbusSerial.h` / `IbusSerial.cpp` — State machine protocol handler (FIND_SOURCE -> FIND_LENGTH -> FIND_MESSAGE -> checksum validation), ring buffers for RX (128 bytes) and TX (64 bytes), bus contention timer, sleep management - `RingBuffer.h` / `RingBuffer.cpp` — Circular buffer implementation for async message handling **Example code** (`Codes/`): - `Basic_Code/Basic_Code.ino` — Minimal sniffer: prints all bus traffic via debug serial - `E46_KBus_Code/E46_KBus_Code.ino` — E46-specific: goodbye lights on lock, follow-me-home on double-lock, park lights on unlock - `E46_KBus_Code/E46_Codes.h` — Complete command table: lighting (22 commands), windows (8), door locks (6), trunk (3), wipers (2), interior lighting (4), instrument cluster (4), remote (3), ignition (6), MFL (7), CD control (7), DSP (12), telephone (4), and more ### Porting to ESP32 The IbusSerial library targets AVR (Arduino Nano) with: - `HardwareSerial` at 9600 8E1 for bus communication - `SoftwareSerial` on pins 7/8 for debug - AVR Timer2 CTC for 1.5ms bus idle detection - `digitalPinToInterrupt(3)` for SEN/STA pin change For ESP32 port, replace: - Timer2 -> `esp_timer_create()` or `hw_timer_t` - SoftwareSerial -> hardware UART0 (USB) for debug - `PROGMEM` -> not needed (ESP32 flash is memory-mapped) - Pin interrupts -> `attachInterrupt()` works directly on any GPIO - Bus idle detection via RX pin interrupt + timer (no SEN/STA without TH3122) ### Alternative Hardware: Dedicated Transceiver ICs The repo also documents three IC-based alternatives (schematics in `Schematics/`): - **TH3122.4 / ELMOS 10026B** — BMW's own bus transceiver, has SEN/STA pin for hardware contention detection, EN pin for sleep. The IbusSerial library was designed for this IC. - **MCP2025** — Microchip LIN bus transceiver, repurposed for I/K-Bus - **SN65HVDA195** — TI automotive K-line transceiver ## SPICE Simulation The `mcp-ltspice` MCP server is available for circuit simulation. To validate the optocoupler design: ``` # Key tools: mcp__mcp-ltspice__create_netlist(title, components, directives, output_path) mcp__mcp-ltspice__simulate_netlist(netlist_path) -> returns raw_file path mcp__mcp-ltspice__get_waveform(raw_file_path, signal_names, max_points) mcp__mcp-ltspice__analyze_waveform(raw_file_path, signal_name, analyses) ``` **Simulated and validated.** Uses LTspice's built-in PC817 subcircuit (from `lib/sub/PC817.sub`) with `Igain=1m` (PC817A, ~100% CTR). Two netlists cover both signal paths: - `reference/ibus_rx_path.cir` — RX path: bus byte 0x50 at 9600 baud → U1 PC817 → ESP32 RX - `reference/ibus_tx_path.cir` — TX path: ESP32 TX → Q1 BC547B → U2 PC817 → bus (R2=220Ω, with RX loopback) - `reference/tx_validated_r2_220.cir` — TX validation at worst-case bus loading (1kΩ pull-up) - `reference/tx_sweep_r2.cir` — R2 sweep (100–470Ω) at worst-case CTR - `reference/tx_sweep_rpull.cir` — Bus impedance sweep (510Ω–10kΩ) - `reference/tx_sweep_ctr.cir` — CTR grade sweep (PC817A through PC817D) **PC817 SPICE pin order:** `1=Anode, 2=Cathode, 3=Collector, 4=Emitter` — subcircuit uses a VCCS (G1) with `{Igain}` parameter to model optical coupling. ### Simulation Results **RX Path (Bus → ESP32, 3.3V VCC):** | Measurement | Value | Notes | |-------------|-------|-------| | V(RX) HIGH | 3.128V | > 2.475V ESP32 VIH threshold | | V(RX) LOW | ~0V | < 0.825V ESP32 VIL threshold | | LED current | 5.21mA | Through R1 (2k) when bus at 12V | | Rise time (10-90%) | 5.8μs | 5.6% of 104.17μs bit time | | Signal polarity | Preserved | Bus HIGH → RX HIGH (emitter-follower) | **TX Path (ESP32 → Bus, 3.3V VCC, R2=220Ω, 1kΩ bus pull-up):** | Measurement | Value | Notes | |-------------|-------|-------| | V(IBUS) LOW | 0.266V | U2 phototransistor pulls bus down | | V(IBUS) swing | 8.08V | 0.27V – 8.35V on simulated bus | | U2 LED current | 9.71mA | Through R2 (220Ω) when Q1 ON | | Q1 Vce(sat) | 0.073V | Fully saturated | | Bus rise time (10-90%) | 9.1μs | 8.7% of 104.17μs bit time | | Signal inversion | Confirmed | TX HIGH → bus LOW (software must invert) | ### R2=220Ω Fix for 3.3V ESP32 The original muki01 design uses R2=470Ω for 5V Arduino. At 3.3V VCC, the LED current drops to 4.66mA — insufficient to saturate the phototransistor against bus impedances below ~2kΩ. Parameter sweeps revealed: - **R2 sweep (worst-case CTR):** R2=330Ω is the threshold; below that, V(IBUS) stays under 0.25V. R2=390Ω and above leaves saturation. - **Bus impedance sweep (R2=470Ω):** Fails at R_PULL ≤ 510Ω (can't pull bus below 4.5V). - **CTR grade sweep (1kΩ bus):** PC817A (Igain=1m) with R2=470Ω gives V_LOW=2.51V — marginal. PC817B+ works fine. **Fix: R2=220Ω** doubles the LED current (4.66→9.71mA), doubling the phototransistor base drive. This supports bus pull-ups down to ~530Ω, covering any realistic BMW I/K-Bus loading. The LED current (9.71mA) is well within the PC817's 50mA max rating. Validated simulation: `reference/tx_validated_r2_220.cir`. ### Design Notes from Simulation 1. **Bus rise time depends on external impedance.** The 9.1μs rise time (with 1kΩ pull-up) is 8.7% of bit time. Real BMW bus has TH3122 transceivers providing low-impedance drive, so actual rise will be similar or faster. 2. **Bench testing needs a low-impedance 12V source.** U1's RX LED (through R1=2kΩ) loads the bus continuously. With only a passive pull-up, IBUS sags to ~4.3V (at 4.7kΩ) or ~8.4V (at 1kΩ). Use a 12V supply through 100-510Ω to simulate a real bus during development. 3. **PC817 grade options.** PC817A (cheapest, CTR 80-160%) works with R2=220Ω. Using PC817B+ provides extra margin with no circuit change. The sweep netlists (`reference/tx_sweep_*.cir`) document the full operating envelope. The Tucker project's validated transistor netlist is at `~/claude/tucker/k-line-board/reference/kline_esp32_validated.cir` for comparison. ## Docs Archive The repo includes an extensive documentation collection (`Docs/`): - **BMW BUS Information/** — 5 PDFs on BMW bus architecture - **BMW Communication Codes/** — 12 files with I-Bus message codes for E39/E46 - **HackTheIBus/** — 21 PDFs covering individual module protocols (IKE, LCM, MFL, EWS, DSP, etc.) ## Docs Site Starlight (Astro) documentation at **https://k-line.warehack.ing**. Source in `site/`. ### Local Development ```bash cd site npm run dev # Astro dev server on port 4329 ``` Local reverse proxy: `k-line.l.warehack.ing` (via local Caddy). ### Remote Deployment ```bash ssh -A warehack-ing@k-line.warehack.ing "cd ~/i-k-bus-board/site && git pull && make rebuild" ``` `make rebuild` stops the container, rebuilds the Docker image (Astro build → Caddy Alpine), and starts fresh. `make up` reuses the existing image (faster but won't pick up code changes). ### Site Structure - **Stack:** Astro + Starlight → static HTML → Caddy (via `caddy-docker-proxy` labels) - **Config:** `site/.env` has `COMPOSE_PROJECT_NAME=k-line-docs` and `DOMAIN=k-line.warehack.ing` - **Public assets:** `site/public/spice/` contains SPICE netlists (.cir), waveform SVGs, and schematic PNG — served at `/spice/` - **Custom CSS:** `site/src/styles/custom.css` — waveform dark mode filter, schematic image styling ### Makefile Targets | Target | Description | |--------|-------------| | `make up` | Start production (reuses existing image) | | `make rebuild` | Force rebuild and restart (picks up code changes) | | `make dev` | Start dev mode with hot reload | | `make down` | Stop containers | | `make logs` | Tail container logs | | `make clean` | Remove containers and images | ## Firmware PlatformIO project in `firmware/` — builds for ESP32, ESP32-C3, ESP32-S3, and OBD-II scanner. ``` firmware/ ├── platformio.ini # 4 environments (BMW sniffer x3, OBD-II scanner) ├── include/config.h # Pin defaults + protocol constants (IBUS_* and KLINE_*) ├── lib/KLine/ │ ├── library.json # PlatformIO lib metadata (v2026.02.13) │ ├── KLineTransport.h / .cpp # Hardware transport (UART, ISR, ring buffers, idle detect) │ ├── IbusHandler.h / .cpp # BMW I/K-Bus FSM (FIND_SOURCE -> checksum validation) │ ├── IbusEsp32.h / .cpp # Backward-compatible facade (wraps Transport + Handler) │ ├── KLineObd2.h / .cpp # OBD-II K-line handler (slow init, fast init, PID requests) │ ├── Obd2Pids.h # OBD-II PID constants + SAE J1979 decode helpers │ ├── RingBuffer.h / .cpp # Circular buffer with bounds-checked remove/peek │ └── E46Codes.h # ~100 BMW E46 command arrays in namespace ibus{} └── src/ ├── main.cpp # BMW I/K-Bus sniffer with module name lookup └── obd2_scanner.cpp # OBD-II scanner (RPM, speed, coolant, throttle, voltage) ``` ### Architecture ``` +-----------------+ | KLineTransport | <-- UART, GPIO ISR, ring buffers, idle detection +-----------------+ / \ +------------+ +------------+ | IbusHandler| | KLineObd2 | <-- Protocol-specific FSMs +------------+ +------------+ | +------------+ | IbusEsp32 | <-- Backward-compatible facade +------------+ ``` **KLineTransport** owns the hardware: UART init, TX/RX ring buffers, GPIO ISR for idle detection, TX inversion, configurable baud/framing/checksum. Protocol-agnostic. **IbusHandler** is the BMW I/K-Bus FSM — reads from transport's RX ring, validates XOR checksum, applies source filter, delivers packets via callback. **KLineObd2** is the OBD-II handler — 5-baud slow init (bit-bang), fast init (TiniPulse), request/response with checksum validation, echo verification, structural ISO 14230 frame parsing, TesterPresent keepalive, and session re-initialization. **IbusEsp32** is preserved as a thin facade so existing BMW sniffer code needs zero changes. ### Building ```bash cd firmware pio run # build default BMW sniffer (esp32dev) pio run -e esp32-c3 # build BMW sniffer for ESP32-C3 pio run -e esp32-s3 # build BMW sniffer for ESP32-S3 pio run -e obd2-scanner # build OBD-II scanner pio run -t upload # flash to connected board pio device monitor # serial monitor at 115200 ``` ### Key Porting Decisions (AVR -> ESP32) | AVR Feature | ESP32 Replacement | |---|---| | Timer2 CTC (1.5ms idle) | `esp_timer` periodic 250us + RX pin GPIO interrupt | | TH3122 SEN/STA pin | GPIO interrupt on UART RX pin (CHANGE) | | SoftwareSerial debug | Serial (UART0, USB) at 115200 | | `PROGMEM` / `pgm_read_byte()` | Plain `const` arrays (flash is memory-mapped) | | TH3122 EN pin sleep | Not ported (no transceiver to disable) | | Global volatile state | Class members with `volatile` where ISR-shared | | Hardcoded source filter | Configurable filter, default OFF (sniffer mode) | | TX signal (for TH3122) | `uart_set_line_inverse(UART_SIGNAL_TXD_INV)` for optocoupler | ### Defensive Design (Post-Review Fixes) **Transport layer (KLineTransport):** - **Atomic timestamps**: `_lastRxTransitionUs` is `uint32_t` (not `int64_t`) — atomic reads on 32-bit CPUs, prevents torn reads between GPIO ISR and timer callback - **TX loopback race**: idle timer reset BEFORE clearing `_isTransmitting`, with compiler memory barrier (`__asm__ volatile("" ::: "memory")`) - **Atomic TX writes**: `write()` checks free space before writing any bytes — entire message fits or entire message is dropped, no partial packets - **Ring buffer bounds**: `remove()` clamps to `available()`, `peek(n)` validates against `available()`, `malloc` failure sets `_size=0` - **FSM timeout**: `FIND_MESSAGE` state has 100ms timeout to prevent parser stall on partial messages (bus glitch, sender crash) - **TX corruption recovery**: corrupt length in TX ring flushes entire buffer; underrun mid-packet aborts TX cleanly **OBD-II handler (KLineObd2) — hardened after Apollo code review:** - **UART pin detach**: `pinMatrixOutDetach()` properly disconnects UART TX from GPIO before bit-bang init — `uart_set_pin(UART_PIN_NO_CHANGE)` is a no-op, not a disconnect - **Timeout underflow guard**: `remainingMs()` helper clamps unsigned subtraction to 0 instead of wrapping to ~65s hang - **Checksum validation**: `readResponse()` validates mod256 checksum on every received frame — corrupt data rejected, not silently accepted - **Echo verification**: `clearEcho()` compares each echo byte against expected data — bus contention detected immediately - **RX buffer flush**: `flushRx()` clears both UART hardware FIFO and ring buffer after init sequences — stale framing errors from bit-bang period discarded - **Structural frame parsing**: `parseFrameData()` decodes ISO 14230 format byte (address mode bits 7:6, data length bits 5:0) — service ID found at known offset, not by byte-scanning - **Negative response handling**: Service `0x7F` detected and NRC code logged, distinguished from timeout - **Session keepalive**: TesterPresent (service `0x3E`) sent automatically when idle exceeds P3max (4s), preventing ECU session timeout - **Session recovery**: Scanner tracks consecutive failures and re-initializes (slow init → fast init) after 5 total failures ## What's Been Done 1. Optocoupler schematic analyzed (RX path, TX path, isolation topology) 2. Protocol differences documented (vs OBD-II K-line for Tucker project) 3. Software reference identified (IbusSerial library, E46 command codes) 4. Module address map and message format documented 5. **SPICE simulation validated** — both RX and TX paths simulated with LTspice PC817 model at 9600 baud. RX path gives clean 0-3.13V logic at 3.3V VCC. TX path confirms signal inversion and 0.27V bus LOW on 1kΩ bus. Rise/fall times within 9600 baud budget. 6. **R2 optimized for 3.3V** — parameter sweeps across R2, bus impedance, and CTR grade identified R2=220Ω (down from 470Ω) as the fix for 3.3V ESP32. Doubles LED drive current to 9.71mA, supports bus pull-ups down to ~530Ω. 7. **IbusSerial ported to ESP32** — PlatformIO project with IbusEsp32 library. Bus sniffer sketch included. Builds for ESP32, ESP32-C3, ESP32-S3. Code reviewed for ISR safety, race conditions, buffer overflows. 8. **Multi-protocol refactor (K-Line)** — split monolithic IbusEsp32 into KLineTransport (hardware) + IbusHandler (BMW FSM). Library renamed from IbusEsp32 to KLine. IbusEsp32 preserved as backward-compatible facade. Zero changes to existing BMW sniffer sketch. 9. **OBD-II K-line support** — KLineObd2 handler with ISO 9141 5-baud slow init, ISO 14230 fast init (TiniPulse), request/response with half-duplex echo clearing. Obd2Pids.h with ~20 common PID decode helpers (SAE J1979). Scanner example sketch polls RPM, speed, coolant temp, throttle, voltage. 10. **OBD-II code review + hardening** — Apollo code review identified 4 critical and 7 important issues. All fixed: proper UART pin detach (`pinMatrixOutDetach`), timeout underflow guards, checksum validation, echo verification, RX buffer flush after init, structural ISO 14230 frame parsing, negative response handling, TesterPresent keepalive, and session re-initialization. 11. **Docs site with SPICE waveforms** — Starlight site deployed to `k-line.warehack.ing`. Circuit design page has inline schematic, tabbed waveform SVGs (RX/TX paths, 2000-point plots from mcltspice), and all 8 netlists as downloadable `.cir` files. Dark mode CSS filter for white-background SVGs. ## What's Next ### BMW I/K-Bus 1. **Breadboard** prototype with PC817 + BC547 + ESP32 2. **Loopback test** — wire TX→RX, verify `write()` → callback round-trip 3. **Bench test** — 12V supply + 1kΩ pull-up, inject bytes from USB-UART at 9600 8E1 4. **Test** on BMW E46 K-Bus (CD changer connector in trunk, key position 1) 5. **Build** command library for target features (goodbye lights, follow-me-home, etc.) ### OBD-II K-line 1. **Loopback test** — wire TX→RX, verify 5-baud init bit pattern with logic analyzer 2. **Bench test** — USB-UART adapter simulating ECU responses (sync byte + keywords) 3. **Vehicle test** — Tucker k-line-board hardware on Ford Fiesta, key-on, slow init, read RPM PID 4. **DTC support** — Mode 03 (read DTCs) and Mode 04 (clear DTCs) with human-readable decode ## Safety Notes - BMW I/K-Bus is a **live vehicle control network**. Malformed messages can trigger unintended behavior (lights, locks, wipers). - Always test with ignition OFF or key position 1 (accessories) first. - The optocoupler isolation protects the ESP32 from the car AND the car from the ESP32. - Sniff bus traffic (RX only) before attempting any TX operations. - The bus has no authentication — any properly formatted message will be accepted by target modules. Use responsibly. - Parasitic drain: the interface draws ~5.4mA continuously from the bus 12V through R1 (RX LED always on when bus is idle). U2 LED only draws current during TX (~9.7mA pulses). Disconnect or add a sleep circuit for long-term parking.