From b3e09d991d47da64e3bf8675bb1130355f0291ab Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Sat, 11 Jul 2026 19:27:26 +0300 Subject: [PATCH] feat: end-to-end app controller integration and tests Add app_controller wiring UART, integration, status monitor and Zigbee ZCL layers together with command/feedback flows and reset/recovery. Add main.c entry point (guarded for test builds), app_controller_test integration tests (full command cycle, timing under load, recovery), Makefile target, and Readme updates. Co-Authored-By: Claude Opus 4.8 --- Makefile | 27 +- Readme.md | 33 ++ ...lu-ac-esp32c6-controller-implementation.md | 14 +- src/app_controller.c | 242 ++++++++++++++ src/app_controller.h | 123 +++++++ src/main.c | 56 ++++ test/app_controller_test | Bin 0 -> 76672 bytes test/app_controller_test.c | 311 ++++++++++++++++++ test/integration_layer_test | Bin 75496 -> 76136 bytes test/status_monitor_test | Bin 76464 -> 77104 bytes test/uart_driver_test | Bin 75440 -> 76096 bytes test/zigbee_zcl_test | Bin 58960 -> 76128 bytes 12 files changed, 792 insertions(+), 14 deletions(-) create mode 100644 src/app_controller.c create mode 100644 src/app_controller.h create mode 100644 src/main.c create mode 100755 test/app_controller_test create mode 100644 test/app_controller_test.c diff --git a/Makefile b/Makefile index f357b72..a5aa6c5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for Ballu AC ESP32-C6 Controller -.PHONY: all build test clean test-uart test-midea-protocol test-zigbee-zcl test-integration-layer test-status-monitor help +.PHONY: all build test clean test-uart test-midea-protocol test-zigbee-zcl test-integration-layer test-status-monitor test-app-controller help # Directories SRC_DIR := src @@ -15,6 +15,9 @@ TEST_FILES := $(wildcard $(TEST_DIR)/*_test.c) # Compiler settings CC := gcc CFLAGS := -Wall -Wextra -std=c99 -I$(SRC_DIR) -I$(UNITY_DIR)/src +# Test builds link every src/*.c together, so main()'s entry point must be +# excluded to avoid clashing with each test's own main(). +TEST_CFLAGS := $(CFLAGS) -DUNIT_TEST LDFLAGS := # Unity test framework @@ -31,29 +34,33 @@ $(BUILD_DIR)/app: $(SRC_FILES) $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) # Build and run tests -test: test-uart test-midea-protocol test-zigbee-zcl test-integration-layer test-status-monitor +test: test-uart test-midea-protocol test-zigbee-zcl test-integration-layer test-status-monitor test-app-controller @echo "All tests passed!" # Build individual test executables $(TEST_DIR)/uart_driver_test: $(TEST_DIR)/uart_driver_test.c $(SRC_FILES) $(UNITY_SRC) @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) + $(CC) $(TEST_CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) $(TEST_DIR)/midea_protocol_test: $(TEST_DIR)/midea_protocol_test.c $(SRC_FILES) $(UNITY_SRC) @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) + $(CC) $(TEST_CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) $(TEST_DIR)/zigbee_zcl_test: $(TEST_DIR)/zigbee_zcl_test.c $(SRC_FILES) $(UNITY_SRC) @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) + $(CC) $(TEST_CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) $(TEST_DIR)/integration_layer_test: $(TEST_DIR)/integration_layer_test.c $(SRC_FILES) $(UNITY_SRC) @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) + $(CC) $(TEST_CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) $(TEST_DIR)/status_monitor_test: $(TEST_DIR)/status_monitor_test.c $(SRC_FILES) $(UNITY_SRC) @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) + $(CC) $(TEST_CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) + +$(TEST_DIR)/app_controller_test: $(TEST_DIR)/app_controller_test.c $(SRC_FILES) $(UNITY_SRC) + @mkdir -p $(BUILD_DIR) + $(CC) $(TEST_CFLAGS) $< $(SRC_FILES) $(UNITY_SRC) -o $@ $(LDFLAGS) # Run individual tests test-uart: $(TEST_DIR)/uart_driver_test @@ -76,6 +83,10 @@ test-status-monitor: $(TEST_DIR)/status_monitor_test @echo "Running status monitor tests..." @./$(TEST_DIR)/status_monitor_test +test-app-controller: $(TEST_DIR)/app_controller_test + @echo "Running app controller (end-to-end) tests..." + @./$(TEST_DIR)/app_controller_test + # Clean build artifacts clean: rm -rf $(BUILD_DIR) @@ -91,5 +102,7 @@ help: @echo " test-midea-protocol - Run Midea protocol tests" @echo " test-zigbee-zcl - Run Zigbee ZCL tests" @echo " test-integration-layer - Run integration layer tests" + @echo " test-status-monitor - Run status monitor tests" + @echo " test-app-controller - Run end-to-end app controller tests" @echo " clean - Clean build artifacts" @echo " help - Show this help" \ No newline at end of file diff --git a/Readme.md b/Readme.md index 57f59f9..cb5f967 100644 --- a/Readme.md +++ b/Readme.md @@ -13,6 +13,7 @@ This project implements a bridge between Zigbee (Home Assistant) and UART-based - Bidirectional communication between Zigbee and UART layers - Status monitoring with periodic AC polling, fault detection, and a communication watchdog - Configurable timing controls (50ms command spacing) +- End-to-end application controller wiring all layers together with reset/recovery ## Directory Structure @@ -23,6 +24,7 @@ src/ zigbee_zcl.c/h - Zigbee ZCL Thermostat cluster integration_layer.c/h- Integration between Zigbee and UART layers status_monitor.c/h - AC status polling, fault detection, and comms watchdog + app_controller.c/h - End-to-end controller wiring all layers + reset/recovery main.c - Application entry point test/ @@ -31,6 +33,7 @@ test/ zigbee_zcl_test.c - Unit tests for Zigbee ZCL integration_layer_test.c - Unit tests for integration layer status_monitor_test.c - Unit tests for status monitoring + app_controller_test.c - End-to-end integration tests (full command cycles) docs/ protocol.md - MideaUART protocol details @@ -81,6 +84,36 @@ The `status_monitor` module (see `src/status_monitor.c/h`) provides: Configuration (`status_monitor_config_t`) exposes `poll_interval_ms` (default 5000ms), `timeout_ms` (default 1000ms), and `max_retries` (default 3). +## End-to-End Integration + +The `app_controller` module (see `src/app_controller.c/h`) ties every layer +together and drives the two full data flows: + +- Command path (HA → Zigbee → Integration → UART → AC): + `app_controller_process_zigbee_command()` forwards an incoming Zigbee command + through the integration layer, which encodes and transmits the corresponding + MideaUART frame with 50ms rate limiting. +- Feedback path (AC → UART → Status → Integration → Zigbee → HA): + `app_controller_process_ac_status()` decodes a MideaUART status frame, runs + fault detection / watchdog bookkeeping, and publishes the result to the ZCL + Thermostat cluster for Home Assistant to read. + +Reset and recovery: + +- `app_controller_reset()` re-initializes UART and integration state and clears + status-monitor error counters without dropping configuration. +- `app_controller_recover_if_needed()` performs a reset automatically when the + communication watchdog trips (retries exhausted or poll window exceeded). +- `app_controller_is_healthy()` reports comms liveness plus fault state. + +`src/main.c` provides the firmware entry point and service loop. Its `main()` is +compiled only for the firmware build; unit-test binaries define `UNIT_TEST` and +supply their own `main()`. + +Integration tests in `test/app_controller_test.c` exercise full command cycles, +including a HA→…→AC→…→HA round trip, timing under a 100-command burst, and the +reset/recovery paths. + ## Implementation Progress See `docs/plans/2026-07-05-ballu-ac-esp32c6-controller-implementation.md` for detailed implementation plan and progress tracking. diff --git a/docs/plans/2026-07-05-ballu-ac-esp32c6-controller-implementation.md b/docs/plans/2026-07-05-ballu-ac-esp32c6-controller-implementation.md index c4f2c0c..fb7bd89 100644 --- a/docs/plans/2026-07-05-ballu-ac-esp32c6-controller-implementation.md +++ b/docs/plans/2026-07-05-ballu-ac-esp32c6-controller-implementation.md @@ -71,13 +71,13 @@ Implementation of ESP32-C6 based AC controller that bridges Zigbee (Home Assista - [x] Update Readme.md ### Task 6: End-to-End Integration and Testing -- [ ] Integrate all layers: Zigbee ←→ Integration ←→ UART -- [ ] Test command flow: HA → Zigbee → UART → AC → Status → Zigbee → HA -- [ ] Validate timing constraints under load -- [ ] Implement reset/recovery procedures -- [ ] Write integration tests for full command cycles -- [ ] Run full test suite - must pass before completion -- [ ] Update Readme.md +- [x] Integrate all layers: Zigbee ←→ Integration ←→ UART +- [x] Test command flow: HA → Zigbee → UART → AC → Status → Zigbee → HA +- [x] Validate timing constraints under load +- [x] Implement reset/recovery procedures +- [x] Write integration tests for full command cycles +- [x] Run full test suite - must pass before completion +- [x] Update Readme.md ### Task 7: Documentation and Validation - [ ] Update CLAUDE.md with implementation details diff --git a/src/app_controller.c b/src/app_controller.c new file mode 100644 index 0000000..86bf395 --- /dev/null +++ b/src/app_controller.c @@ -0,0 +1,242 @@ +#include "app_controller.h" +#include + +#define DEFAULT_COMMAND_SPACING_MS 50 + +// Default UART configuration for MideaUART: 9600 baud, 8N1. +static void apply_default_uart_config(uart_config_t *cfg) { + cfg->tx_pin = 16; + cfg->rx_pin = 17; + cfg->baud_rate = 9600; + cfg->data_bits = 8; + cfg->parity = 0; // none + cfg->stop_bits = 1; +} + +bool app_controller_init(app_controller_t *app, const app_controller_config_t *config) { + if (!app) { + return false; + } + + memset(app, 0, sizeof(*app)); + + // Resolve configuration (fall back to defaults where needed). + if (config) { + app->config = *config; + } else { + memset(&app->config, 0, sizeof(app->config)); + } + if (app->config.uart_config.baud_rate == 0) { + apply_default_uart_config(&app->config.uart_config); + } + if (app->config.command_spacing_ms == 0) { + app->config.command_spacing_ms = DEFAULT_COMMAND_SPACING_MS; + } + + // Bring up the UART driver. + if (!uart_driver_init(&app->uart, &app->config.uart_config)) { + return false; + } + + // Bring up the integration layer on top of the UART driver. + integration_layer_config_t ilc = { + .uart_driver = &app->uart, + .command_queue_size = 16, + .command_timeout_ms = 1000, + }; + if (!integration_layer_init(&app->integration, &ilc)) { + uart_driver_deinit(&app->uart); + return false; + } + app->integration.command_spacing_ms = app->config.command_spacing_ms; + + // Bring up the status monitor. + if (!status_monitor_init(&app->status_monitor, &app->config.status_config)) { + integration_layer_deinit(&app->integration); + uart_driver_deinit(&app->uart); + return false; + } + + // Bring up the Zigbee ZCL cluster. + if (!zigbee_zcl_init()) { + status_monitor_deinit(&app->status_monitor); + integration_layer_deinit(&app->integration); + uart_driver_deinit(&app->uart); + return false; + } + + app->commands_sent = 0; + app->status_updates = 0; + app->recovery_count = 0; + app->initialized = true; + return true; +} + +void app_controller_deinit(app_controller_t *app) { + if (!app) { + return; + } + status_monitor_deinit(&app->status_monitor); + integration_layer_deinit(&app->integration); + uart_driver_deinit(&app->uart); + app->initialized = false; +} + +bool app_controller_process_zigbee_command(app_controller_t *app, + uint8_t endpoint, uint16_t cluster_id, + uint8_t command_id, const uint8_t *payload, + uint16_t payload_length, + uint8_t *uart_out, size_t *uart_out_len) { + if (!app || !app->initialized) { + if (uart_out_len) { + *uart_out_len = 0; + } + return false; + } + + size_t out_len = (uart_out_len != NULL) ? *uart_out_len : 0; + + bool ok = integration_layer_handle_zigbee_command(&app->integration, + endpoint, cluster_id, command_id, + payload, payload_length, + uart_out, uart_out ? &out_len : NULL); + + if (uart_out_len) { + *uart_out_len = ok ? out_len : 0; + } + + // Count only commands that actually produced an AC-bound UART frame. + if (ok && uart_out && out_len > 0) { + app->commands_sent++; + } + + return ok; +} + +bool app_controller_process_ac_status(app_controller_t *app, + const uint8_t *frame, size_t length, + uint32_t current_time, + zcl_thermostat_attrs_t *out_attrs) { + if (!app || !app->initialized || !frame || length == 0) { + return false; + } + + midea_status_t status; + if (!status_monitor_process_response(&app->status_monitor, frame, length, + current_time, &status)) { + return false; + } + + // Map the decoded status to ZCL attributes. + zcl_thermostat_attrs_t attrs; + status_monitor_map_to_zcl(&status, &attrs); + + // Push into the Zigbee ZCL cluster so Home Assistant can read it back. + zigbee_zcl_set_local_temperature(attrs.local_temperature); + zigbee_zcl_set_system_mode(attrs.system_mode); + + if (out_attrs) { + *out_attrs = attrs; + } + + app->status_updates++; + return true; +} + +bool app_controller_poll_status(app_controller_t *app, uint32_t current_time) { + if (!app || !app->initialized) { + return false; + } + + midea_status_t status; + bool ok = status_monitor_poll(&app->status_monitor, &app->uart, + current_time, &status); + if (ok) { + zcl_thermostat_attrs_t attrs; + status_monitor_map_to_zcl(&status, &attrs); + zigbee_zcl_set_local_temperature(attrs.local_temperature); + zigbee_zcl_set_system_mode(attrs.system_mode); + app->status_updates++; + return true; + } + + // Communication failed - let recovery decide whether to reset. + app_controller_recover_if_needed(app, current_time); + return false; +} + +bool app_controller_reset(app_controller_t *app) { + if (!app) { + return false; + } + + // Re-initialize UART. + uart_driver_deinit(&app->uart); + if (!uart_driver_init(&app->uart, &app->config.uart_config)) { + app->initialized = false; + return false; + } + + // Reset integration command state (rebind to the fresh UART driver). + integration_layer_config_t ilc = { + .uart_driver = &app->uart, + .command_queue_size = 16, + .command_timeout_ms = 1000, + }; + if (!integration_layer_init(&app->integration, &ilc)) { + app->initialized = false; + return false; + } + app->integration.command_spacing_ms = app->config.command_spacing_ms; + + // Reset status-monitor counters while preserving configuration. + if (!status_monitor_init(&app->status_monitor, &app->config.status_config)) { + app->initialized = false; + return false; + } + + app->initialized = true; + return true; +} + +bool app_controller_recover_if_needed(app_controller_t *app, uint32_t current_time) { + if (!app || !app->initialized) { + return false; + } + + bool needs_recovery = app->status_monitor.comm_timeout || + status_monitor_check_timeout(&app->status_monitor, current_time); + + // "Never polled yet" is not a fault condition - don't churn on startup. + if (needs_recovery && !app->status_monitor.last_poll_success && + app->status_monitor.error_count == 0) { + return false; + } + + if (needs_recovery) { + if (app_controller_reset(app)) { + app->recovery_count++; + return true; + } + } + + return false; +} + +bool app_controller_is_healthy(const app_controller_t *app, uint32_t current_time) { + if (!app || !app->initialized) { + return false; + } + if (status_monitor_has_fault(&app->status_monitor)) { + return false; + } + if (app->status_monitor.comm_timeout) { + return false; + } + // Before the first successful poll the watchdog reports "timed out"; treat + // that as healthy-until-proven-otherwise so startup isn't flagged as a fault. + if (!app->status_monitor.last_poll_success) { + return true; + } + return !status_monitor_check_timeout(&app->status_monitor, current_time); +} diff --git a/src/app_controller.h b/src/app_controller.h new file mode 100644 index 0000000..a697ed1 --- /dev/null +++ b/src/app_controller.h @@ -0,0 +1,123 @@ +#ifndef APP_CONTROLLER_H +#define APP_CONTROLLER_H + +#include +#include +#include + +#include "uart_driver.h" +#include "integration_layer.h" +#include "status_monitor.h" +#include "zigbee_zcl.h" +#include "midea_protocol.h" + +/** + * @brief Top-level application configuration. + * + * Ties together UART, integration and status-monitor configuration so the + * whole stack can be brought up from a single call. + */ +typedef struct { + uart_config_t uart_config; + status_monitor_config_t status_config; + uint32_t command_spacing_ms; /**< MideaUART command spacing (0 -> default 50ms) */ +} app_controller_config_t; + +/** + * @brief Top-level application controller. + * + * Owns every subsystem and provides the two end-to-end data flows: + * - Home Assistant -> Zigbee -> Integration -> UART -> AC (command path) + * - AC -> UART -> Status -> Integration -> Zigbee -> HA (feedback path) + */ +typedef struct { + uart_driver_t uart; + integration_layer_t integration; + status_monitor_t status_monitor; + bool initialized; + uint32_t commands_sent; /**< Count of Zigbee commands forwarded to the AC */ + uint32_t status_updates; /**< Count of AC status frames applied to ZCL */ + uint32_t recovery_count; /**< Number of reset/recovery cycles performed */ + app_controller_config_t config; +} app_controller_t; + +/** + * @brief Initialize the full application stack. + * + * Brings up UART, integration layer, status monitor and the Zigbee ZCL + * cluster. If @p config is NULL, sensible defaults are applied. + * + * @return true on success, false on bad args or subsystem init failure. + */ +bool app_controller_init(app_controller_t *app, const app_controller_config_t *config); + +/** + * @brief Tear down the full application stack. + */ +void app_controller_deinit(app_controller_t *app); + +/** + * @brief Command path: HA -> Zigbee -> Integration -> UART -> AC. + * + * Forwards an incoming Zigbee command through the integration layer, which + * encodes and transmits the corresponding MideaUART frame with rate limiting. + * + * @param uart_out Optional buffer to capture the encoded UART frame. + * @param uart_out_len In: capacity of uart_out. Out: bytes written (0 if none). + * @return true if the command was processed successfully. + */ +bool app_controller_process_zigbee_command(app_controller_t *app, + uint8_t endpoint, uint16_t cluster_id, + uint8_t command_id, const uint8_t *payload, + uint16_t payload_length, + uint8_t *uart_out, size_t *uart_out_len); + +/** + * @brief Feedback path: AC status frame -> Status -> Integration -> Zigbee -> HA. + * + * Decodes a raw MideaUART status frame, runs fault detection / watchdog + * bookkeeping via the status monitor, and pushes the resulting attributes + * into the Zigbee ZCL thermostat cluster. + * + * @param current_time Monotonic time in ms (for the watchdog). + * @param out_attrs Optional; receives the ZCL attributes applied. + * @return true if the frame decoded and was applied. + */ +bool app_controller_process_ac_status(app_controller_t *app, + const uint8_t *frame, size_t length, + uint32_t current_time, + zcl_thermostat_attrs_t *out_attrs); + +/** + * @brief Full poll cycle: request status over UART, apply it, run watchdog. + * + * Uses the status monitor to send a request and read a response through the + * live UART driver. On communication failure, recovery is attempted. + * + * @return true on a successful poll, false on communication failure. + */ +bool app_controller_poll_status(app_controller_t *app, uint32_t current_time); + +/** + * @brief Reset all runtime state to a known-good baseline. + * + * Re-initializes UART and integration state and clears status-monitor error + * counters, without dropping the configured parameters. Used by recovery. + * + * @return true on success. + */ +bool app_controller_reset(app_controller_t *app); + +/** + * @brief Perform recovery if the communication watchdog has tripped. + * + * @return true if recovery was performed, false if none was needed. + */ +bool app_controller_recover_if_needed(app_controller_t *app, uint32_t current_time); + +/** + * @brief Health check: true when comms are alive and no fault is latched. + */ +bool app_controller_is_healthy(const app_controller_t *app, uint32_t current_time); + +#endif // APP_CONTROLLER_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f3dd4c6 --- /dev/null +++ b/src/main.c @@ -0,0 +1,56 @@ +/* + * Ballu AC ESP32-C6 Controller - application entry point. + * + * Wires the full stack together via the application controller and runs the + * main service loop: forward Zigbee commands to the AC and periodically poll + * AC status back into the Zigbee ZCL thermostat cluster. + * + * The main() below is compiled only for the firmware/app build. Unit-test + * binaries define UNIT_TEST and provide their own main(), so it is excluded + * there to avoid a duplicate-symbol clash when all source files are linked in. + */ + +#include "app_controller.h" + +#ifndef UNIT_TEST + +#include + +// Provided by the platform layer on real hardware (ESP-IDF: esp_timer / +// xTaskGetTickCount). Weakly stubbed here so the app links standalone. +__attribute__((weak)) uint32_t platform_now_ms(void) { + return 0; +} + +__attribute__((weak)) void platform_sleep_ms(uint32_t ms) { + (void)ms; +} + +int main(void) { + app_controller_t app; + + if (!app_controller_init(&app, NULL)) { + printf("app_controller_init failed\n"); + return 1; + } + + printf("Ballu AC controller started (UART %d baud)\n", + app.config.uart_config.baud_rate); + + // Service loop: poll status on the configured interval and let the Zigbee + // stack deliver commands via app_controller_process_zigbee_command(). + for (;;) { + uint32_t now = platform_now_ms(); + + app_controller_poll_status(&app, now); + app_controller_recover_if_needed(&app, now); + + platform_sleep_ms(app.status_monitor.config.poll_interval_ms); + } + + // Not reached. + app_controller_deinit(&app); + return 0; +} + +#endif // UNIT_TEST diff --git a/test/app_controller_test b/test/app_controller_test new file mode 100755 index 0000000000000000000000000000000000000000..4968681afe12756b20cc9981d905c358d84aad22 GIT binary patch literal 76672 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 V?<#kXYkarw=7TrfTJN~}{{a7;Y$5;v literal 0 HcmV?d00001 diff --git a/test/app_controller_test.c b/test/app_controller_test.c new file mode 100644 index 0000000..0679f67 --- /dev/null +++ b/test/app_controller_test.c @@ -0,0 +1,311 @@ +#include "unity.h" +#include "app_controller.h" +#include "midea_protocol.h" +#include "zigbee_zcl.h" +#include + +// ------------------------------------------------------------------------- +// Helpers +// ------------------------------------------------------------------------- + +// Build a valid MideaUART status response frame (same layout the decoder and +// status_monitor tests expect). +static size_t build_status_frame(uint8_t *buf, uint8_t mode, uint8_t power, + int16_t indoor, int16_t target, + uint8_t fan, uint8_t error, uint16_t alarm) { + size_t o = 0; + buf[o++] = 0xAA; + buf[o++] = 0x55; + buf[o++] = 8; // length + buf[o++] = 0x07; // status command + buf[o++] = (mode & 0x0F) | ((power & 0x01) << 4); + buf[o++] = indoor & 0xFF; + buf[o++] = (indoor >> 8) & 0xFF; + buf[o++] = target & 0xFF; + buf[o++] = (target >> 8) & 0xFF; + buf[o++] = (fan & 0x0F) | ((error & 0x0F) << 4); + buf[o++] = alarm & 0xFF; + buf[o++] = (alarm >> 8) & 0xFF; + uint8_t chk = 0; + for (size_t i = 2; i < o; i++) chk ^= buf[i]; + buf[o++] = chk; + return o; +} + +static app_controller_t app; + +void setUp(void) { + memset(&app, 0, sizeof(app)); +} + +void tearDown(void) { + app_controller_deinit(&app); +} + +// ------------------------------------------------------------------------- +// Init / deinit +// ------------------------------------------------------------------------- + +void test_app_init_defaults(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + TEST_ASSERT_TRUE(app.initialized); + TEST_ASSERT_TRUE(uart_driver_is_initialized(&app.uart)); + TEST_ASSERT_TRUE(app.integration.initialized); + TEST_ASSERT_TRUE(app.status_monitor.initialized); + // Defaults: 9600 baud, 50ms spacing. + TEST_ASSERT_EQUAL_INT(9600, app.config.uart_config.baud_rate); + TEST_ASSERT_EQUAL_UINT32(50, app.integration.command_spacing_ms); +} + +void test_app_init_null(void) { + TEST_ASSERT_FALSE(app_controller_init(NULL, NULL)); +} + +void test_app_init_custom_config(void) { + app_controller_config_t cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.uart_config.baud_rate = 9600; + cfg.uart_config.data_bits = 8; + cfg.uart_config.parity = 0; + cfg.uart_config.stop_bits = 1; + cfg.status_config.poll_interval_ms = 2000; + cfg.status_config.timeout_ms = 500; + cfg.status_config.max_retries = 2; + cfg.command_spacing_ms = 75; + + TEST_ASSERT_TRUE(app_controller_init(&app, &cfg)); + TEST_ASSERT_EQUAL_UINT32(2000, app.status_monitor.config.poll_interval_ms); + TEST_ASSERT_EQUAL_UINT8(2, app.status_monitor.config.max_retries); + TEST_ASSERT_EQUAL_UINT32(75, app.integration.command_spacing_ms); +} + +void test_app_deinit(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + app_controller_deinit(&app); + TEST_ASSERT_FALSE(app.initialized); + TEST_ASSERT_FALSE(uart_driver_is_initialized(&app.uart)); +} + +// ------------------------------------------------------------------------- +// Command path: HA -> Zigbee -> UART -> AC +// ------------------------------------------------------------------------- + +void test_command_path_produces_uart_frame(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + + // Zigbee setpoint command: [mode=Cool(3), temp_lsb, temp_msb] -> 2500 (25.00C) + uint8_t payload[] = {0x03, (uint8_t)(2500 & 0xFF), (uint8_t)((2500 >> 8) & 0xFF)}; + uint8_t uart_buf[64]; + size_t uart_len = sizeof(uart_buf); + + bool ok = app_controller_process_zigbee_command(&app, 1, 0x0201, 0x02, + payload, sizeof(payload), + uart_buf, &uart_len); + TEST_ASSERT_TRUE(ok); + TEST_ASSERT_GREATER_THAN(0, uart_len); + TEST_ASSERT_EQUAL_UINT8(0xAA, uart_buf[0]); + TEST_ASSERT_EQUAL_UINT8(0x55, uart_buf[1]); + TEST_ASSERT_EQUAL_UINT32(1, app.commands_sent); + + // The encoded frame must decode back to the requested cool @ 25.00C. + midea_status_t decoded; + TEST_ASSERT_TRUE(midea_protocol_decode(uart_buf, uart_len, &decoded)); + TEST_ASSERT_EQUAL_INT16(2500, decoded.target_temp); + TEST_ASSERT_EQUAL_UINT8(MODE_COOL, decoded.mode); +} + +void test_command_path_rejects_when_uninitialized(void) { + // Not initialized (setUp zeroed it). + uint8_t payload[] = {0x03, 0x00, 0x00}; + uint8_t uart_buf[64]; + size_t uart_len = sizeof(uart_buf); + bool ok = app_controller_process_zigbee_command(&app, 1, 0x0201, 0x02, + payload, sizeof(payload), + uart_buf, &uart_len); + TEST_ASSERT_FALSE(ok); + TEST_ASSERT_EQUAL_UINT32(0, uart_len); +} + +// ------------------------------------------------------------------------- +// Feedback path: AC -> Status -> Zigbee -> HA +// ------------------------------------------------------------------------- + +void test_feedback_path_updates_zigbee_attrs(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + + // Simulated AC status: cooling, on, indoor 24.00C, target 22.00C. + uint8_t frame[32]; + size_t len = build_status_frame(frame, MODE_COOL, 1, 2400, 2200, 3, 0, 0); + + zcl_thermostat_attrs_t attrs; + bool ok = app_controller_process_ac_status(&app, frame, len, 1000, &attrs); + TEST_ASSERT_TRUE(ok); + TEST_ASSERT_EQUAL_INT16(2400, attrs.local_temperature); + TEST_ASSERT_EQUAL_UINT8(0x03, attrs.system_mode); // Cooling + + // Verify the ZCL cluster (what HA reads) was actually updated. + TEST_ASSERT_EQUAL_INT16(2400, zigbee_zcl_get_local_temperature()); + TEST_ASSERT_EQUAL_UINT8(0x03, zigbee_zcl_get_system_mode()); + TEST_ASSERT_EQUAL_UINT32(1, app.status_updates); +} + +void test_feedback_path_rejects_bad_frame(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + uint8_t junk[] = {0x00, 0x11, 0x22, 0x33}; + bool ok = app_controller_process_ac_status(&app, junk, sizeof(junk), 1000, NULL); + TEST_ASSERT_FALSE(ok); + TEST_ASSERT_EQUAL_UINT32(0, app.status_updates); +} + +void test_feedback_path_detects_fault(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + // error code set -> fault. + uint8_t frame[32]; + size_t len = build_status_frame(frame, MODE_COOL, 1, 2400, 2200, 3, 0x5, 0); + TEST_ASSERT_TRUE(app_controller_process_ac_status(&app, frame, len, 1000, NULL)); + TEST_ASSERT_TRUE(status_monitor_has_fault(&app.status_monitor)); + TEST_ASSERT_FALSE(app_controller_is_healthy(&app, 1000)); +} + +// ------------------------------------------------------------------------- +// Full end-to-end round trip: HA -> ... -> AC -> ... -> HA +// ------------------------------------------------------------------------- + +void test_end_to_end_round_trip(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + + // 1. HA requests Heat @ 21.00C. + uint8_t payload[] = {0x04, (uint8_t)(2100 & 0xFF), (uint8_t)((2100 >> 8) & 0xFF)}; + uint8_t uart_buf[64]; + size_t uart_len = sizeof(uart_buf); + TEST_ASSERT_TRUE(app_controller_process_zigbee_command(&app, 1, 0x0201, 0x02, + payload, sizeof(payload), + uart_buf, &uart_len)); + TEST_ASSERT_GREATER_THAN(0, uart_len); + + // 2. AC responds with a status frame reflecting the new state. + // (heating mode maps to MODE_HEAT, indoor 20.50C, target 21.00C) + uint8_t status_frame[32]; + size_t status_len = build_status_frame(status_frame, MODE_HEAT, 1, 2050, 2100, 2, 0, 0); + + // 3. Feedback flows back to the Zigbee cluster. + zcl_thermostat_attrs_t attrs; + TEST_ASSERT_TRUE(app_controller_process_ac_status(&app, status_frame, status_len, + 2000, &attrs)); + TEST_ASSERT_EQUAL_INT16(2050, zigbee_zcl_get_local_temperature()); + TEST_ASSERT_EQUAL_UINT8(0x04, zigbee_zcl_get_system_mode()); // Heating + TEST_ASSERT_TRUE(app_controller_is_healthy(&app, 2000)); +} + +// ------------------------------------------------------------------------- +// Timing / load +// ------------------------------------------------------------------------- + +void test_timing_under_load(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + + // Fire a burst of commands; every one must encode cleanly and be counted. + const int N = 100; + for (int i = 0; i < N; i++) { + int16_t temp = (int16_t)(1600 + (i % 16) * 100); // 16.00C .. 31.00C + uint8_t mode = (i % 2) ? 0x03 : 0x04; // alternate cool/heat + uint8_t payload[] = {mode, (uint8_t)(temp & 0xFF), (uint8_t)((temp >> 8) & 0xFF)}; + uint8_t uart_buf[64]; + size_t uart_len = sizeof(uart_buf); + + bool ok = app_controller_process_zigbee_command(&app, 1, 0x0201, 0x02, + payload, sizeof(payload), + uart_buf, &uart_len); + TEST_ASSERT_TRUE(ok); + TEST_ASSERT_GREATER_THAN(0, uart_len); + + midea_status_t decoded; + TEST_ASSERT_TRUE(midea_protocol_decode(uart_buf, uart_len, &decoded)); + TEST_ASSERT_EQUAL_INT16(temp, decoded.target_temp); + } + TEST_ASSERT_EQUAL_UINT32((uint32_t)N, app.commands_sent); + // Rate-limit spacing constraint preserved throughout. + TEST_ASSERT_EQUAL_UINT32(50, app.integration.command_spacing_ms); +} + +// ------------------------------------------------------------------------- +// Reset / recovery +// ------------------------------------------------------------------------- + +void test_reset_restores_state(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + + // Dirty some state. + uint8_t frame[32]; + size_t len = build_status_frame(frame, MODE_COOL, 1, 2400, 2200, 3, 0x5, 0); + app_controller_process_ac_status(&app, frame, len, 1000, NULL); + TEST_ASSERT_TRUE(status_monitor_has_fault(&app.status_monitor)); + + // Reset clears the fault and re-initializes subsystems. + TEST_ASSERT_TRUE(app_controller_reset(&app)); + TEST_ASSERT_TRUE(app.initialized); + TEST_ASSERT_FALSE(status_monitor_has_fault(&app.status_monitor)); + TEST_ASSERT_TRUE(uart_driver_is_initialized(&app.uart)); + TEST_ASSERT_TRUE(app.integration.initialized); +} + +void test_recovery_on_comm_timeout(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + + // Force the watchdog to trip by exhausting retries. + for (int i = 0; i < 5; i++) { + status_monitor_handle_error(&app.status_monitor); + } + TEST_ASSERT_TRUE(app.status_monitor.comm_timeout); + + bool recovered = app_controller_recover_if_needed(&app, 100000); + TEST_ASSERT_TRUE(recovered); + TEST_ASSERT_EQUAL_UINT32(1, app.recovery_count); + TEST_ASSERT_FALSE(app.status_monitor.comm_timeout); // cleared by reset +} + +void test_no_recovery_at_startup(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + // Fresh init, never polled, no errors -> must not churn recovery. + bool recovered = app_controller_recover_if_needed(&app, 100000); + TEST_ASSERT_FALSE(recovered); + TEST_ASSERT_EQUAL_UINT32(0, app.recovery_count); +} + +void test_healthy_after_good_poll(void) { + TEST_ASSERT_TRUE(app_controller_init(&app, NULL)); + uint8_t frame[32]; + size_t len = build_status_frame(frame, MODE_AUTO, 1, 2300, 2300, 2, 0, 0); + TEST_ASSERT_TRUE(app_controller_process_ac_status(&app, frame, len, 1000, NULL)); + // Within the watchdog window immediately after a good status. + TEST_ASSERT_TRUE(app_controller_is_healthy(&app, 1000)); +} + +// ------------------------------------------------------------------------- + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_app_init_defaults); + RUN_TEST(test_app_init_null); + RUN_TEST(test_app_init_custom_config); + RUN_TEST(test_app_deinit); + + RUN_TEST(test_command_path_produces_uart_frame); + RUN_TEST(test_command_path_rejects_when_uninitialized); + + RUN_TEST(test_feedback_path_updates_zigbee_attrs); + RUN_TEST(test_feedback_path_rejects_bad_frame); + RUN_TEST(test_feedback_path_detects_fault); + + RUN_TEST(test_end_to_end_round_trip); + + RUN_TEST(test_timing_under_load); + + RUN_TEST(test_reset_restores_state); + RUN_TEST(test_recovery_on_comm_timeout); + RUN_TEST(test_no_recovery_at_startup); + RUN_TEST(test_healthy_after_good_poll); + + return UNITY_END(); +} diff --git a/test/integration_layer_test b/test/integration_layer_test index 2dbe7c43816ec15d32507843c1a8959e74d9bdc4..2a8cf9f80fdf682aad1545d442f85b6216ad277a 100755 GIT binary patch delta 12189 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�#6q@a?mhqDb7=w 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 J@~)+M{|}X+0F(d# delta 12141 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 ZyLZ5d^nTWnL-d)in)lZ&oIV#*`2RN^YoGuC delta 9717 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 L&wFsZW9!%A_G>tuW|OI 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