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

587 lines
15 KiB
C++

#pragma once
// BMW E46 K-Bus command table
// Ported from muki01/I-K_Bus E46_Codes.h (MIT license)
// PROGMEM removed (ESP32 flash is memory-mapped), wrapped in namespace.
//
// Message format: [source, length, destination, command, data...]
// Checksums are NOT included — IbusEsp32::write() appends them automatically.
//
// Length byte = count of remaining bytes including the checksum that write() adds.
// So for a 7-element array, length byte (index 1) should be 0x05 (5 bytes after length).
#include <Arduino.h>
namespace ibus {
// --- Module addresses ---
constexpr uint8_t M_GM5 = 0x00; // Body control module
constexpr uint8_t M_CDC = 0x18; // CD Changer
constexpr uint8_t M_DIA = 0x3F; // Diagnostic computer
constexpr uint8_t M_EWS = 0x44; // Immobilizer
constexpr uint8_t M_MFL = 0x50; // Steering wheel controls
constexpr uint8_t M_IHKA = 0x5B; // Climate control panel
constexpr uint8_t M_RAD = 0x68; // Radio
constexpr uint8_t M_DSP = 0x6A; // Digital Sound Processor
constexpr uint8_t M_IKE = 0x80; // Instrument cluster
constexpr uint8_t M_SES = 0xB0; // Speed-dependent volume
constexpr uint8_t M_ALL = 0xBF; // Broadcast
constexpr uint8_t M_TEL = 0xC8; // Telephone
constexpr uint8_t M_LCM = 0xD0; // Light Control Module
constexpr uint8_t M_ANZV = 0xE7; // Display (phone status)
constexpr uint8_t M_RLS = 0xE8; // Rain/Light Sensor
// --- GM5 diagnostic I/O ---
constexpr uint8_t GM5_SET_IO = 0x0C;
constexpr uint8_t GM5_BTN_DOME_LIGHT = 0x01;
constexpr uint8_t GM5_BTN_CENTER_LOCK = 0x03;
constexpr uint8_t GM5_BTN_TRUNK_OPEN = 0x05;
constexpr uint8_t GM5_BTN_WINDOW_DRIVER_DOWN = 0x0A;
constexpr uint8_t GM5_BTN_WINDOW_DRIVER_UP = 0x0B;
constexpr uint8_t GM5_BTN_WINDOW_PASS_DOWN = 0x0C;
constexpr uint8_t GM5_BTN_WINDOW_PASS_UP = 0x0D;
constexpr uint8_t GM5_LED_ALARM_WARNING = 0x4E;
constexpr uint8_t GM5_INPUT_STATE_DIGITAL = 0x00;
constexpr uint8_t GM5_INPUT_STATE_ANALOG = 0x01;
// ===================================================================
// Lighting commands (DIA -> LCM or DIA -> ALL broadcast)
// ===================================================================
const uint8_t TurnOffLights[] = {
0x3F, 0x0F, 0xD0, 0x0C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0xE4, 0xFF, 0x00
};
const uint8_t ParkLights_And_Signals[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x48, 0x0A, 0x06
};
const uint8_t ParkLights_And_Signals_And_FogLights[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x48, 0x0B, 0x06
};
const uint8_t Low_Beams[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x02, 0x4E, 0x0A, 0x06
};
const uint8_t FollowMeHome[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x06
};
const uint8_t GoodbyeLights[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x62, 0x08, 0xA0, 0x06
};
// --- Individual light control (DIA -> ALL, 12 bytes + checksum) ---
const uint8_t Fog[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06
};
const uint8_t LeftTail[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x06
};
const uint8_t RearLight[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x06
};
const uint8_t Brake_Above[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06
};
const uint8_t Brake_Left[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x06
};
const uint8_t Brake_Right[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06
};
const uint8_t FrontRightLowBeam[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06
};
const uint8_t FrontLeftLowBeam[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06
};
const uint8_t LowBeamDelayed[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06
};
const uint8_t MainBeamLeft[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06
};
const uint8_t HighBeamRightLow[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x06
};
const uint8_t IgnitionLowBeam[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x06
};
const uint8_t LeftRearContin[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x06
};
const uint8_t RightRearContin[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x06
};
const uint8_t LeftFrontTurnContin[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x06
};
const uint8_t RightFrontTurnContin[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x06
};
const uint8_t LeftParking[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x06
};
const uint8_t RightParking[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x06
};
const uint8_t HazardLights[] = {
0x3F, 0x0B, 0xBF, 0x0C, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06
};
// ===================================================================
// Window control (DIA -> GM5)
// ===================================================================
const uint8_t Window_FrontDriver_Open[] = {
0x3F, 0x05, 0x00, 0x0C, 0x52, 0x01
};
const uint8_t Window_FrontDriver_Close[] = {
0x3F, 0x05, 0x00, 0x0C, 0x53, 0x01
};
const uint8_t Window_FrontPassenger_Open[] = {
0x3F, 0x05, 0x00, 0x0C, 0x54, 0x01
};
const uint8_t Window_FrontPassenger_Close[] = {
0x3F, 0x05, 0x00, 0x0C, 0x55, 0x01
};
const uint8_t Window_RearDriver_Open[] = {
0x3F, 0x05, 0x00, 0x0C, 0x41, 0x01
};
const uint8_t Window_RearDriver_Close[] = {
0x3F, 0x05, 0x00, 0x0C, 0x42, 0x01
};
const uint8_t Window_RearPassenger_Open[] = {
0x3F, 0x05, 0x00, 0x0C, 0x44, 0x01
};
const uint8_t Window_RearPassenger_Close[] = {
0x3F, 0x05, 0x00, 0x0C, 0x43, 0x01
};
// ===================================================================
// Interior lighting (DIA -> GM5)
// ===================================================================
const uint8_t Interior_Off[] = {
0x3F, 0x05, 0x00, 0x0C, 0x01, 0x01
};
const uint8_t Interior_Dim2[] = {
0x3F, 0x05, 0x00, 0x0C, 0x30, 0x01
};
const uint8_t Interior_On3s[] = {
0x3F, 0x05, 0x00, 0x0C, 0x60, 0x01
};
const uint8_t Interior_OffDim[] = {
0x3F, 0x05, 0x00, 0x0C, 0x68, 0x01
};
// ===================================================================
// Door locks (DIA -> GM5)
// ===================================================================
const uint8_t Doors_Unlock_Interior[] = {
0x3F, 0x05, 0x00, 0x0C, 0x03, 0x01
};
const uint8_t Doors_Lock_Key[] = {
0x3F, 0x05, 0x00, 0x0C, 0x34, 0x01
};
const uint8_t Doors_Fuel_Trunk[] = {
0x3F, 0x05, 0x00, 0x0C, 0x46, 0x01
};
const uint8_t DriverDoor_Lock[] = {
0x3F, 0x05, 0x00, 0x0C, 0x47, 0x01
};
const uint8_t AllExceptDriver_Lock[] = {
0x3F, 0x05, 0x00, 0x0C, 0x4F, 0x01
};
const uint8_t Doors_HardLock[] = {
0x3F, 0x05, 0x00, 0x0C, 0x97, 0x01
};
// ===================================================================
// Trunk (DIA -> GM5)
// ===================================================================
const uint8_t Trunk_Open[] = {
0x3F, 0x05, 0x00, 0x0C, 0x02, 0x01
};
const uint8_t Trunk_Open2[] = {
0x3F, 0x05, 0x00, 0x0C, 0x05, 0x01
};
const uint8_t Trunk_Open3[] = {
0x3F, 0x05, 0x00, 0x0C, 0x95, 0x01
};
// ===================================================================
// Wipers and washers (DIA -> GM5)
// ===================================================================
const uint8_t Wipers_Front[] = {
0x3F, 0x05, 0x00, 0x0C, 0x49, 0x01
};
const uint8_t Washer_Front[] = {
0x3F, 0x05, 0x00, 0x0C, 0x62, 0x01
};
// ===================================================================
// Hazard / special (DIA -> GM5)
// ===================================================================
const uint8_t Hazard_IKE_LCM[] = {
0x3F, 0x05, 0x00, 0x0C, 0x70, 0x01
};
const uint8_t Hazard_LCM_3s[] = {
0x3F, 0x05, 0x00, 0x0C, 0x75, 0x01
};
const uint8_t CLOWN_FLASH[] = {
0x3F, 0x05, 0x00, 0x0C, 0x4E, 0x01
};
// ===================================================================
// Instrument cluster / speed (DIA -> IKE)
// ===================================================================
const uint8_t AvgSpeedDelete[] = {
0x3B, 0x05, 0x80, 0x41, 0x10, 0x0A
};
const uint8_t RequestMileage[] = {
0xBF, 0x03, 0x80, 0x16
};
// Speed limit: last byte is speed value in km/h
const uint8_t SpeedLimitBeep[] = {
0x3B, 0x06, 0x80, 0x40, 0x09, 0x00, 0x00
};
const uint8_t SpeedLimitCurrent[] = {
0x3B, 0x05, 0x80, 0x41, 0x09, 0x20
};
const uint8_t SpeedLimitDisable[] = {
0x3B, 0x05, 0x80, 0x41, 0x09, 0x08
};
// ===================================================================
// Remote control events (GM5 -> ALL broadcast)
// ===================================================================
const uint8_t Remote_CloseButton[] = {
0x00, 0x04, 0xBF, 0x72, 0x16
};
const uint8_t Remote_OpenButton[] = {
0x00, 0x04, 0xBF, 0x72, 0x26
};
const uint8_t Remote_ReleaseButton[] = {
0x00, 0x04, 0xBF, 0x72, 0x06
};
// ===================================================================
// Ignition and key state events
// ===================================================================
const uint8_t KEY_IN[] = {
0x44, 0x05, 0xBF, 0x74, 0x04, 0x00
};
const uint8_t KEY_OUT[] = {
0x44, 0x05, 0xBF, 0x74, 0x00, 0xFF
};
const uint8_t IGNITION_OFF[] = {
0x80, 0x04, 0xBF, 0x11, 0x00
};
const uint8_t IGNITION_POS1[] = {
0x80, 0x04, 0xBF, 0x11, 0x01
};
const uint8_t IGNITION_POS2[] = {
0x80, 0x04, 0xBF, 0x11, 0x03
};
const uint8_t REMOTE_UNLOCK[] = {
0x00, 0x04, 0xBF, 0x72, 0x22
};
const uint8_t REMOTE_LOCK[] = {
0x00, 0x04, 0xBF, 0x72, 0x12
};
// ===================================================================
// Steering wheel controls (MFL -> RAD/TEL)
// ===================================================================
const uint8_t MFL_VOL_UP[] = {
0x50, 0x04, 0x68, 0x32, 0x11
};
const uint8_t MFL_VOL_DOWN[] = {
0x50, 0x04, 0x68, 0x32, 0x10
};
const uint8_t MFL_TEL_VOL_UP[] = {
0x50, 0x04, 0xC8, 0x32, 0x11
};
const uint8_t MFL_TEL_VOL_DOWN[] = {
0x50, 0x04, 0xC8, 0x32, 0x10
};
const uint8_t MFL_SES_PRESS[] = {
0x50, 0x04, 0xB0, 0x3B, 0x80
};
const uint8_t MFL_SEND_END_PRESS[] = {
0x50, 0x04, 0xC8, 0x3B, 0x80
};
const uint8_t MFL_RT_PRESS[] = {
0x50, 0x04, 0x68, 0x3B, 0x02
};
// ===================================================================
// CD changer control (RAD -> CDC)
// ===================================================================
const uint8_t CD_STOP[] = {
0x68, 0x05, 0x18, 0x38, 0x01, 0x00
};
const uint8_t CD_PLAY[] = {
0x68, 0x05, 0x18, 0x38, 0x03, 0x00
};
const uint8_t CD_PAUSE[] = {
0x68, 0x05, 0x18, 0x38, 0x02, 0x00
};
const uint8_t CD_STOP_STATUS[] = {
0x18, 0x0A, 0x68, 0x39, 0x00, 0x02, 0x00, 0x3F, 0x00, 0x07, 0x01
};
const uint8_t CD_PLAY_STATUS[] = {
0x18, 0x0A, 0x68, 0x39, 0x02, 0x09, 0x00, 0x3F, 0x00, 0x07, 0x01
};
const uint8_t BACK_ONE[] = {
0x68, 0x05, 0x18, 0x38, 0x08, 0x00
};
const uint8_t BACK_TWO[] = {
0x68, 0x05, 0x18, 0x38, 0x08, 0x01
};
const uint8_t LEFT[] = {
0x68, 0x05, 0x18, 0x38, 0x0A, 0x01
};
const uint8_t RIGHT[] = {
0x68, 0x05, 0x18, 0x38, 0x0A, 0x00
};
const uint8_t SELECT[] = {
0x68, 0x05, 0x18, 0x38, 0x07, 0x01
};
const uint8_t BUTTON_ONE[] = {
0x68, 0x05, 0x18, 0x38, 0x06, 0x01
};
const uint8_t BUTTON_TWO[] = {
0x68, 0x05, 0x18, 0x38, 0x06, 0x02
};
const uint8_t BUTTON_THREE[] = {
0x68, 0x05, 0x18, 0x38, 0x06, 0x03
};
const uint8_t BUTTON_FOUR[] = {
0x68, 0x05, 0x18, 0x38, 0x06, 0x04
};
const uint8_t BUTTON_FIVE[] = {
0x68, 0x05, 0x18, 0x38, 0x06, 0x05
};
const uint8_t BUTTON_SIX[] = {
0x68, 0x05, 0x18, 0x38, 0x06, 0x06
};
// ===================================================================
// CDC status (CDC -> RAD or CDC -> ALL)
// ===================================================================
const uint8_t CDC_STATUS_REPLY_RST[] = {
0x18, 0x04, 0xFF, 0x02, 0x01
};
const uint8_t CDC_STATUS_REQUEST[] = {
0x68, 0x03, 0x18, 0x01
};
const uint8_t CDC_STATUS_REPLY[] = {
0x18, 0x04, 0xFF, 0x02, 0x00
};
const uint8_t CD_STATUS[] = {
0x18, 0x0E, 0x68, 0x39, 0x00, 0x82, 0x00, 0x3F,
0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01
};
const uint8_t BUTTON_PRESSED[] = {
0x68, 0x04, 0xFF, 0x3B, 0x00
};
// ===================================================================
// DSP control (RAD -> DSP)
// ===================================================================
const uint8_t DSP_STATUS_REQUEST[] = {
0x68, 0x03, 0x6A, 0x01
};
const uint8_t DSP_STATUS_REPLY[] = {
0x6A, 0x04, 0xFF, 0x02, 0x00
};
const uint8_t DSP_STATUS_REPLY_RST[] = {
0x6A, 0x04, 0xFF, 0x02, 0x01
};
const uint8_t DSP_VOL_UP_1[] = {
0x68, 0x04, 0x6A, 0x32, 0x11
};
const uint8_t DSP_VOL_UP_2[] = {
0x68, 0x04, 0x6A, 0x32, 0x21
};
const uint8_t DSP_VOL_UP_3[] = {
0x68, 0x04, 0x6A, 0x32, 0x31
};
const uint8_t DSP_VOL_DOWN_1[] = {
0x68, 0x04, 0x6A, 0x32, 0x10
};
const uint8_t DSP_VOL_DOWN_2[] = {
0x68, 0x04, 0x6A, 0x32, 0x20
};
const uint8_t DSP_VOL_DOWN_3[] = {
0x68, 0x04, 0x6A, 0x32, 0x30
};
const uint8_t DSP_FUNC_0[] = {
0x68, 0x04, 0x6A, 0x36, 0x30
};
const uint8_t DSP_FUNC_1[] = {
0x68, 0x04, 0x6A, 0x36, 0xE1
};
const uint8_t DSP_SRCE_OFF[] = {
0x68, 0x04, 0x6A, 0x36, 0xAF
};
const uint8_t DSP_SRCE_CD[] = {
0x68, 0x04, 0x6A, 0x36, 0xA0
};
const uint8_t DSP_SRCE_TUNER[] = {
0x68, 0x04, 0x6A, 0x36, 0xA1
};
// ===================================================================
// Telephone (TEL -> ANZV display)
// ===================================================================
const uint8_t INCOMING_CALL[] = {
0xC8, 0x04, 0xE7, 0x2C, 0x05
};
const uint8_t PHONE_ON[] = {
0xC8, 0x04, 0xE7, 0x2C, 0x10
};
const uint8_t HANDSFREE_PHONE_ON[] = {
0xC8, 0x04, 0xE7, 0x2C, 0x11
};
const uint8_t ACTIVE_CALL[] = {
0xC8, 0x04, 0xE7, 0x2C, 0x33
};
// ===================================================================
// Radio navigation (RAD -> misc)
// ===================================================================
const uint8_t GO_TO_RADIO[] = {
0x68, 0x04, 0xFF, 0x3B, 0x00
};
const uint8_t REQUEST_TIME[] = {
0x68, 0x05, 0x80, 0x41, 0x01, 0x01
};
// ===================================================================
// Volume increment table (DSP volume steps, 0-63)
// ===================================================================
const uint8_t VOL_INCREMENT[] = {
0, 68, 70, 72, 74, 76, 78, 80,
82, 84, 86, 88, 90, 92, 94, 96,
98, 100, 102, 104, 106, 108, 110, 112,
114, 116, 118, 120, 122, 124, 126, 128,
130, 132, 134, 136, 138, 140, 142, 144,
146, 148, 150, 152, 154, 156, 158, 160,
162, 164, 166, 168, 170, 172, 174, 176,
178, 180, 182, 184, 186, 188, 190, 192
};
} // namespace ibus