#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 #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(); // Flush both UART FIFO and RX ring buffer (discard all pending data) void flushRx(); // 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; };