Webasto W-BUS protocol implementation for ESP32. Single-wire automotive K-line style communication at 2400 baud 8E1...
This document captures practical W-BUS protocol details for controlling Webasto heaters from ESP32 via the WBusSimple implementation.
In this repo those knobs exist in configuration:
WBUS_TX_PIN, WBUS_RX_PINWBUS_EN_PIN (optional)WBUS_SEND_BREAK (optional “break” pulse before first command)W‑BUS messages are framed like:
length - 1 bytesHeader is a packed byte: high nibble is source address, low nibble is destination address.
In this repo we generate it as:
header = ((src & 0x0F) << 4) | (dst & 0x0F)Typical addressing used here:
WBUS_ADDR_CONTROLLER (commonly 0xF)WBUS_ADDR_HEATER (commonly 0x4)So the common bytes you’ll see are:
0xF40x4FImportant: some public implementations accept additional header bytes (other address pairs). In this repo we now treat “valid header” as whatever matches your configured addresses.
The length byte counts (payload bytes + checksum byte) after the length field.
If you send a command byte plus N data bytes:
length = 1 (cmd) + N (data) + 1 (checksum)Checksum is a simple XOR:
csum = header XOR length XOR payload_bytes...Where payload_bytes... means everything except the checksum itself.
So, when verifying a received frame, compute XOR across:
and compare the result to the final byte.
Some heaters/interfaces expect an initial “break” before the first command.
In this repo WBUS_SEND_BREAK triggers a one‑time sequence roughly like:
Exact timing is hardware dependent. If you see unreliable first‑packet behavior, this is the first knob to experiment with.
Many interactions use a command byte and optionally a “sub‑index” (one data byte) to select a page of data.
| Command | Name | Data | Notes |
|---|---|---|---|
0x10 |
Stop | none | Stops heating/ventilation |
0x21 |
Parking Heater | 1 byte (minutes) | Start heating for N minutes |
0x22 |
Ventilation | 1 byte (minutes) | Start ventilation (fan only) for N minutes |
0x44 |
Keep-alive | 2-3 bytes | Maintains active session |
0x50 |
Status request | 1+ bytes (index or 0x30+IDs) | Query status pages |
We use:
0x50 with index 0x070xD0 0x07 <opState> ...opState is a large heater state machine. In the receiver firmware we map it coarsely into Off vs Running.
There are (at least) two commonly seen status mechanisms.
This is the richer mechanism and the primary one used in this repo.
0x50 with data 0x30 followed by a list of status IDs0xD0 0x30 ... and then a sequence like <id><value…> repeatedThe catch: the response does not always include explicit per‑field lengths, and field sizes can vary by heater/firmware.
Implementation in this repo:
WBusSimple::tryParseStatusTlv()Some setups (and at least one public Arduino implementation) poll a small set of fixed pages:
| Page | Contents | Response size |
|---|---|---|
0x03 |
State flags bitfield (heat_request, vent_request, combustion_fan, glowplug, fuel_pump, nozzle_heating) | 1 byte |
0x04 |
Actuator percentages (glowplug %, fuel pump Hz, combustion fan %) | 8 bytes |
0x05 |
Temperature, voltage, flame, heater power | ~8 bytes |
0x06 |
Counters (working hours, operating hours, start counter) | 8 bytes |
0x07 |
Operating state | 4 bytes |
0x0F |
Component values (glowplug, pump, fan - scaled) | 3 bytes |
In this repo we have readers for:
readStateFlags() → page 0x03readActuators() → page 0x04readCounters() → page 0x06readOperatingState() → page 0x07Per the esphome-webasto pattern, heaters may require periodic keep-alive messages to maintain an active heating/ventilation session.
The WBusSimple class now tracks:
activeCmd: The currently running command (0x21=heat, 0x22=vent, 0=none)activeUntilMs: When the session expireslastKeepAliveMs: When the last keep-alive was sentHelper methods:
needsKeepAlive(nowMs): Returns true every 10 seconds while a command is activeneedsRenewal(nowMs): Returns true when <30 seconds remain (should re-issue the start command)setActiveCommand(cmd, minutes) / clearActiveCommand(): Manage stateCommands now retry up to 3 times (configurable via kCommandRetries) with ACK verification, matching the esphome-webasto pattern.
In this repo we added a fallback: if the multi‑status TLV snapshot doesn’t arrive/parse during the poll window, the receiver tries these pages and logs the raw bytes to Serial.
When you’re bringing up real hardware:
2400 8E1.0x50 0x07 (operating state)0x50 0x05 and 0x50 0x0F (fallback pages)If you can capture raw byte streams from your heater, you can extend tryParseStatusTlv() safely by:
0x44 … patterns appear in public projects); in this repo there is an optional sendKeepAlive().0x50 0x30 multi‑status; keep the simple‑page fallback available.lib/common/wbus_simple.*src/receiver/main.cpp