#pragma once // OBD-II K-line protocol handler (ISO 9141 / ISO 14230) // Uses KLineTransport for UART, ring buffers, and bus management. // Handles 5-baud slow init, fast init, request/response framing, // and half-duplex echo clearing. #include #include "KLineTransport.h" #include "driver/uart.h" #ifndef KLINE_RESPONSE_TIMEOUT_MS #define KLINE_RESPONSE_TIMEOUT_MS 2000 #endif #ifndef KLINE_ECHO_TIMEOUT_MS #define KLINE_ECHO_TIMEOUT_MS 100 #endif class KLineObd2 { public: KLineObd2(KLineTransport& transport); // ISO 9141 5-baud slow init — bit-bangs target address at 5 baud, // then reads sync (0x55) and keyword bytes from ECU. // Returns true if ECU responds with valid sync + keywords. bool slowInit(uint8_t targetAddr = 0x33); // ISO 14230 fast init — 25ms LOW + 25ms HIGH TiniPulse, // then reads StartComm response. bool fastInit(); // Send an OBD-II request. Frames the message and discards echo bytes. // data[] should contain: [header, source, target, mode, pid, ...] // For simple PID requests, use requestPid() instead. void sendRequest(const uint8_t* data, uint8_t len); // Read response from ECU. Blocks until response received or timeout. // Returns number of bytes read, or 0 on timeout/checksum error. uint8_t readResponse(uint8_t* buf, uint8_t maxLen, uint16_t timeoutMs = KLINE_RESPONSE_TIMEOUT_MS); // Convenience: send a mode/PID request and read the response. // Returns response data length (excluding header/checksum), or 0 on failure. uint8_t requestPid(uint8_t mode, uint8_t pid, uint8_t* response, uint8_t maxLen); // Send TesterPresent (service 0x3E) to keep the diagnostic session alive. void sendTesterPresent(); // Reset initialized state (for re-init after session loss) void reset() { _initialized = false; } bool isInitialized() const { return _initialized; } // Protocol info from init handshake uint8_t keyword1() const { return _kw1; } uint8_t keyword2() const { return _kw2; } private: // Bit-bang a single byte at 5 baud (200ms per bit) on TX pin void sendByte5Baud(uint8_t data); // Detach UART TX from GPIO pin (so we can bit-bang) void detachUartTx(); // Re-attach UART TX to GPIO pin (after bit-bang init) void reattachUartTx(); // Discard echo bytes from half-duplex bus, verify they match expected. // Returns true if all echo bytes matched, false on bus contention. bool clearEcho(const uint8_t* expected, uint8_t count); // Read a single byte with timeout int readByteTimeout(uint16_t timeoutMs); // Parse ISO 14230 frame structure. Returns index of first data byte // (service ID position), or -1 on parse error. // Sets *dataLen to the number of data bytes. int8_t parseFrameData(const uint8_t* buf, uint8_t bufLen, uint8_t* dataLen); // Remaining timeout helper — clamps to 0 if already expired static uint16_t remainingMs(unsigned long startMs, uint16_t timeoutMs); KLineTransport& _transport; bool _initialized; uint8_t _kw1; uint8_t _kw2; uint8_t _testerAddr; // our address (0xF1 = external test equipment) uint8_t _ecuAddr; // ECU address from init response unsigned long _lastRequestMs; // for P3 keepalive timing static constexpr uint16_t P3_KEEPALIVE_MS = 4000; // send TesterPresent before P3max };