diff --git a/.github/workflows/arduino.yml b/.github/workflows/arduino.yml new file mode 100644 index 0000000..a3a0484 --- /dev/null +++ b/.github/workflows/arduino.yml @@ -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 . diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml new file mode 100644 index 0000000..c45c173 --- /dev/null +++ b/.github/workflows/platformio.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index d9b4e61..a0b9106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}") diff --git a/README.rst b/README.rst index f41cfbd..b3e5e52 100644 --- a/README.rst +++ b/README.rst @@ -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 ```` 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 + +No migration is required for existing PlatformIO projects. + +Documentation +------------- + +- `Protocol reference `_ +- `PlatformIO usage example `_ +- `Arduino usage example `_ +- `hOn simulator `_ +- `SmartAir2 simulator `_ + Protocol description -------------------- +Detailed reference: `HaierProtocol Reference `_ + 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 diff --git a/doc/hon_simulator.rst b/doc/hon_simulator.rst new file mode 100644 index 0000000..eeb71ab --- /dev/null +++ b/doc/hon_simulator.rst @@ -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. diff --git a/doc/protocol_reference.rst b/doc/protocol_reference.rst new file mode 100644 index 0000000..ef45f59 --- /dev/null +++ b/doc/protocol_reference.rst @@ -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. diff --git a/doc/smartair2_simulator.rst b/doc/smartair2_simulator.rst new file mode 100644 index 0000000..6ccf6ef --- /dev/null +++ b/doc/smartair2_simulator.rst @@ -0,0 +1,58 @@ +smartair2_simulator +=================== + +Overview +-------- + +`smartair2_simulator` provides a simulated SmartAir2-style Haier appliance +for testing hosts and transport layers that communicate with older units. + +Location +-------- + +Sources live under: `tools/smartair2_simulator`_ + +Build +----- + +Prerequisites: CMake and a C++ compiler. + +Windows + +.. 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) + +Usage +----- + +Run the compiled binary from the build directory. The simulator accepts common +runtime options (serial port/device, verbosity, configuration). Run with +``--help`` to list supported options: + +.. code-block:: sh + + ./smartair2_simulator --help + +Example (Linux) + +.. code-block:: sh + + ./smartair2_simulator /dev/ttyUSB0 115200 + +Notes +----- + +The simulator is intended for development and automated tests. For exact +command-line parameters check the executable's help output. diff --git a/doc/usage_arduino.rst b/doc/usage_arduino.rst new file mode 100644 index 0000000..b55438c --- /dev/null +++ b/doc/usage_arduino.rst @@ -0,0 +1,110 @@ +Arduino Usage +============= + +This guide explains how to use ``HaierProtocol`` from Arduino IDE/CLI projects. + +Install the library +------------------- + +Option 1 (Library Manager): + +1. Open Arduino IDE. +2. Go to **Sketch -> Include Library -> Manage Libraries...** +3. Search for ``HaierProtocol`` and install. + +Option 2 (ZIP/local source): + +1. Download the repository as ZIP (or clone it). +2. In Arduino IDE select **Sketch -> Include Library -> Add .ZIP Library...** + +Minimal sketch +-------------- + +Create a sketch like this: + +.. code-block:: cpp + + #include + #include + + using namespace haier_protocol; + + class UartProtocolStream : public ProtocolStream { + public: + explicit UartProtocolStream(Stream& serial) : serial_(serial) {} + + size_t available() noexcept override { + return static_cast(serial_.available()); + } + + size_t read_array(uint8_t* data, size_t len) noexcept override { + size_t read_count = 0; + while (read_count < len && serial_.available() > 0) { + int value = serial_.read(); + if (value < 0) { + break; + } + data[read_count++] = static_cast(value); + } + return read_count; + } + + void write_array(const uint8_t* data, size_t len) noexcept override { + serial_.write(data, len); + } + + private: + Stream& serial_; + }; + + UartProtocolStream proto_stream(Serial1); + ProtocolHandler protocol(proto_stream); + + HandlerError on_control(FrameType type, const uint8_t* data, size_t size) { + (void)data; + (void)size; + + if (type == FrameType::CONTROL) { + HaierMessage confirm(FrameType::CONFIRM); + protocol.send_answer(confirm); + return HandlerError::HANDLER_OK; + } + protocol.no_answer(); + return HandlerError::UNSUPPORTED_MESSAGE; + } + + void setup() { + Serial.begin(115200); + Serial1.begin(9600); + + protocol.set_message_handler(FrameType::CONTROL, on_control); + } + + void loop() { + protocol.loop(); + } + +Optional request example +------------------------ + +To send a request expecting answer: + +.. code-block:: cpp + + uint8_t data[] = {0x10, 0x20, 0x30}; + HaierMessage request(FrameType::CONTROL, data, sizeof(data)); + protocol.send_message(request, true, 1, std::chrono::milliseconds(300)); + +If no response is expected: + +.. code-block:: cpp + + protocol.send_message_without_answer(request, true); + +Include paths +------------- + +Both include styles are supported: + +- ``#include `` +- ``#include "protocol/haier_protocol.h"`` diff --git a/doc/usage_platformio.rst b/doc/usage_platformio.rst new file mode 100644 index 0000000..7e009cb --- /dev/null +++ b/doc/usage_platformio.rst @@ -0,0 +1,121 @@ +PlatformIO Usage +================ + +This guide shows a practical setup for using ``HaierProtocol`` in PlatformIO. + +Install the library +------------------- + +Add dependency in ``platformio.ini``: + +.. code-block:: ini + + [env:esp32dev] + platform = espressif32 + board = esp32dev + framework = arduino + lib_deps = + paveldn/HaierProtocol + +If you use a local checkout instead of registry release: + +.. code-block:: ini + + [env:esp32dev] + platform = espressif32 + board = esp32dev + framework = arduino + lib_deps = + file://../HaierProtocol + +Minimal example +--------------- + +Create ``src/main.cpp``: + +.. code-block:: cpp + + #include + #include + + using namespace haier_protocol; + + class UartProtocolStream : public ProtocolStream { + public: + explicit UartProtocolStream(HardwareSerial& serial) : serial_(serial) {} + + size_t available() noexcept override { + return static_cast(serial_.available()); + } + + size_t read_array(uint8_t* data, size_t len) noexcept override { + size_t read_count = 0; + while (read_count < len && serial_.available() > 0) { + int value = serial_.read(); + if (value < 0) { + break; + } + data[read_count++] = static_cast(value); + } + return read_count; + } + + void write_array(const uint8_t* data, size_t len) noexcept override { + serial_.write(data, len); + serial_.flush(); + } + + private: + HardwareSerial& serial_; + }; + + UartProtocolStream proto_stream(Serial2); + ProtocolHandler protocol(proto_stream); + + HandlerError on_message(FrameType type, const uint8_t* data, size_t size) { + if (type == FrameType::CONTROL) { + HaierMessage answer(FrameType::CONFIRM); + protocol.send_answer(answer); + return HandlerError::HANDLER_OK; + } + protocol.no_answer(); + return HandlerError::UNSUPPORTED_MESSAGE; + } + + void setup() { + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, 16, 17); + + protocol.set_message_handler(FrameType::CONTROL, on_message); + protocol.set_answer_timeout(200); + protocol.set_cooldown_interval(400); + } + + void loop() { + protocol.loop(); + + // Example outgoing request every 5 seconds + static uint32_t last_send = 0; + uint32_t now = millis(); + if (now - last_send > 5000) { + uint8_t payload[] = {0x01, 0x02}; + HaierMessage msg(FrameType::CONTROL, payload, sizeof(payload)); + protocol.send_message(msg, true, 1, std::chrono::milliseconds(300)); + last_send = now; + } + } + +Include paths +------------- + +Both include styles are valid: + +- ``#include `` +- ``#include "protocol/haier_protocol.h"`` + +Notes +----- + +- Call ``protocol.loop()`` as frequently as possible. +- For constrained systems, keep handlers short and non-blocking. +- When you intentionally skip a reply in message handler, call ``protocol.no_answer()``. diff --git a/include/protocol/haier_frame_types.h b/include/protocol/haier_frame_types.h deleted file mode 100644 index ee73450..0000000 --- a/include/protocol/haier_frame_types.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef HAIER_FRAME_TYPES_H -#define HAIER_FRAME_TYPES_H - -#include - -namespace haier_protocol -{ - - -// In this section comments: -// - module is the ESP32 control module (communication module in Haier protocol document) -// - device is the conditioner control board (network appliances in Haier protocol document) -enum class FrameType : uint8_t { - UNKNOWN_FRAME_TYPE = 0x00, - CONTROL = 0x01, // Requests or sets one or multiple parameters (module <-> device, required) - STATUS = 0x02, // Contains one or multiple parameters values, usually answer to control frame (module <-> device, required) - INVALID = 0x03, // Communication error indication (module <-> device, required) - ALARM_STATUS = 0x04, // Alarm status report (module <-> device, interactive, required) - CONFIRM = 0x05, // Acknowledgment, usually used to confirm reception of frame if there is no special answer (module <-> device, required) - REPORT = 0x06, // Report frame (module <-> device, interactive, required) - STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required) - SYSTEM_DOWNLINK = 0x11, // System downlink frame (module -> device, optional) - DEVICE_UPLINK = 0x12, // Device uplink frame (module <- device , interactive, optional) - SYSTEM_QUERY = 0x13, // System query frame (module -> device, optional) - SYSTEM_QUERY_RESPONSE = 0x14, // System query response frame (module <- device , optional) - DEVICE_QUERY = 0x15, // Device query frame (module <- device, optional) - DEVICE_QUERY_RESPONSE = 0x16, // Device query response frame (module -> device, optional) - GROUP_COMMAND = 0x60, // Group command frame (module -> device, interactive, optional) - GET_DEVICE_VERSION = 0x61, // Requests device version (module -> device, required) - GET_DEVICE_VERSION_RESPONSE = 0x62, // Device version answer (module <- device, required_ - GET_ALL_ADDRESSES = 0x67, // Requests all devices addresses (module -> device, interactive, optional) - GET_ALL_ADDRESSES_RESPONSE = 0x68, // Answer to request of all devices addresses (module <- device , interactive, optional) - HANDSET_CHANGE_NOTIFICATION = 0x69, // Handset change notification frame (module <- device , interactive, optional) - GET_DEVICE_ID = 0x70, // Requests Device ID (module -> device, required) - GET_DEVICE_ID_RESPONSE = 0x71, // Response to device ID request (module <- device , required) - GET_ALARM_STATUS = 0x73, // Alarm status request (module -> device, required) - GET_ALARM_STATUS_RESPONSE = 0x74, // Response to alarm status request (module <- device, required) - GET_DEVICE_CONFIGURATION = 0x7C, // Requests device configuration (module -> device, interactive, required) - GET_DEVICE_CONFIGURATION_RESPONSE = 0x7D, // Response to device configuration request (module <- device, interactive, required) - DOWNLINK_TRANSPARENT_TRANSMISSION = 0x8C, // Downlink transparent transmission (proxy data Haier cloud -> device) (module -> device, interactive, optional) - UPLINK_TRANSPARENT_TRANSMISSION = 0x8D, // Uplink transparent transmission (proxy data device -> Haier cloud) (module <- device, interactive, optional) - START_DEVICE_UPGRADE = 0xE1, // Initiate device OTA upgrade (module -> device, OTA required) - START_DEVICE_UPGRADE_RESPONSE = 0xE2, // Response to initiate device upgrade command (module <- device, OTA required) - GET_FIRMWARE_CONTENT = 0xE5, // Requests to send firmware (module <- device, OTA required) - GET_FIRMWARE_CONTENT_RESPONSE = 0xE6, // Response to send firmware request (module -> device, OTA required) (multipacket?) - CHANGE_BAUD_RATE = 0xE7, // Requests to change port baud rate (module <- device, OTA required) - CHANGE_BAUD_RATE_RESPONSE = 0xE8, // Response to change port baud rate request (module -> device, OTA required) - GET_SUBBOARD_INFO = 0xE9, // Requests subboard information (module -> device, required) - GET_SUBBOARD_INFO_RESPONSE = 0xEA, // Response to subboard information request (module <- device, required) - GET_HARDWARE_INFO = 0xEB, // Requests information about device and subboard (module -> device, required) - GET_HARDWARE_INFO_RESPONSE = 0xEC, // Response to hardware information request (module <- device, required) - GET_UPGRADE_RESULT = 0xED, // Requests result of the firmware update (module <- device, OTA required) - GET_UPGRADE_RESULT_RESPONSE = 0xEF, // Response to firmware update results request (module -> device, OTA required) - GET_NETWORK_STATUS = 0xF0, // Requests network status (module <- device, interactive, optional) - GET_NETWORK_STATUS_RESPONSE = 0xF1, // Response to network status request (module -> device, interactive, optional) - START_WIFI_CONFIGURATION = 0xF2, // Starts WiFi configuration procedure (module <- device, interactive, required) - START_WIFI_CONFIGURATION_RESPONSE = 0xF3, // Response to start WiFi configuration request (module -> device, interactive, required) - STOP_WIFI_CONFIGURATION = 0xF4, // Stop WiFi configuration procedure (module <- device, interactive, required) - STOP_WIFI_CONFIGURATION_RESPONSE = 0xF5, // Response to stop WiFi configuration request (module -> device, interactive, required) - REPORT_NETWORK_STATUS = 0xF7, // Reports network status (module -> device, required) - CLEAR_CONFIGURATION = 0xF8, // Request to clear module configuration (module <- device, interactive, optional) - BIG_DATA_REPORT_CONFIGURATION = 0xFA, // Configuration for auto report device full status (module -> device, interactive, optional) - BIG_DATA_REPORT_CONFIGURATION_RESPONSE = 0xFB, // Response to set big data configuration (module <- device, interactive, optional) - GET_MANAGEMENT_INFORMATION = 0xFC, // Request management information from device (module -> device, required) - GET_MANAGEMENT_INFORMATION_RESPONSE = 0xFD, // Response to management information request (module <- device, required) - WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional) -}; - -} // HaierProtocol -#endif // HAIER_FRAME_TYPES_H diff --git a/include/protocol/haier_message.h b/include/protocol/haier_message.h deleted file mode 100644 index 3b9d8f5..0000000 --- a/include/protocol/haier_message.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef HAIER_MESSAGE_H -#define HAIER_MESSAGE_H - -#include -#include "haier_frame_types.h" - -namespace haier_protocol -{ - -constexpr uint16_t NO_SUBCOMMAND = 0x0000; - -class HaierMessage -{ -public: - HaierMessage() noexcept; - explicit HaierMessage(FrameType frame_type) noexcept; - HaierMessage(FrameType frame_type, uint16_t subcommand) noexcept; - HaierMessage(FrameType frame_type, const uint8_t* data, size_t data_size) noexcept; - HaierMessage(FrameType frame_type, uint16_t subcommand, const uint8_t* data, size_t data_size); - HaierMessage(const HaierMessage&); - HaierMessage(HaierMessage&&) noexcept; - virtual ~HaierMessage() noexcept; - HaierMessage& operator=(const HaierMessage&); - HaierMessage& operator=(HaierMessage&&) noexcept; - FrameType get_frame_type() const { return this->frame_type_; }; - uint16_t get_sub_command() const { return this->subcommand_; }; - size_t get_data_size() const { return this->data_size_; }; - const uint8_t* get_data() const { return this->data_; }; - size_t fill_buffer(uint8_t* const buffer, size_t limit) const; - size_t get_buffer_size() const { return this->data_size_ + ((this->subcommand_ == NO_SUBCOMMAND) ? 0 : 2); } -protected: - FrameType frame_type_; - uint16_t subcommand_; - size_t data_size_; - uint8_t* data_; -}; - -} // HaierProtocol -#endif // HAIER_MESSAGE_H \ No newline at end of file diff --git a/include/protocol/haier_protocol.h b/include/protocol/haier_protocol.h deleted file mode 100644 index 89a7ae1..0000000 --- a/include/protocol/haier_protocol.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef HAIER_PROTOCOL_H -#define HAIER_PROTOCOL_H - -#include -#include -#include -#include -#include -#include "transport/protocol_transport.h" -#include "protocol/haier_message.h" - -namespace haier_protocol -{ - -enum class HandlerError -{ - HANDLER_OK = 0, - UNSUPPORTED_MESSAGE, - UNEXPECTED_MESSAGE, - UNSUPPORTED_SUBCOMMAND, - WRONG_MESSAGE_STRUCTURE, - RUNTIME_ERROR, - UNKNOWN_ERROR, - INVALID_ANSWER, -}; - -// Message handler type. Expected that function sends answer back. -// argument 1: Incoming message type -// argument 2: Incoming data buffer (nullptr if none) -// argument 3: Incoming data buffer size -// return: Result of processing -using MessageHandler = std::function; - -// Answers handler type. -// argument 1: Request message type that caused this answer -// argument 2: Incoming message type -// argument 3: Incoming data buffer (nullptr if none) -// argument 4: Incoming data buffer size -// return: Result of processing -using AnswerHandler = std::function; - -// Timeout handler type. -// argument 1: Request message type that caused this answer -// return: Result of processing -using TimeoutHandler = std::function; - -HandlerError default_message_handler(FrameType message_type, const uint8_t* data, size_t data_size); -HandlerError default_answer_handler(FrameType message_type, FrameType request_type, const uint8_t* data, size_t data_size); -HandlerError default_timeout_handler(FrameType message_type); - -class ProtocolHandler -{ -public: - ProtocolHandler() = delete; - ProtocolHandler(const ProtocolHandler&) = delete; - ProtocolHandler& operator=(const ProtocolHandler&) = delete; - explicit ProtocolHandler(ProtocolStream&) noexcept; - ProtocolHandler(ProtocolStream&, size_t) noexcept; - size_t get_outgoing_queue_size() const noexcept {return this->outgoing_messages_.size(); }; - bool is_waiting_for_answer() const {return (this->state_ == ProtocolState::WAITING_FOR_ANSWER); }; - void set_answer_timeout(long long answer_timeout_miliseconds); - void set_answer_timeout(std::chrono::milliseconds answer_timeout); - void set_cooldown_interval(long long answer_timeout_miliseconds); - void set_cooldown_interval(std::chrono::milliseconds answer_timeout); - void send_message(const HaierMessage& message, bool use_crc, uint8_t num_retries = 0, std::chrono::milliseconds interval = std::chrono::milliseconds::zero()); - void send_message_without_answer(const HaierMessage& message, bool use_crc); - void send_answer(const HaierMessage& answer); - void send_answer(const HaierMessage& answer, bool use_crc); - // Use this function to suppress warning if you don't answer an appliance request on purpose - void no_answer(); - void set_message_handler(FrameType message_type, MessageHandler handler); - void remove_message_handler(FrameType message_type); - void set_default_message_handler(MessageHandler handler); - void set_answer_handler(FrameType message_type, AnswerHandler handler); - void remove_answer_handler(FrameType message_type); - void set_default_answer_handler(AnswerHandler handler); - void set_timeout_handler(FrameType message_type, TimeoutHandler handler); - void remove_timeout_handler(FrameType message_type); - void set_default_timeout_handler(TimeoutHandler handler); - virtual void loop(); -protected: - bool write_message_(const HaierMessage& message, bool use_crc); - enum class ProtocolState - { - IDLE, - WAITING_FOR_ANSWER, - }; - struct OutgoingQueueItem - { - const HaierMessage message; - bool use_crc; - bool no_answer; - int number_of_retries; - std::chrono::milliseconds retry_interval; - }; - using OutgoingQueue = std::queue; - TransportLevelHandler transport_; - std::map message_handlers_map_; - std::map answer_handlers_map_; - std::map timeout_handlers_map_; - OutgoingQueue outgoing_messages_; - MessageHandler default_message_handler_; - AnswerHandler default_answer_handler_; - TimeoutHandler default_timeout_handler_; - ProtocolState state_; - bool processing_message_; - bool incoming_message_crc_status_; - bool answer_sent_; - FrameType last_message_type_; - std::chrono::milliseconds answer_timeout_interval_; - std::chrono::milliseconds cooldown_interval_; - std::chrono::steady_clock::time_point cooldown_time_point_; - std::chrono::steady_clock::time_point answer_time_point_; - std::chrono::steady_clock::time_point retry_time_point_; -}; - - -} // HaierProtocol -#endif // HAIER_PROTOCOL_H \ No newline at end of file diff --git a/include/transport/haier_frame.h b/include/transport/haier_frame.h deleted file mode 100644 index df35816..0000000 --- a/include/transport/haier_frame.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef HAIER_FRAME_H -#define HAIER_FRAME_H - -#include - -namespace haier_protocol -{ - -constexpr uint8_t MAX_FRAME_SIZE = 0xF1; -constexpr uint8_t FRAME_HEADER_SIZE = 0x0A; -constexpr uint8_t SEPARATOR_BYTE = 0xFF; -constexpr uint8_t SEPARATOR_POST_BYTE = 0x55; -constexpr uint8_t FRAME_SEPARATORS_COUNT = 0x02; -constexpr uint8_t PURE_HEADER_SIZE = FRAME_HEADER_SIZE - FRAME_SEPARATORS_COUNT; - -enum class FrameStatus -{ - FRAME_COMPLETE, - FRAME_HEADER_ONLY, - FRAME_EMPTY -}; - -enum class FrameError -{ - COMPLETE_FRAME, - HEADER_ONLY, - FRAME_SEPARATOR_WRONG, - HEADER_TOO_SMALL, - FRAME_TOO_BIG, - FRAME_TOO_SMALL, - DATA_SIZE_WRONG, - CHECKSUM_WRONG, - CRC_WRONG, - WRONG_POST_SEPARATOR_BYTE, - UNKNOWN_DATA, -}; - -class HaierFrame -{ -public: - HaierFrame() noexcept; - HaierFrame(const HaierFrame&) = delete; - HaierFrame& operator=(const HaierFrame&) = delete; - HaierFrame& operator=(HaierFrame&&) noexcept; - HaierFrame(uint8_t frame_type, const uint8_t* const data, uint8_t data_size, bool use_crc = true); - HaierFrame(HaierFrame&&) noexcept; - virtual ~HaierFrame() noexcept; - FrameStatus get_status() const { return this->status_; }; - uint8_t get_frame_type() const { return this->frame_type_; }; - bool get_use_crc() const { return this->use_crc_; }; - uint8_t get_data_size() const { return this->data_size_; }; - uint8_t get_checksum() const { return this->checksum_; }; - uint16_t get_crc() const { return this->crc_; }; - size_t get_buffer_size() const; - const uint8_t* get_data() const { return this->data_; }; - size_t fill_buffer(uint8_t* const buffer, size_t limit) const; - size_t parse_buffer(const uint8_t* const buffer, size_t size, FrameError& err); - void reset(); -protected: - uint8_t frame_type_; - bool use_crc_; - uint8_t data_size_; - uint8_t checksum_; - uint16_t crc_; - uint8_t* data_; - FrameStatus status_; - uint8_t additional_bytes_; - uint8_t get_header_byte_(uint8_t pos) const; -}; - -} // HaierProtocol -#endif // HAIER_FRAME_H diff --git a/include/transport/protocol_transport.h b/include/transport/protocol_transport.h deleted file mode 100644 index 2140207..0000000 --- a/include/transport/protocol_transport.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef PROTOCOL_TRANSPORT_H -#define PROTOCOL_TRANSPORT_H -#include -#include -#include -#include "utils/haier_log.h" -#include "utils/circular_buffer.h" -#include "utils/protocol_stream.h" -#include "transport/haier_frame.h" - -namespace haier_protocol -{ - -struct TimestampedFrame -{ - HaierFrame frame; - std::chrono::steady_clock::time_point timestamp; -}; - -class TransportLevelHandler -{ -public: - TransportLevelHandler(const TransportLevelHandler&) = delete; - TransportLevelHandler& operator=(const TransportLevelHandler&) = delete; - explicit TransportLevelHandler(ProtocolStream& stream, size_t buffer_size) noexcept; - uint8_t send_data(uint8_t frameType, const uint8_t* data, size_t data_size, bool use_crc=true); - size_t read_data(); - void process_data(); - size_t get_buffer_size() noexcept { return this->buffer_.get_capacity(); }; - size_t available() const noexcept { return this->incoming_queue_.size(); }; - bool pop(TimestampedFrame& tframe); - void drop(size_t frames_count); - void reset_protocol() noexcept; - virtual ~TransportLevelHandler(); -protected: - void clear_(); - void drop_bytes_(size_t size); - ProtocolStream& stream_; - CircularBuffer buffer_; - size_t pos_; - size_t sep_count_; - bool frame_start_found_; - HaierFrame current_frame_; - std::chrono::steady_clock::time_point frame_start_; - std::queue incoming_queue_; -}; - -} // HaierProtocol -#endif // PROTOCOL_TRANSPORT_H \ No newline at end of file diff --git a/include/utils/circular_buffer.h b/include/utils/circular_buffer.h deleted file mode 100644 index af7dc13..0000000 --- a/include/utils/circular_buffer.h +++ /dev/null @@ -1,185 +0,0 @@ -#ifndef CIRCULAR_BUFFER_H -#define CIRCULAR_BUFFER_H - -#include -#include - -template -class CircularBuffer -{ -public: - constexpr static size_t CIRCULAR_BUFFER_MINIMUM_SIZE = 0x80; - CircularBuffer() = delete; - explicit CircularBuffer(size_t capacity); - CircularBuffer(const CircularBuffer& source); - ~CircularBuffer() noexcept; - CircularBuffer& operator=(const CircularBuffer& source); - const T& operator[] (size_t index) const; - size_t get_capacity() const { return capacity_; }; - size_t get_size() const; - size_t get_space() const { return this->capacity_ - this->get_size(); }; - size_t push(const T& item); - size_t push(const T* items, size_t size); - size_t pop(T* items, size_t size); - T* reserve(size_t& size); - void clear(); - size_t drop(size_t size); - bool empty() const { return is_empty_; }; -private: - bool is_empty_; - size_t capacity_; - T* buffer_; - size_t head_; - size_t tail_; -}; - -template -CircularBuffer::CircularBuffer(size_t capacity) : - is_empty_(true), - capacity_(capacity < CIRCULAR_BUFFER_MINIMUM_SIZE ? CIRCULAR_BUFFER_MINIMUM_SIZE : capacity), - buffer_(new T[capacity_]), - head_(0), - tail_(0) -{} - -template -CircularBuffer::CircularBuffer(const CircularBuffer& source) : - is_empty_(source.is_empty_), - capacity_(source.capacity_), - buffer_(new T[capacity_]), - head_(source.head_), - tail_(source.tail_) -{ - size_t size = source.get_size(); - for (size_t i = 0; i < size; i++) - { - size_t ind = (this->head_ + i) % this->capacity_; - this->buffer_[ind] = source.buffer_[ind]; - } -} - -template -CircularBuffer::~CircularBuffer() noexcept -{ - delete[] this->buffer_; -} - -template -CircularBuffer& CircularBuffer::operator=(const CircularBuffer& source) -{ - if (this != &source) - { - delete[] this->buffer_; - this->capacity_ = source.capacity_; - this->buffer_ = new T[this->capacity_]; - this->head_ = source.head_; - this->tail_ = source.tail_; - size_t size = source.get_size(); - for (size_t i = 0; i < size; i++) - { - size_t ind = (this->head_ + i) % this->capacity_; - this->buffer_[ind] = source.buffer_[ind]; - } - } - return *this; -} - -template -const T& CircularBuffer::operator[] (size_t index) const -{ - return this->buffer_[(this->head_ + index) % this->capacity_]; -} - -template -size_t CircularBuffer::get_size() const -{ - if (this->tail_ == this->head_) - return this->is_empty_ ? 0 : this->capacity_; - return this->head_ < this->tail_ ? this->tail_ - this->head_ : this->capacity_ - this->head_ + this->tail_; -} - -template -size_t CircularBuffer::push(const T& item) -{ - if (this->is_empty_ || (this->tail_ != this->head_)) - { - this->buffer_[this->tail_] = item; - this->tail_ = (this->tail_ + 1) % this->capacity_; - this->is_empty_ = false; - return 1; - } - return 0; -} - -template -size_t CircularBuffer::push(const T* items, size_t size) -{ - size_t size_to_push = this->get_space(); - if (size_to_push > size) - size_to_push = size; - for (size_t i = 0; i < size_to_push; i++) - { - this->buffer_[tail_] = items[i]; - this->tail_ = (this->tail_ + 1) % this->capacity_; - } - if (size_to_push > 0) - this->is_empty_ = false; - return size_to_push; -} - -template -size_t CircularBuffer::pop(T* items, size_t size) -{ - size_t sz = this->get_size(); - size_t pop_size = sz; - if (size < pop_size) - pop_size = size; - for (size_t i = 0; i < pop_size; i++) - { - items[i] = this->buffer_[this->head_]; - this->head_ = (this->head_ + 1) % this->capacity_; - } - if (pop_size == sz) - this->is_empty_ = true; - return pop_size; -} - -template -T* CircularBuffer::reserve(size_t& size) -{ - size_t new_size; - size_t head = this->head_ == 0 ? this->capacity_ : 0; - if (head > this->tail_) - new_size = std::min(size, head - this->tail_); - else - new_size = std::min(size, this->capacity_ - this->tail_); - T* result = this->buffer_ + this->tail_; - this->tail_ = (this->tail_ + new_size) % this->capacity_; - size = new_size; - if (new_size > 0) - this->is_empty_ = false; - return result; -} - -template -void CircularBuffer::clear() -{ - this->is_empty_ = true; - this->head_ = 0; - this->tail_ = 0; -} - -template -size_t CircularBuffer::drop(size_t size) -{ - size_t sz = this->get_size(); - size_t drop_size = sz; - if (size < drop_size) - drop_size = size; - this->head_ = (this->head_ + drop_size) % this->capacity_; - if (drop_size == sz) - this->is_empty_ = true;; - return drop_size; -} - -#endif // CIRCULAR_BUFFER_H \ No newline at end of file diff --git a/include/utils/haier_log.h b/include/utils/haier_log.h deleted file mode 100644 index a13416d..0000000 --- a/include/utils/haier_log.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef HAIER_LOG_H -#define HAIER_LOG_H - -#include -#include -#include - -extern const char hex_map[]; - -#ifndef HAIER_LOG_LEVEL - #define HAIER_LOG_LEVEL 0 -#endif - -#if (HAIER_LOG_LEVEL > 0) - #define HAIER_LOGE(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_ERROR, __VA_ARGS__) - #define HAIER_BUFE(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_ERROR, header, buffer, size) -#else - #define HAIER_LOGE(...) - #define HAIER_BUFE(header, buffer, size) -#endif -#if (HAIER_LOG_LEVEL > 1) - #define HAIER_LOGW(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_WARNING, __VA_ARGS__) - #define HAIER_BUFW(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_WARNING, header, buffer, size) -#else - #define HAIER_LOGW(...) - #define HAIER_BUFW(header, buffer, size) -#endif -#if (HAIER_LOG_LEVEL > 2) - #define HAIER_LOGI(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_INFO, __VA_ARGS__) - #define HAIER_BUFI(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_INFO, header, buffer, size) -#else - #define HAIER_LOGI(...) - #define HAIER_BUFI(header, buffer, size) -#endif -#if (HAIER_LOG_LEVEL > 3) - #define HAIER_LOGD(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_DEBUG, __VA_ARGS__) - #define HAIER_BUFD(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_DEBUG, header, buffer, size) -#else - #define HAIER_LOGD(...) - #define HAIER_BUFD(header, buffer, size) -#endif -#if (HAIER_LOG_LEVEL > 4) - #define HAIER_LOGV(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_VERBOSE, __VA_ARGS__) - #define HAIER_BUFV(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_VERBOSE, header, buffer, size) -#else - #define HAIER_LOGV(...) - #define HAIER_BUFV(header, buffer, size) -#endif - -std::string buf_to_hex(const uint8_t* message, size_t size); - -namespace haier_protocol -{ - -enum class HaierLogLevel -{ - LEVEL_NONE = 0, - LEVEL_ERROR = 1, - LEVEL_WARNING = 2, - LEVEL_INFO = 3, - LEVEL_DEBUG = 5, - LEVEL_VERBOSE = 6 -}; - // , , -using LogHandler = std::function; - -size_t log_haier(HaierLogLevel level, const char* format, ...); -size_t log_haier_buffer(HaierLogLevel level, const char* header, const uint8_t* buffer, size_t size); -size_t log_haier_buffers(HaierLogLevel level, const char* header, const uint8_t* buffer1, size_t size1, const uint8_t* buffer2, size_t size2); -void set_log_handler(LogHandler); -void reset_log_handler(); - -} -#endif // HAIER_LOG_H - diff --git a/include/utils/protocol_stream.h b/include/utils/protocol_stream.h deleted file mode 100644 index bd77a2d..0000000 --- a/include/utils/protocol_stream.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PROTOCOL_STREAM_H -#define PROTOCOL_STREAM_H - -namespace haier_protocol -{ - -class ProtocolStream -{ -public: - // Return number of bytes available in read buffer - virtual size_t available() noexcept = 0; - // Read min(len, available()) bytes to data, return the number of bytes read - virtual size_t read_array(uint8_t* data, size_t len) noexcept = 0; - // Write len bytes from data - virtual void write_array(const uint8_t* data, size_t len) noexcept = 0; -}; - -} -#endif // PROTOCOL_STREAM_H \ No newline at end of file diff --git a/library.json b/library.json index a240dda..b7f5b1b 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "HaierProtocol", - "version": "0.9.31", + "version": "0.9.32", "description": "A library to control Haier smart appliances using serial protocol", "keywords": "haier, hOn, air-conditioner, washing-machine, uart, serial", "repository": @@ -18,8 +18,8 @@ { "include": [ - "include/*.h", - "include/*/*.h", + "src/*.h", + "src/*/*.h", "src/*.cpp", "src/*/*.cpp" ] diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..d2c730c --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=HaierProtocol +version=0.9.32 +author=Pavlo Dudnytskyi +maintainer=Pavlo Dudnytskyi +sentence=A library to control Haier smart appliances using serial protocol. +paragraph=Implements Haier transport/protocol framing, message parsing, and request/answer handling for smart appliances. +category=Communication +url=https://github.com/paveldn/HaierProtocol +architectures=* +includes=HaierProtocol.h +license=MIT \ No newline at end of file diff --git a/src/HaierProtocol.h b/src/HaierProtocol.h new file mode 100644 index 0000000..c814f90 --- /dev/null +++ b/src/HaierProtocol.h @@ -0,0 +1,6 @@ +#ifndef HAIER_PROTOCOL_ARDUINO_MAIN_HEADER_H +#define HAIER_PROTOCOL_ARDUINO_MAIN_HEADER_H + +#include "protocol/haier_protocol.h" + +#endif // HAIER_PROTOCOL_ARDUINO_MAIN_HEADER_H \ No newline at end of file diff --git a/src/protocol/haier_frame_types.h b/src/protocol/haier_frame_types.h new file mode 100644 index 0000000..787e8ac --- /dev/null +++ b/src/protocol/haier_frame_types.h @@ -0,0 +1,70 @@ +#ifndef HAIER_FRAME_TYPES_H +#define HAIER_FRAME_TYPES_H + +#include + +namespace haier_protocol +{ + + +// In this section comments: +// - module is the ESP32 control module (communication module in Haier protocol document) +// - device is the conditioner control board (network appliances in Haier protocol document) +enum class FrameType : uint8_t { + UNKNOWN_FRAME_TYPE = 0x00, + CONTROL = 0x01, // Requests or sets one or multiple parameters (module <-> device, required) + STATUS = 0x02, // Contains one or multiple parameters values, usually answer to control frame (module <-> device, required) + INVALID = 0x03, // Communication error indication (module <-> device, required) + ALARM_STATUS = 0x04, // Alarm status report (module <-> device, interactive, required) + CONFIRM = 0x05, // Acknowledgment, usually used to confirm reception of frame if there is no special answer (module <-> device, required) + REPORT = 0x06, // Report frame (module <-> device, interactive, required) + STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required) + SYSTEM_DOWNLINK = 0x11, // System downlink frame (module -> device, optional) + DEVICE_UPLINK = 0x12, // Device uplink frame (module <- device , interactive, optional) + SYSTEM_QUERY = 0x13, // System query frame (module -> device, optional) + SYSTEM_QUERY_RESPONSE = 0x14, // System query response frame (module <- device , optional) + DEVICE_QUERY = 0x15, // Device query frame (module <- device, optional) + DEVICE_QUERY_RESPONSE = 0x16, // Device query response frame (module -> device, optional) + GROUP_COMMAND = 0x60, // Group command frame (module -> device, interactive, optional) + GET_DEVICE_VERSION = 0x61, // Requests device version (module -> device, required) + GET_DEVICE_VERSION_RESPONSE = 0x62, // Device version answer (module <- device, required_ + GET_ALL_ADDRESSES = 0x67, // Requests all devices addresses (module -> device, interactive, optional) + GET_ALL_ADDRESSES_RESPONSE = 0x68, // Answer to request of all devices addresses (module <- device , interactive, optional) + HANDSET_CHANGE_NOTIFICATION = 0x69, // Handset change notification frame (module <- device , interactive, optional) + GET_DEVICE_ID = 0x70, // Requests Device ID (module -> device, required) + GET_DEVICE_ID_RESPONSE = 0x71, // Response to device ID request (module <- device , required) + GET_ALARM_STATUS = 0x73, // Alarm status request (module -> device, required) + GET_ALARM_STATUS_RESPONSE = 0x74, // Response to alarm status request (module <- device, required) + GET_DEVICE_CONFIGURATION = 0x7C, // Requests device configuration (module -> device, interactive, required) + GET_DEVICE_CONFIGURATION_RESPONSE = 0x7D, // Response to device configuration request (module <- device, interactive, required) + DOWNLINK_TRANSPARENT_TRANSMISSION = 0x8C, // Downlink transparent transmission (proxy data Haier cloud -> device) (module -> device, interactive, optional) + UPLINK_TRANSPARENT_TRANSMISSION = 0x8D, // Uplink transparent transmission (proxy data device -> Haier cloud) (module <- device, interactive, optional) + START_DEVICE_UPGRADE = 0xE1, // Initiate device OTA upgrade (module -> device, OTA required) + START_DEVICE_UPGRADE_RESPONSE = 0xE2, // Response to initiate device upgrade command (module <- device, OTA required) + GET_FIRMWARE_CONTENT = 0xE5, // Requests to send firmware (module <- device, OTA required) + GET_FIRMWARE_CONTENT_RESPONSE = 0xE6, // Response to send firmware request (module -> device, OTA required) (multipacket?) + CHANGE_BAUD_RATE = 0xE7, // Requests to change port baud rate (module <- device, OTA required) + CHANGE_BAUD_RATE_RESPONSE = 0xE8, // Response to change port baud rate request (module -> device, OTA required) + GET_SUBBOARD_INFO = 0xE9, // Requests subboard information (module -> device, required) + GET_SUBBOARD_INFO_RESPONSE = 0xEA, // Response to subboard information request (module <- device, required) + GET_HARDWARE_INFO = 0xEB, // Requests information about device and subboard (module -> device, required) + GET_HARDWARE_INFO_RESPONSE = 0xEC, // Response to hardware information request (module <- device, required) + GET_UPGRADE_RESULT = 0xED, // Requests result of the firmware update (module <- device, OTA required) + GET_UPGRADE_RESULT_RESPONSE = 0xEF, // Response to firmware update results request (module -> device, OTA required) + GET_NETWORK_STATUS = 0xF0, // Requests network status (module <- device, interactive, optional) + GET_NETWORK_STATUS_RESPONSE = 0xF1, // Response to network status request (module -> device, interactive, optional) + START_WIFI_CONFIGURATION = 0xF2, // Starts WiFi configuration procedure (module <- device, interactive, required) + START_WIFI_CONFIGURATION_RESPONSE = 0xF3, // Response to start WiFi configuration request (module -> device, interactive, required) + STOP_WIFI_CONFIGURATION = 0xF4, // Stop WiFi configuration procedure (module <- device, interactive, required) + STOP_WIFI_CONFIGURATION_RESPONSE = 0xF5, // Response to stop WiFi configuration request (module -> device, interactive, required) + REPORT_NETWORK_STATUS = 0xF7, // Reports network status (module -> device, required) + CLEAR_CONFIGURATION = 0xF8, // Request to clear module configuration (module <- device, interactive, optional) + BIG_DATA_REPORT_CONFIGURATION = 0xFA, // Configuration for auto report device full status (module -> device, interactive, optional) + BIG_DATA_REPORT_CONFIGURATION_RESPONSE = 0xFB, // Response to set big data configuration (module <- device, interactive, optional) + GET_MANAGEMENT_INFORMATION = 0xFC, // Request management information from device (module -> device, required) + GET_MANAGEMENT_INFORMATION_RESPONSE = 0xFD, // Response to management information request (module <- device, required) + WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional) +}; + +} // HaierProtocol +#endif // HAIER_FRAME_TYPES_H \ No newline at end of file diff --git a/src/protocol/haier_message.h b/src/protocol/haier_message.h new file mode 100644 index 0000000..bc89c3c --- /dev/null +++ b/src/protocol/haier_message.h @@ -0,0 +1,40 @@ +#ifndef HAIER_MESSAGE_H +#define HAIER_MESSAGE_H + +#include +#include +#include "haier_frame_types.h" + +namespace haier_protocol +{ + +constexpr uint16_t NO_SUBCOMMAND = 0x0000; + +class HaierMessage +{ +public: + HaierMessage() noexcept; + explicit HaierMessage(FrameType frame_type) noexcept; + HaierMessage(FrameType frame_type, uint16_t subcommand) noexcept; + HaierMessage(FrameType frame_type, const uint8_t* data, size_t data_size) noexcept; + HaierMessage(FrameType frame_type, uint16_t subcommand, const uint8_t* data, size_t data_size); + HaierMessage(const HaierMessage&); + HaierMessage(HaierMessage&&) noexcept; + virtual ~HaierMessage() noexcept; + HaierMessage& operator=(const HaierMessage&); + HaierMessage& operator=(HaierMessage&&) noexcept; + FrameType get_frame_type() const { return this->frame_type_; }; + uint16_t get_sub_command() const { return this->subcommand_; }; + size_t get_data_size() const { return this->data_size_; }; + const uint8_t* get_data() const { return this->data_; }; + size_t fill_buffer(uint8_t* const buffer, size_t limit) const; + size_t get_buffer_size() const { return this->data_size_ + ((this->subcommand_ == NO_SUBCOMMAND) ? 0 : 2); } +protected: + FrameType frame_type_; + uint16_t subcommand_; + size_t data_size_; + uint8_t* data_; +}; + +} // HaierProtocol +#endif // HAIER_MESSAGE_H \ No newline at end of file diff --git a/src/protocol/haier_protocol.h b/src/protocol/haier_protocol.h new file mode 100644 index 0000000..7b4cda5 --- /dev/null +++ b/src/protocol/haier_protocol.h @@ -0,0 +1,120 @@ +#ifndef HAIER_PROTOCOL_H +#define HAIER_PROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include "transport/protocol_transport.h" +#include "protocol/haier_message.h" + +namespace haier_protocol +{ + +enum class HandlerError +{ + HANDLER_OK = 0, + UNSUPPORTED_MESSAGE, + UNEXPECTED_MESSAGE, + UNSUPPORTED_SUBCOMMAND, + WRONG_MESSAGE_STRUCTURE, + RUNTIME_ERROR, + UNKNOWN_ERROR, + INVALID_ANSWER, +}; + +// Message handler type. Expected that function sends answer back. +// argument 1: Incoming message type +// argument 2: Incoming data buffer (nullptr if none) +// argument 3: Incoming data buffer size +// return: Result of processing +using MessageHandler = std::function; + +// Answers handler type. +// argument 1: Request message type that caused this answer +// argument 2: Incoming message type +// argument 3: Incoming data buffer (nullptr if none) +// argument 4: Incoming data buffer size +// return: Result of processing +using AnswerHandler = std::function; + +// Timeout handler type. +// argument 1: Request message type that caused this answer +// return: Result of processing +using TimeoutHandler = std::function; + +HandlerError default_message_handler(FrameType message_type, const uint8_t* data, size_t data_size); +HandlerError default_answer_handler(FrameType message_type, FrameType request_type, const uint8_t* data, size_t data_size); +HandlerError default_timeout_handler(FrameType message_type); + +class ProtocolHandler +{ +public: + ProtocolHandler() = delete; + ProtocolHandler(const ProtocolHandler&) = delete; + ProtocolHandler& operator=(const ProtocolHandler&) = delete; + explicit ProtocolHandler(ProtocolStream&) noexcept; + ProtocolHandler(ProtocolStream&, size_t) noexcept; + size_t get_outgoing_queue_size() const noexcept {return this->outgoing_messages_.size(); }; + bool is_waiting_for_answer() const {return (this->state_ == ProtocolState::WAITING_FOR_ANSWER); }; + void set_answer_timeout(long long answer_timeout_miliseconds); + void set_answer_timeout(std::chrono::milliseconds answer_timeout); + void set_cooldown_interval(long long answer_timeout_miliseconds); + void set_cooldown_interval(std::chrono::milliseconds answer_timeout); + void send_message(const HaierMessage& message, bool use_crc, uint8_t num_retries = 0, std::chrono::milliseconds interval = std::chrono::milliseconds::zero()); + void send_message_without_answer(const HaierMessage& message, bool use_crc); + void send_answer(const HaierMessage& answer); + void send_answer(const HaierMessage& answer, bool use_crc); + // Use this function to suppress warning if you don't answer an appliance request on purpose + void no_answer(); + void set_message_handler(FrameType message_type, MessageHandler handler); + void remove_message_handler(FrameType message_type); + void set_default_message_handler(MessageHandler handler); + void set_answer_handler(FrameType message_type, AnswerHandler handler); + void remove_answer_handler(FrameType message_type); + void set_default_answer_handler(AnswerHandler handler); + void set_timeout_handler(FrameType message_type, TimeoutHandler handler); + void remove_timeout_handler(FrameType message_type); + void set_default_timeout_handler(TimeoutHandler handler); + virtual void loop(); +protected: + bool write_message_(const HaierMessage& message, bool use_crc); + enum class ProtocolState + { + IDLE, + WAITING_FOR_ANSWER, + }; + struct OutgoingQueueItem + { + const HaierMessage message; + bool use_crc; + bool no_answer; + int number_of_retries; + std::chrono::milliseconds retry_interval; + }; + using OutgoingQueue = std::queue; + TransportLevelHandler transport_; + std::map message_handlers_map_; + std::map answer_handlers_map_; + std::map timeout_handlers_map_; + OutgoingQueue outgoing_messages_; + MessageHandler default_message_handler_; + AnswerHandler default_answer_handler_; + TimeoutHandler default_timeout_handler_; + ProtocolState state_; + bool processing_message_; + bool incoming_message_crc_status_; + bool answer_sent_; + FrameType last_message_type_; + std::chrono::milliseconds answer_timeout_interval_; + std::chrono::milliseconds cooldown_interval_; + std::chrono::steady_clock::time_point cooldown_time_point_; + std::chrono::steady_clock::time_point answer_time_point_; + std::chrono::steady_clock::time_point retry_time_point_; +}; + + +} // HaierProtocol +#endif // HAIER_PROTOCOL_H \ No newline at end of file diff --git a/src/transport/haier_frame.h b/src/transport/haier_frame.h new file mode 100644 index 0000000..c653d59 --- /dev/null +++ b/src/transport/haier_frame.h @@ -0,0 +1,73 @@ +#ifndef HAIER_FRAME_H +#define HAIER_FRAME_H + +#include +#include + +namespace haier_protocol +{ + +constexpr uint8_t MAX_FRAME_SIZE = 0xF1; +constexpr uint8_t FRAME_HEADER_SIZE = 0x0A; +constexpr uint8_t SEPARATOR_BYTE = 0xFF; +constexpr uint8_t SEPARATOR_POST_BYTE = 0x55; +constexpr uint8_t FRAME_SEPARATORS_COUNT = 0x02; +constexpr uint8_t PURE_HEADER_SIZE = FRAME_HEADER_SIZE - FRAME_SEPARATORS_COUNT; + +enum class FrameStatus +{ + FRAME_COMPLETE, + FRAME_HEADER_ONLY, + FRAME_EMPTY +}; + +enum class FrameError +{ + COMPLETE_FRAME, + HEADER_ONLY, + FRAME_SEPARATOR_WRONG, + HEADER_TOO_SMALL, + FRAME_TOO_BIG, + FRAME_TOO_SMALL, + DATA_SIZE_WRONG, + CHECKSUM_WRONG, + CRC_WRONG, + WRONG_POST_SEPARATOR_BYTE, + UNKNOWN_DATA, +}; + +class HaierFrame +{ +public: + HaierFrame() noexcept; + HaierFrame(const HaierFrame&) = delete; + HaierFrame& operator=(const HaierFrame&) = delete; + HaierFrame& operator=(HaierFrame&&) noexcept; + HaierFrame(uint8_t frame_type, const uint8_t* const data, uint8_t data_size, bool use_crc = true); + HaierFrame(HaierFrame&&) noexcept; + virtual ~HaierFrame() noexcept; + FrameStatus get_status() const { return this->status_; }; + uint8_t get_frame_type() const { return this->frame_type_; }; + bool get_use_crc() const { return this->use_crc_; }; + uint8_t get_data_size() const { return this->data_size_; }; + uint8_t get_checksum() const { return this->checksum_; }; + uint16_t get_crc() const { return this->crc_; }; + size_t get_buffer_size() const; + const uint8_t* get_data() const { return this->data_; }; + size_t fill_buffer(uint8_t* const buffer, size_t limit) const; + size_t parse_buffer(const uint8_t* const buffer, size_t size, FrameError& err); + void reset(); +protected: + uint8_t frame_type_; + bool use_crc_; + uint8_t data_size_; + uint8_t checksum_; + uint16_t crc_; + uint8_t* data_; + FrameStatus status_; + uint8_t additional_bytes_; + uint8_t get_header_byte_(uint8_t pos) const; +}; + +} // HaierProtocol +#endif // HAIER_FRAME_H \ No newline at end of file diff --git a/src/transport/protocol_transport.h b/src/transport/protocol_transport.h new file mode 100644 index 0000000..0fbbfa3 --- /dev/null +++ b/src/transport/protocol_transport.h @@ -0,0 +1,50 @@ +#ifndef PROTOCOL_TRANSPORT_H +#define PROTOCOL_TRANSPORT_H +#include +#include +#include +#include +#include "utils/haier_log.h" +#include "utils/circular_buffer.h" +#include "utils/protocol_stream.h" +#include "transport/haier_frame.h" + +namespace haier_protocol +{ + +struct TimestampedFrame +{ + HaierFrame frame; + std::chrono::steady_clock::time_point timestamp; +}; + +class TransportLevelHandler +{ +public: + TransportLevelHandler(const TransportLevelHandler&) = delete; + TransportLevelHandler& operator=(const TransportLevelHandler&) = delete; + explicit TransportLevelHandler(ProtocolStream& stream, size_t buffer_size) noexcept; + uint8_t send_data(uint8_t frameType, const uint8_t* data, size_t data_size, bool use_crc=true); + size_t read_data(); + void process_data(); + size_t get_buffer_size() noexcept { return this->buffer_.get_capacity(); }; + size_t available() const noexcept { return this->incoming_queue_.size(); }; + bool pop(TimestampedFrame& tframe); + void drop(size_t frames_count); + void reset_protocol() noexcept; + virtual ~TransportLevelHandler(); +protected: + void clear_(); + void drop_bytes_(size_t size); + ProtocolStream& stream_; + CircularBuffer buffer_; + size_t pos_; + size_t sep_count_; + bool frame_start_found_; + HaierFrame current_frame_; + std::chrono::steady_clock::time_point frame_start_; + std::queue incoming_queue_; +}; + +} // HaierProtocol +#endif // PROTOCOL_TRANSPORT_H \ No newline at end of file diff --git a/src/utils/circular_buffer.h b/src/utils/circular_buffer.h new file mode 100644 index 0000000..dd52504 --- /dev/null +++ b/src/utils/circular_buffer.h @@ -0,0 +1,185 @@ +#ifndef CIRCULAR_BUFFER_H +#define CIRCULAR_BUFFER_H + +#include +#include + +template +class CircularBuffer +{ +public: + constexpr static size_t CIRCULAR_BUFFER_MINIMUM_SIZE = 0x80; + CircularBuffer() = delete; + explicit CircularBuffer(size_t capacity); + CircularBuffer(const CircularBuffer& source); + ~CircularBuffer() noexcept; + CircularBuffer& operator=(const CircularBuffer& source); + const T& operator[] (size_t index) const; + size_t get_capacity() const { return capacity_; }; + size_t get_size() const; + size_t get_space() const { return this->capacity_ - this->get_size(); }; + size_t push(const T& item); + size_t push(const T* items, size_t size); + size_t pop(T* items, size_t size); + T* reserve(size_t& size); + void clear(); + size_t drop(size_t size); + bool empty() const { return is_empty_; }; +private: + bool is_empty_; + size_t capacity_; + T* buffer_; + size_t head_; + size_t tail_; +}; + +template +CircularBuffer::CircularBuffer(size_t capacity) : + is_empty_(true), + capacity_(capacity < CIRCULAR_BUFFER_MINIMUM_SIZE ? CIRCULAR_BUFFER_MINIMUM_SIZE : capacity), + buffer_(new T[capacity_]), + head_(0), + tail_(0) +{} + +template +CircularBuffer::CircularBuffer(const CircularBuffer& source) : + is_empty_(source.is_empty_), + capacity_(source.capacity_), + buffer_(new T[capacity_]), + head_(source.head_), + tail_(source.tail_) +{ + size_t size = source.get_size(); + for (size_t i = 0; i < size; i++) + { + size_t ind = (this->head_ + i) % this->capacity_; + this->buffer_[ind] = source.buffer_[ind]; + } +} + +template +CircularBuffer::~CircularBuffer() noexcept +{ + delete[] this->buffer_; +} + +template +CircularBuffer& CircularBuffer::operator=(const CircularBuffer& source) +{ + if (this != &source) + { + delete[] this->buffer_; + this->capacity_ = source.capacity_; + this->buffer_ = new T[this->capacity_]; + this->head_ = source.head_; + this->tail_ = source.tail_; + size_t size = source.get_size(); + for (size_t i = 0; i < size; i++) + { + size_t ind = (this->head_ + i) % this->capacity_; + this->buffer_[ind] = source.buffer_[ind]; + } + } + return *this; +} + +template +const T& CircularBuffer::operator[] (size_t index) const +{ + return this->buffer_[(this->head_ + index) % this->capacity_]; +} + +template +size_t CircularBuffer::get_size() const +{ + if (this->tail_ == this->head_) + return this->is_empty_ ? 0 : this->capacity_; + return this->head_ < this->tail_ ? this->tail_ - this->head_ : this->capacity_ - this->head_ + this->tail_; +} + +template +size_t CircularBuffer::push(const T& item) +{ + if (this->is_empty_ || (this->tail_ != this->head_)) + { + this->buffer_[this->tail_] = item; + this->tail_ = (this->tail_ + 1) % this->capacity_; + this->is_empty_ = false; + return 1; + } + return 0; +} + +template +size_t CircularBuffer::push(const T* items, size_t size) +{ + size_t size_to_push = this->get_space(); + if (size_to_push > size) + size_to_push = size; + for (size_t i = 0; i < size_to_push; i++) + { + this->buffer_[tail_] = items[i]; + this->tail_ = (this->tail_ + 1) % this->capacity_; + } + if (size_to_push > 0) + this->is_empty_ = false; + return size_to_push; +} + +template +size_t CircularBuffer::pop(T* items, size_t size) +{ + size_t sz = this->get_size(); + size_t pop_size = sz; + if (size < pop_size) + pop_size = size; + for (size_t i = 0; i < pop_size; i++) + { + items[i] = this->buffer_[this->head_]; + this->head_ = (this->head_ + 1) % this->capacity_; + } + if (pop_size == sz) + this->is_empty_ = true; + return pop_size; +} + +template +T* CircularBuffer::reserve(size_t& size) +{ + size_t new_size; + size_t head = this->head_ == 0 ? this->capacity_ : 0; + if (head > this->tail_) + new_size = std::min(size, head - this->tail_); + else + new_size = std::min(size, this->capacity_ - this->tail_); + T* result = this->buffer_ + this->tail_; + this->tail_ = (this->tail_ + new_size) % this->capacity_; + size = new_size; + if (new_size > 0) + this->is_empty_ = false; + return result; +} + +template +void CircularBuffer::clear() +{ + this->is_empty_ = true; + this->head_ = 0; + this->tail_ = 0; +} + +template +size_t CircularBuffer::drop(size_t size) +{ + size_t sz = this->get_size(); + size_t drop_size = sz; + if (size < drop_size) + drop_size = size; + this->head_ = (this->head_ + drop_size) % this->capacity_; + if (drop_size == sz) + this->is_empty_ = true;; + return drop_size; +} + +#endif // CIRCULAR_BUFFER_H \ No newline at end of file diff --git a/src/utils/haier_log.h b/src/utils/haier_log.h new file mode 100644 index 0000000..55be418 --- /dev/null +++ b/src/utils/haier_log.h @@ -0,0 +1,75 @@ +#ifndef HAIER_LOG_H +#define HAIER_LOG_H + +#include +#include +#include +#include + +extern const char hex_map[]; + +#ifndef HAIER_LOG_LEVEL + #define HAIER_LOG_LEVEL 0 +#endif + +#if (HAIER_LOG_LEVEL > 0) + #define HAIER_LOGE(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_ERROR, __VA_ARGS__) + #define HAIER_BUFE(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_ERROR, header, buffer, size) +#else + #define HAIER_LOGE(...) + #define HAIER_BUFE(header, buffer, size) +#endif +#if (HAIER_LOG_LEVEL > 1) + #define HAIER_LOGW(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_WARNING, __VA_ARGS__) + #define HAIER_BUFW(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_WARNING, header, buffer, size) +#else + #define HAIER_LOGW(...) + #define HAIER_BUFW(header, buffer, size) +#endif +#if (HAIER_LOG_LEVEL > 2) + #define HAIER_LOGI(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_INFO, __VA_ARGS__) + #define HAIER_BUFI(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_INFO, header, buffer, size) +#else + #define HAIER_LOGI(...) + #define HAIER_BUFI(header, buffer, size) +#endif +#if (HAIER_LOG_LEVEL > 3) + #define HAIER_LOGD(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_DEBUG, __VA_ARGS__) + #define HAIER_BUFD(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_DEBUG, header, buffer, size) +#else + #define HAIER_LOGD(...) + #define HAIER_BUFD(header, buffer, size) +#endif +#if (HAIER_LOG_LEVEL > 4) + #define HAIER_LOGV(...) log_haier(haier_protocol::HaierLogLevel::LEVEL_VERBOSE, __VA_ARGS__) + #define HAIER_BUFV(header, buffer, size) log_haier_buffer(haier_protocol::HaierLogLevel::LEVEL_VERBOSE, header, buffer, size) +#else + #define HAIER_LOGV(...) + #define HAIER_BUFV(header, buffer, size) +#endif + +std::string buf_to_hex(const uint8_t* message, size_t size); + +namespace haier_protocol +{ + +enum class HaierLogLevel +{ + LEVEL_NONE = 0, + LEVEL_ERROR = 1, + LEVEL_WARNING = 2, + LEVEL_INFO = 3, + LEVEL_DEBUG = 5, + LEVEL_VERBOSE = 6 +}; + // , , +using LogHandler = std::function; + +size_t log_haier(HaierLogLevel level, const char* format, ...); +size_t log_haier_buffer(HaierLogLevel level, const char* header, const uint8_t* buffer, size_t size); +size_t log_haier_buffers(HaierLogLevel level, const char* header, const uint8_t* buffer1, size_t size1, const uint8_t* buffer2, size_t size2); +void set_log_handler(LogHandler); +void reset_log_handler(); + +} +#endif // HAIER_LOG_H \ No newline at end of file diff --git a/src/utils/protocol_stream.h b/src/utils/protocol_stream.h new file mode 100644 index 0000000..3df7aa6 --- /dev/null +++ b/src/utils/protocol_stream.h @@ -0,0 +1,22 @@ +#ifndef PROTOCOL_STREAM_H +#define PROTOCOL_STREAM_H + +#include +#include + +namespace haier_protocol +{ + +class ProtocolStream +{ +public: + // Return number of bytes available in read buffer + virtual size_t available() noexcept = 0; + // Read min(len, available()) bytes to data, return the number of bytes read + virtual size_t read_array(uint8_t* data, size_t len) noexcept = 0; + // Write len bytes from data + virtual void write_array(const uint8_t* data, size_t len) noexcept = 0; +}; + +} +#endif // PROTOCOL_STREAM_H \ No newline at end of file diff --git a/test/ci/arduino_example/arduino_example.ino b/test/ci/arduino_example/arduino_example.ino new file mode 100644 index 0000000..ef6484d --- /dev/null +++ b/test/ci/arduino_example/arduino_example.ino @@ -0,0 +1,59 @@ +#include +#include + +using namespace haier_protocol; + +class UartProtocolStream : public ProtocolStream { +public: + explicit UartProtocolStream(Stream& serial) : serial_(serial) {} + + size_t available() noexcept override { + return static_cast(serial_.available()); + } + + size_t read_array(uint8_t* data, size_t len) noexcept override { + size_t read_count = 0; + while (read_count < len && serial_.available() > 0) { + int value = serial_.read(); + if (value < 0) { + break; + } + data[read_count++] = static_cast(value); + } + return read_count; + } + + void write_array(const uint8_t* data, size_t len) noexcept override { + serial_.write(data, len); + } + +private: + Stream& serial_; +}; + +UartProtocolStream proto_stream(Serial1); +ProtocolHandler protocol(proto_stream); + +HandlerError on_control(FrameType type, const uint8_t* data, size_t size) { + (void)data; + (void)size; + + if (type == FrameType::CONTROL) { + HaierMessage confirm(FrameType::CONFIRM); + protocol.send_answer(confirm); + return HandlerError::HANDLER_OK; + } + protocol.no_answer(); + return HandlerError::UNSUPPORTED_MESSAGE; +} + +void setup() { + Serial.begin(115200); + Serial1.begin(9600); + + protocol.set_message_handler(FrameType::CONTROL, on_control); +} + +void loop() { + protocol.loop(); +} diff --git a/test/ci/platformio_example/platformio.ini b/test/ci/platformio_example/platformio.ini new file mode 100644 index 0000000..df6751f --- /dev/null +++ b/test/ci/platformio_example/platformio.ini @@ -0,0 +1,5 @@ +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +lib_extra_dirs = ../../../ diff --git a/test/ci/platformio_example/src/main.cpp b/test/ci/platformio_example/src/main.cpp new file mode 100644 index 0000000..39a8e2c --- /dev/null +++ b/test/ci/platformio_example/src/main.cpp @@ -0,0 +1,69 @@ +#include +#include + +using namespace haier_protocol; + +class UartProtocolStream : public ProtocolStream { +public: + explicit UartProtocolStream(HardwareSerial& serial) : serial_(serial) {} + + size_t available() noexcept override { + return static_cast(serial_.available()); + } + + size_t read_array(uint8_t* data, size_t len) noexcept override { + size_t read_count = 0; + while (read_count < len && serial_.available() > 0) { + int value = serial_.read(); + if (value < 0) { + break; + } + data[read_count++] = static_cast(value); + } + return read_count; + } + + void write_array(const uint8_t* data, size_t len) noexcept override { + serial_.write(data, len); + serial_.flush(); + } + +private: + HardwareSerial& serial_; +}; + +UartProtocolStream proto_stream(Serial2); +ProtocolHandler protocol(proto_stream); + +HandlerError on_message(FrameType type, const uint8_t* data, size_t size) { + if (type == FrameType::CONTROL) { + HaierMessage answer(FrameType::CONFIRM); + protocol.send_answer(answer); + return HandlerError::HANDLER_OK; + } + protocol.no_answer(); + return HandlerError::UNSUPPORTED_MESSAGE; +} + +void setup() { + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, 16, 17); + + protocol.set_message_handler(FrameType::CONTROL, on_message); + protocol.set_answer_timeout(200); + protocol.set_cooldown_interval(400); +} + +void loop() { + protocol.loop(); + + // Example outgoing request every 5 seconds + static uint32_t last_send = 0; + uint32_t now = millis(); + if (now - last_send > 5000) { + uint8_t payload[] = {0x01, 0x02}; + HaierMessage msg(FrameType::CONTROL, payload, sizeof(payload)); + protocol.send_message(msg, true, 1, std::chrono::milliseconds(300)); + last_send = now; + } +} diff --git a/test/hon_test/CMakeLists.txt b/test/hon_test/CMakeLists.txt index cf762b0..13346a0 100644 --- a/test/hon_test/CMakeLists.txt +++ b/test/hon_test/CMakeLists.txt @@ -10,7 +10,7 @@ set(TOOLS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../tools") add_compile_options(-DHAIER_LOG_LEVEL=5) add_compile_options(-DRUN_ALL_TESTS) -include_directories("${LIB_ROOT}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${TOOLS_PATH}/utils") +include_directories("${LIB_ROOT}/src" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${TOOLS_PATH}/utils") list(APPEND SOURCE_FILES "${TOOLS_PATH}/utils/console_log.cpp") list(APPEND SOURCE_FILES "${TOOLS_PATH}/utils/hon_server.cpp") diff --git a/test/simple_transport_test/CMakeLists.txt b/test/simple_transport_test/CMakeLists.txt index d552606..b2e5d6f 100644 --- a/test/simple_transport_test/CMakeLists.txt +++ b/test/simple_transport_test/CMakeLists.txt @@ -11,7 +11,7 @@ add_compile_options(-DHAIER_LOG_LEVEL=5) add_compile_options(-DHAIER_LOG_TAG="haier") add_compile_options(-DRUN_ALL_TESTS) -include_directories("${LIB_ROOT}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${TOOLS_PATH}/utils") +include_directories("${LIB_ROOT}/src" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${TOOLS_PATH}/utils") list(APPEND SOURCE_FILES "${TOOLS_PATH}/utils/console_log.cpp") list(APPEND SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/test.cpp") diff --git a/test/smartair2_test/CMakeLists.txt b/test/smartair2_test/CMakeLists.txt index c520a1b..4e035ed 100644 --- a/test/smartair2_test/CMakeLists.txt +++ b/test/smartair2_test/CMakeLists.txt @@ -10,7 +10,7 @@ set(TOOLS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../tools") add_compile_options(-DHAIER_LOG_LEVEL=5) add_compile_options(-DRUN_ALL_TESTS) -include_directories("${LIB_ROOT}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${TOOLS_PATH}/utils") +include_directories("${LIB_ROOT}/src" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${TOOLS_PATH}/utils") list(APPEND SOURCE_FILES "${TOOLS_PATH}/utils/console_log.cpp") list(APPEND SOURCE_FILES "${TOOLS_PATH}/utils/smartair2_server.cpp") diff --git a/tools/hon_simulator/CMakeLists.txt b/tools/hon_simulator/CMakeLists.txt index 5a1f5e8..d944286 100644 --- a/tools/hon_simulator/CMakeLists.txt +++ b/tools/hon_simulator/CMakeLists.txt @@ -25,7 +25,7 @@ if (UNIX) endif (UNIX) target_include_directories("${TEST_NAME}" PRIVATE - "${LIB_ROOT}/include" + "${LIB_ROOT}/src" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" ) diff --git a/tools/remote_serial_bridge/CMakeLists.txt b/tools/remote_serial_bridge/CMakeLists.txt index 29c32c1..1d986cb 100644 --- a/tools/remote_serial_bridge/CMakeLists.txt +++ b/tools/remote_serial_bridge/CMakeLists.txt @@ -27,7 +27,7 @@ if(WIN32) endif (WIN32) target_include_directories("${APP_NAME}" PRIVATE - "${LIB_ROOT}/include" + "${LIB_ROOT}/src" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" ) diff --git a/tools/smartair2_simulator/CMakeLists.txt b/tools/smartair2_simulator/CMakeLists.txt index 2028e7d..9acd247 100644 --- a/tools/smartair2_simulator/CMakeLists.txt +++ b/tools/smartair2_simulator/CMakeLists.txt @@ -25,7 +25,7 @@ if (UNIX) endif (UNIX) target_include_directories("${TEST_NAME}" PRIVATE - "${LIB_ROOT}/include" + "${LIB_ROOT}/src" "${CMAKE_CURRENT_SOURCE_DIR}/../utils" )