Ryan Malloy 8ba53630c0 Add OBD-II K-line support (ISO 9141/14230) with scanner example
New protocol handler alongside BMW I/K-Bus:
- KLineObd2: 5-baud slow init, fast init (TiniPulse), request/response
  with half-duplex echo clearing, PID convenience wrapper
- Obd2Pids.h: ~20 common PIDs with SAE J1979 decode helpers
- obd2_scanner.cpp: polls RPM, speed, coolant, throttle, voltage

Build config changes:
- config.h: KLINE_* defaults (10400/8N1/MOD256/no idle detect)
- platformio.ini: build_src_filter separates sketches, new
  [env:obd2-scanner] environment with KLINE_TX_INVERT=0
2026-02-13 05:46:07 -07:00

69 lines
2.3 KiB
C++

#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 <Arduino.h>
#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.
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);
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);
// Discard echo bytes from half-duplex bus
void clearEcho(uint8_t count);
// Read a single byte with timeout
int readByteTimeout(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
};