This document describes the wire format and runtime behavior implemented in this library.
It is focused on transport and protocol handling logic used by HaierProtocol.
- This reference describes what is implemented in the current codebase.
- It does not try to redefine vendor-level business commands.
- Frame types are listed in
src/protocol/haier_frame_types.h.
The library works in three layers:
- Stream layer (
ProtocolStream) - Transport/frame layer (
HaierFrame+TransportLevelHandler) - Message/transaction layer (
HaierMessage+ProtocolHandler)
To use the library, provide an implementation of:
available(): bytes available for readingread_array(uint8_t* data, size_t len): read up tolenbyteswrite_array(const uint8_t* data, size_t len): write exactlylenbytes
Constants (from src/transport/haier_frame.h):
SEPARATOR_BYTE=0xFFSEPARATOR_POST_BYTE=0x55FRAME_SEPARATORS_COUNT=2FRAME_HEADER_SIZE=10(includes two leading separators)PURE_HEADER_SIZE=8(header bytes excluding two leading separators)MAX_FRAME_SIZE=0xF1(241)
Logical frame structure (before byte-stuffing):
| Field | Size (bytes) | Notes |
|---|---|---|
| Separator | 2 | FF FF |
| Length | 1 | PURE_HEADER_SIZE + data_size |
| Flags | 1 | Bit 0x40 means CRC present |
| Reserved | 5 | Zero in generated frames |
| Type | 1 | Frame type (see FrameType) |
| Data | N | 0..233 bytes (effective max with current limits) |
| Checksum | 1 | 8-bit additive checksum |
| CRC | 2 (optional) | CRC-16/ARC when flag 0x40 is set |
Length byte is validated against:
- minimum:
PURE_HEADER_SIZE - maximum:
MAX_FRAME_SIZE
So payload max is:
MAX_FRAME_SIZE - PURE_HEADER_SIZE = 0xF1 - 0x08 = 0xE9(233 bytes)
When serializing a frame, every payload/header/checksum/CRC byte equal to 0xFF is followed by 0x55.
This escaping is applied after the first two separator bytes.
On receive, the parser removes escaped 0x55 after 0xFF. If a byte following 0xFF is not 0x55 where escaping is expected, parsing fails with WRONG_POST_SEPARATOR_BYTE.
Checksum:
- 8-bit additive sum of logical (de-escaped) bytes from
Lengththrough end ofData - plus
SEPARATOR_POST_BYTEcontribution for every stuffed0xFFbyte in header/type/data - compared to transmitted checksum byte
CRC:
- CRC-16/ARC (polynomial reflected, lookup-table implementation)
- computed from logical bytes
Length..Data - transmitted as two bytes: high byte then low byte
- enabled only if flag bit
0x40is set
TransportLevelHandler behavior:
- Reads from stream into a circular buffer.
- Searches for frame start by detecting separator pattern.
- Parses header first, then data/checksum/(optional CRC).
- Uses frame timeout
300 msfor partial frames. - Pushes valid frames into
incoming_queue_asTimestampedFrame.
Error recovery behavior:
- Buffer overflow may drop bytes; partial frame is reset.
- Wrong escaping resets current frame and continues scanning.
- Invalid checksum/CRC drops the frame and continues scanning.
Message payload layout:
- Optional 2-byte subcommand (big-endian)
- Followed by message data bytes
If subcommand is NO_SUBCOMMAND (0x0000), subcommand bytes are omitted.
State machine:
IDLEWAITING_FOR_ANSWER
Defaults:
- max retries constant:
9(request API stores retries asmin(user, 9) + 1attempts) - default answer timeout:
200 ms - default cooldown between sends:
400 ms
Loop algorithm (high-level):
- Always call transport
read_data()andprocess_data(). - In
IDLE: - If multiple incoming frames exist, keep only the latest one. - Dispatch message handler by incoming frame type. - If queued outgoing message exists and timing allows, send it. - In
WAITING_FOR_ANSWER: - On timeout: retry or call timeout handler when retries are exhausted. - On incoming frame: dispatch answer handler and finish request.
Handler APIs:
set_message_handler(type, handler)set_answer_handler(type, handler)set_timeout_handler(type, handler)- plus default handlers for each group
Important runtime notes:
send_answer()is valid only while a message handler is running.send_answer(answer)(without CRC arg) mirrors CRC mode of the incoming frame.no_answer()can be called in a message handler to suppress warning when intentionally not replying.
See complete enum in:
src/protocol/haier_frame_types.h
It includes common types such as CONTROL, STATUS, CONFIRM, REPORT, and many query/response pairs.
- Implement
ProtocolStreamfor your UART/serial backend. - Construct
ProtocolHandlerwith that stream. - Register message/answer/timeout handlers.
- Call
handler.loop()frequently from the main loop/task. - Use
send_message(...)for request/response transactions.