i-k-bus-board/firmware/lib/AutoWire/KLineTransport.h
Ryan Malloy 1464fcabe6 Refactor: extract KLineTransport and IbusHandler from IbusEsp32
Split the monolithic IbusEsp32 class into composable layers:
- KLineTransport: UART, GPIO ISR, ring buffers, idle detection
- IbusHandler: BMW I/K-Bus FSM, source filtering, packet callback
- IbusEsp32: thin facade preserving the original API

Library renamed from IbusEsp32 to AutoWire. Existing sniffer
sketch (main.cpp) requires zero changes. All 3 ESP32 environments
build cleanly (esp32dev, esp32-c3, esp32-s3).
2026-02-13 05:41:39 -07:00

122 lines
3.5 KiB
C++

#pragma once
// Hardware transport for single-wire automotive buses (K-line family)
// Extracted from IbusEsp32 — owns UART, GPIO ISR, ring buffers, idle detection.
// Protocol-agnostic: BMW I/K-Bus and OBD-II K-line use different configs.
#include <Arduino.h>
#include "esp_timer.h"
#include "RingBuffer.h"
// Map IBUS_DEBUG to KLINE_DEBUG for backward compatibility
#if defined(IBUS_DEBUG) && !defined(KLINE_DEBUG)
#define KLINE_DEBUG
#endif
// Accept either KLINE_ or IBUS_ prefixed buffer size defines
#ifndef KLINE_RX_BUFFER_SIZE
#ifdef IBUS_RX_BUFFER_SIZE
#define KLINE_RX_BUFFER_SIZE IBUS_RX_BUFFER_SIZE
#else
#define KLINE_RX_BUFFER_SIZE 256
#endif
#endif
#ifndef KLINE_TX_BUFFER_SIZE
#ifdef IBUS_TX_BUFFER_SIZE
#define KLINE_TX_BUFFER_SIZE IBUS_TX_BUFFER_SIZE
#else
#define KLINE_TX_BUFFER_SIZE 128
#endif
#endif
#ifndef KLINE_MAX_MSG
#define KLINE_MAX_MSG 40
#endif
enum ChecksumType : uint8_t {
CHECKSUM_XOR, // BMW I/K-Bus: XOR all bytes
CHECKSUM_MOD256, // OBD-II K-line: sum mod 256
};
struct KLineConfig {
uint32_t baud = 9600;
uint32_t framing = SERIAL_8E1;
uint16_t idleTimeoutUs = 1500; // 0 = no idle detection (master/slave mode)
uint16_t idleCheckUs = 250;
uint16_t packetGapMs = 10;
bool txInvert = true; // true for optocoupler, false for transistor
ChecksumType checksumType = CHECKSUM_XOR;
};
class KLineTransport {
public:
KLineTransport();
~KLineTransport();
void begin(HardwareSerial& serial, int8_t rxPin, int8_t txPin,
int8_t ledPin = -1, uint8_t uartNum = 1,
const KLineConfig& config = KLineConfig());
// Call from loop() — handles TX scheduling
void run();
// Queue a message for TX. Checksum is appended per config.checksumType.
void write(const uint8_t* message, uint8_t size);
// Send raw bytes without framing/checksum (for init sequences)
void sendRaw(const uint8_t* data, uint8_t len);
// Drain UART FIFO into RX ring buffer
void drainUart();
// RX ring buffer accessors (protocol handlers read from these)
int rxAvailable();
int rxPeek(int n = 0);
void rxRemove(int n);
int rxRead();
// Bus idle state
bool clearToSend() const { return _clearToSend; }
// TX state — protocol handlers need this to suppress loopback echo
bool isTransmitting() const { return _isTransmitting; }
// LED control for protocol handlers
void ledOn();
void ledOff();
// Access to serial port (for direct byte ops during init sequences)
HardwareSerial* serial() { return _serial; }
// Pin access (for bit-bang init sequences that detach UART)
int8_t txPin() const { return _txPin; }
uint8_t uartNum() const { return _uartNum; }
// Checksum utilities
static uint8_t checksumXor(const uint8_t* data, uint8_t length);
static uint8_t checksumMod256(const uint8_t* data, uint8_t length);
uint8_t calculateChecksum(const uint8_t* data, uint8_t length) const;
private:
void trySend();
void sendNextPacket();
void setupBusIdleDetection(int8_t rxPin);
static void IRAM_ATTR onRxPinChange(void* arg);
static void onIdleTimerCallback(void* arg);
HardwareSerial* _serial;
int8_t _ledPin;
int8_t _txPin;
uint8_t _uartNum;
KLineConfig _config;
RingBuffer _rxRing;
RingBuffer _txRing;
// Bus idle detection
esp_timer_handle_t _idleTimer;
volatile uint32_t _lastRxTransitionUs;
volatile bool _clearToSend;
volatile bool _isTransmitting;
unsigned long _lastTxMs;
};