Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/arduino.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Arduino CLI Build

on:
push:
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Arduino CLI
uses: arduino/setup-arduino-cli@v1
- name: Add ESP32 core index and install latest esp32 core
run: |
# Add ESP32 core index and install the latest available esp32 core
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core install esp32:esp32
- name: Install local library into Arduino sketchbook libraries
run: |
mkdir -p "$HOME/Arduino/libraries/HaierProtocol"
cp -R "$GITHUB_WORKSPACE"/* "$HOME/Arduino/libraries/HaierProtocol/"
echo "Installed local library to $HOME/Arduino/libraries/HaierProtocol"
- name: List Arduino CLI libraries (debug)
run: |
arduino-cli lib list || true
- name: Compile Arduino example
working-directory: test/ci/arduino_example
run: |
arduino-cli compile --fqbn esp32:esp32:esp32 .
20 changes: 20 additions & 0 deletions .github/workflows/platformio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: PlatformIO Build

on:
push:
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install PlatformIO
run: pip install -U platformio
- name: Build PlatformIO example
working-directory: test/ci/platformio_example
run: pio run -e esp32dev --verbose
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ project(${LIB_NAME} VERSION ${LIB_VERSION}
DESCRIPTION ${LIB_DESCRIPTION}
HOMEPAGE_URL ${LIB_URL})

set(LIB_INCLUDE_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/include")
set(LIB_INCLUDE_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(LIB_SRC_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/src")

include_directories("${LIB_INCLUDE_FOLDER}")
Expand Down
40 changes: 40 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,49 @@ This library implements a Haier protocol transport level. It can help
with sending and receiving messages to appliances that support it and
process answers.

Compatibility
-------------

This library is compatible with both **PlatformIO** and **Arduino** toolchains.

- Existing include paths stay unchanged (for example ``"protocol/haier_protocol.h"``).
- Public headers are located in the ``src`` tree (the ``include`` tree is no longer used).
- For Arduino IDE/CLI, you can also include the main header:

Note on C++ standard library support
----------------------------------

This library uses C++ timing facilities (``std::chrono``). It requires a
toolchain and board core that provide the C++ standard library. Older or
constrained Arduino cores (for example some AVR/Uno toolchains) may not
provide ``<chrono>`` and will fail to compile. For these boards prefer
PlatformIO or newer Arduino cores that include full C++ support.

If you target constrained boards, see the Arduino usage documentation for
workarounds and recommended cores.

- For Arduino IDE/CLI, you can also include the main header:

.. code-block:: cpp

#include <HaierProtocol.h>

No migration is required for existing PlatformIO projects.

Documentation
-------------

- `Protocol reference <doc/protocol_reference.rst>`_
- `PlatformIO usage example <doc/usage_platformio.rst>`_
- `Arduino usage example <doc/usage_arduino.rst>`_
- `hOn simulator <doc/hon_simulator.rst>`_
- `SmartAir2 simulator <doc/smartair2_simulator.rst>`_

Protocol description
--------------------

Detailed reference: `HaierProtocol Reference <doc/protocol_reference.rst>`_

Haier protocol is a synchronous protocol. All data delivered with this
protocol is split into portions - frames. The protocol has two versions.
One is used for older HVAC units that work with the SmartAir2
Expand Down
61 changes: 61 additions & 0 deletions doc/hon_simulator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
hon_simulator
==============

Overview
--------

`hon_simulator` is a small simulator for devices that use the hOn-style
Haier protocol. It is intended to help developers test host software and
transport implementations without requiring physical appliances.

Location
--------

Sources live in the tools tree: `tools/hon_simulator`_

Build
-----

Prerequisites: CMake and a C++ compiler (Visual Studio on Windows, GCC/Clang on Linux/macOS).

Windows (recommended)

.. code-block:: powershell

mkdir build
cd build
cmake -G "Visual Studio 17 2022" ..
cmake --build . --config Release

Linux / macOS

.. code-block:: sh

mkdir build && cd build
cmake ..
make -j$(nproc)

The resulting executable will be in the build output directory (e.g. ``build/Release`` or ``build/``).

Usage
-----

Run the built executable from the build directory. The simulator accepts runtime
options (serial device/port, logging verbosity, etc.). Use the built-in help to
discover supported flags:

.. code-block:: sh

./hon_simulator --help

Example (Windows)

.. code-block:: powershell

.\Release\hon_simulator.exe

Notes
-----

The simulator is meant for manual testing and continuous integration scenarios.
Consult the executable's ``--help`` output for exact runtime options and flags.
194 changes: 194 additions & 0 deletions doc/protocol_reference.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
HaierProtocol Reference
=======================

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``.

Scope
-----

- 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``.

Layer model
-----------

The library works in three layers:

1. **Stream layer** (``ProtocolStream``)
2. **Transport/frame layer** (``HaierFrame`` + ``TransportLevelHandler``)
3. **Message/transaction layer** (``HaierMessage`` + ``ProtocolHandler``)

``ProtocolStream`` contract
---------------------------

To use the library, provide an implementation of:

- ``available()``: bytes available for reading
- ``read_array(uint8_t* data, size_t len)``: read up to ``len`` bytes
- ``write_array(const uint8_t* data, size_t len)``: write exactly ``len`` bytes

Frame format
------------

Constants (from ``src/transport/haier_frame.h``):

- ``SEPARATOR_BYTE`` = ``0xFF``
- ``SEPARATOR_POST_BYTE`` = ``0x55``
- ``FRAME_SEPARATORS_COUNT`` = ``2``
- ``FRAME_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):

.. list-table::
:header-rows: 1

* - 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 and maximum payload
--------------------------

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)

Byte-stuffing
-------------

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 and CRC
----------------

Checksum:

- 8-bit additive sum of logical (de-escaped) bytes from ``Length`` through end of ``Data``
- plus ``SEPARATOR_POST_BYTE`` contribution for every stuffed ``0xFF`` byte 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 ``0x40`` is set

Transport-level receive flow
----------------------------

``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 ms`` for partial frames.
- Pushes valid frames into ``incoming_queue_`` as ``TimestampedFrame``.

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 model (``HaierMessage``)
--------------------------------

Message payload layout:

- Optional 2-byte subcommand (big-endian)
- Followed by message data bytes

If subcommand is ``NO_SUBCOMMAND`` (``0x0000``), subcommand bytes are omitted.

Protocol transaction behavior (``ProtocolHandler``)
---------------------------------------------------

State machine:

- ``IDLE``
- ``WAITING_FOR_ANSWER``

Defaults:

- max retries constant: ``9`` (request API stores retries as ``min(user, 9) + 1`` attempts)
- default answer timeout: ``200 ms``
- default cooldown between sends: ``400 ms``

Loop algorithm (high-level):

1. Always call transport ``read_data()`` and ``process_data()``.
2. 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.
3. 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.

Frame type reference
--------------------

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.

Minimal integration pattern
---------------------------

1. Implement ``ProtocolStream`` for your UART/serial backend.
2. Construct ``ProtocolHandler`` with that stream.
3. Register message/answer/timeout handlers.
4. Call ``handler.loop()`` frequently from the main loop/task.
5. Use ``send_message(...)`` for request/response transactions.
Loading