#include "unity.h" #include "status_monitor.h" #include "midea_protocol.h" #include "uart_driver.h" #include void setUp(void) {} void tearDown(void) {} // Build a valid MideaUART status response frame for testing. // data[0..7] follow the decoder layout: // d0: (mode & 0x0F) | (power << 4) // d1,d2: indoor_temp (LE) // d3,d4: target_temp (LE) // d5: (fan & 0x0F) | (error << 4) // d6: alarm low, d7: alarm high 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; } void test_status_monitor_init_defaults(void) { status_monitor_t m; TEST_ASSERT_TRUE(status_monitor_init(&m, NULL)); TEST_ASSERT_TRUE(m.initialized); TEST_ASSERT_EQUAL_UINT32(5000, m.config.poll_interval_ms); TEST_ASSERT_EQUAL_UINT32(1000, m.config.timeout_ms); TEST_ASSERT_EQUAL_UINT8(3, m.config.max_retries); TEST_ASSERT_FALSE(m.last_poll_success); TEST_ASSERT_FALSE(m.fault_detected); } void test_status_monitor_init_custom_config(void) { status_monitor_config_t cfg = { .poll_interval_ms = 2000, .timeout_ms = 500, .max_retries = 5 }; status_monitor_t m; TEST_ASSERT_TRUE(status_monitor_init(&m, &cfg)); TEST_ASSERT_EQUAL_UINT32(2000, m.config.poll_interval_ms); TEST_ASSERT_EQUAL_UINT32(500, m.config.timeout_ms); TEST_ASSERT_EQUAL_UINT8(5, m.config.max_retries); } void test_status_monitor_init_null(void) { TEST_ASSERT_FALSE(status_monitor_init(NULL, NULL)); } void test_build_request_frame(void) { uint8_t req[8]; size_t len = status_monitor_build_request(req, sizeof(req)); TEST_ASSERT_EQUAL_UINT(5, len); TEST_ASSERT_EQUAL_UINT8(0xAA, req[0]); TEST_ASSERT_EQUAL_UINT8(0x55, req[1]); TEST_ASSERT_EQUAL_UINT8(0x07, req[3]); // checksum = len ^ cmd = 1 ^ 0x07 TEST_ASSERT_EQUAL_UINT8((uint8_t)(1 ^ 0x07), req[4]); } void test_build_request_buffer_too_small(void) { uint8_t req[3]; TEST_ASSERT_EQUAL_UINT(0, status_monitor_build_request(req, sizeof(req))); TEST_ASSERT_EQUAL_UINT(0, status_monitor_build_request(NULL, 8)); } void test_process_response_success(void) { status_monitor_t m; status_monitor_init(&m, NULL); uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0); midea_status_t status; TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 1000, &status)); TEST_ASSERT_EQUAL_UINT8(MODE_COOL, status.mode); TEST_ASSERT_EQUAL_INT16(2300, status.indoor_temp); TEST_ASSERT_EQUAL_INT16(2400, status.target_temp); TEST_ASSERT_TRUE(m.last_poll_success); TEST_ASSERT_EQUAL_UINT32(1000, m.last_poll_time); TEST_ASSERT_EQUAL_UINT8(0, m.retry_count); TEST_ASSERT_FALSE(m.fault_detected); } void test_process_response_decode_failure(void) { status_monitor_t m; status_monitor_init(&m, NULL); // Bad header -> decode fails -> counts as error. uint8_t bad[12] = { 0x00, 0x00, 8, 0x07, 0, 0, 0, 0, 0, 0, 0, 0 }; midea_status_t status; TEST_ASSERT_FALSE(status_monitor_process_response(&m, bad, sizeof(bad), 500, &status)); TEST_ASSERT_FALSE(m.last_poll_success); TEST_ASSERT_EQUAL_UINT8(1, m.retry_count); TEST_ASSERT_EQUAL_UINT32(1, m.error_count); } void test_process_response_null_buffer(void) { status_monitor_t m; status_monitor_init(&m, NULL); midea_status_t status; TEST_ASSERT_FALSE(status_monitor_process_response(&m, NULL, 0, 100, &status)); TEST_ASSERT_EQUAL_UINT8(1, m.retry_count); } void test_fault_detection_error_code(void) { status_monitor_t m; status_monitor_init(&m, NULL); uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 3, 0); midea_status_t status; TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 1000, &status)); TEST_ASSERT_EQUAL_UINT8(3, status.error_code); TEST_ASSERT_TRUE(status_monitor_has_fault(&m)); TEST_ASSERT_TRUE(m.fault_code != 0); } void test_fault_detection_alarm_mask(void) { status_monitor_t m; status_monitor_init(&m, NULL); uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_HEAT, 1, 2000, 2100, 1, 0, 0x0004); midea_status_t status; TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 1000, &status)); TEST_ASSERT_EQUAL_UINT16(0x0004, status.alarm_mask); TEST_ASSERT_TRUE(status_monitor_has_fault(&m)); } void test_fault_cleared_on_good_status(void) { status_monitor_t m; status_monitor_init(&m, NULL); uint8_t frame[16]; // First a faulty status. size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 5, 0); midea_status_t status; status_monitor_process_response(&m, frame, len, 1000, &status); TEST_ASSERT_TRUE(status_monitor_has_fault(&m)); // Then a clean status clears the fault. len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0); status_monitor_process_response(&m, frame, len, 2000, &status); TEST_ASSERT_FALSE(status_monitor_has_fault(&m)); TEST_ASSERT_EQUAL_UINT16(0, m.fault_code); } void test_handle_error_trips_watchdog(void) { status_monitor_config_t cfg = { .poll_interval_ms = 5000, .timeout_ms = 1000, .max_retries = 3 }; status_monitor_t m; status_monitor_init(&m, &cfg); status_monitor_handle_error(&m); TEST_ASSERT_EQUAL_UINT8(1, m.retry_count); TEST_ASSERT_FALSE(m.comm_timeout); status_monitor_handle_error(&m); TEST_ASSERT_EQUAL_UINT8(2, m.retry_count); TEST_ASSERT_FALSE(m.comm_timeout); status_monitor_handle_error(&m); TEST_ASSERT_EQUAL_UINT8(3, m.retry_count); TEST_ASSERT_TRUE(m.comm_timeout); TEST_ASSERT_EQUAL_UINT32(3, m.error_count); } void test_check_timeout_never_polled(void) { status_monitor_t m; status_monitor_init(&m, NULL); TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 0)); TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 100000)); } void test_check_timeout_within_window(void) { status_monitor_t m; status_monitor_init(&m, NULL); // window = 5000 + 1000 = 6000 uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0); midea_status_t status; status_monitor_process_response(&m, frame, len, 1000, &status); // 1000 -> 5000 : within 6000ms window TEST_ASSERT_FALSE(status_monitor_check_timeout(&m, 5000)); } void test_check_timeout_exceeded(void) { status_monitor_t m; status_monitor_init(&m, NULL); // window = 6000 uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0); midea_status_t status; status_monitor_process_response(&m, frame, len, 1000, &status); // 1000 -> 8000 = 7000ms elapsed > 6000ms window TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 8000)); } void test_check_timeout_uninitialized(void) { status_monitor_t m; memset(&m, 0, sizeof(m)); TEST_ASSERT_TRUE(status_monitor_check_timeout(&m, 100)); TEST_ASSERT_TRUE(status_monitor_check_timeout(NULL, 100)); } void test_poll_uart_no_data(void) { // The mock UART receive returns 0 bytes -> poll should fail and count error. status_monitor_t m; status_monitor_init(&m, NULL); uart_config_t ucfg = { .tx_pin = 1, .rx_pin = 2, .baud_rate = 9600, .data_bits = 8, .parity = 0, .stop_bits = 1 }; uart_driver_t uart; TEST_ASSERT_TRUE(uart_driver_init(&uart, &ucfg)); midea_status_t status; TEST_ASSERT_FALSE(status_monitor_poll(&m, &uart, 1000, &status)); TEST_ASSERT_FALSE(m.last_poll_success); TEST_ASSERT_EQUAL_UINT8(1, m.retry_count); TEST_ASSERT_EQUAL_UINT32(1000, m.last_attempt_time); } void test_poll_null_args(void) { status_monitor_t m; status_monitor_init(&m, NULL); midea_status_t status; TEST_ASSERT_FALSE(status_monitor_poll(&m, NULL, 0, &status)); TEST_ASSERT_FALSE(status_monitor_poll(NULL, NULL, 0, &status)); } void test_map_to_zcl_modes(void) { midea_status_t s; zcl_thermostat_attrs_t z; memset(&s, 0, sizeof(s)); // Power off -> Off regardless of mode s.power_state = 0; s.mode = MODE_COOL; s.indoor_temp = 2500; status_monitor_map_to_zcl(&s, &z); TEST_ASSERT_EQUAL_UINT8(0x00, z.system_mode); TEST_ASSERT_EQUAL_INT16(2500, z.local_temperature); s.power_state = 1; s.mode = MODE_COOL; status_monitor_map_to_zcl(&s, &z); TEST_ASSERT_EQUAL_UINT8(0x03, z.system_mode); s.mode = MODE_HEAT; status_monitor_map_to_zcl(&s, &z); TEST_ASSERT_EQUAL_UINT8(0x04, z.system_mode); s.mode = MODE_AUTO; status_monitor_map_to_zcl(&s, &z); TEST_ASSERT_EQUAL_UINT8(0x01, z.system_mode); s.mode = MODE_DRY; status_monitor_map_to_zcl(&s, &z); TEST_ASSERT_EQUAL_UINT8(0x08, z.system_mode); } void test_get_last_status_and_zcl(void) { status_monitor_t m; status_monitor_init(&m, NULL); uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_HEAT, 1, 2100, 2200, 3, 0, 0); midea_status_t status; status_monitor_process_response(&m, frame, len, 1000, &status); midea_status_t got; TEST_ASSERT_TRUE(status_monitor_get_last_status(&m, &got)); TEST_ASSERT_EQUAL_UINT8(MODE_HEAT, got.mode); TEST_ASSERT_EQUAL_INT16(2100, got.indoor_temp); zcl_thermostat_attrs_t z; TEST_ASSERT_TRUE(status_monitor_get_last_zcl_attrs(&m, &z)); TEST_ASSERT_EQUAL_UINT8(0x04, z.system_mode); TEST_ASSERT_EQUAL_INT16(2100, z.local_temperature); TEST_ASSERT_FALSE(status_monitor_get_last_status(&m, NULL)); TEST_ASSERT_FALSE(status_monitor_get_last_status(NULL, &got)); } void test_successful_poll_resets_retry(void) { status_monitor_t m; status_monitor_init(&m, NULL); // Accumulate errors status_monitor_handle_error(&m); status_monitor_handle_error(&m); TEST_ASSERT_EQUAL_UINT8(2, m.retry_count); // A good response resets retry_count and clears comm_timeout uint8_t frame[16]; size_t len = build_status_frame(frame, MODE_COOL, 1, 2300, 2400, 2, 0, 0); midea_status_t status; TEST_ASSERT_TRUE(status_monitor_process_response(&m, frame, len, 3000, &status)); TEST_ASSERT_EQUAL_UINT8(0, m.retry_count); TEST_ASSERT_FALSE(m.comm_timeout); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_status_monitor_init_defaults); RUN_TEST(test_status_monitor_init_custom_config); RUN_TEST(test_status_monitor_init_null); RUN_TEST(test_build_request_frame); RUN_TEST(test_build_request_buffer_too_small); RUN_TEST(test_process_response_success); RUN_TEST(test_process_response_decode_failure); RUN_TEST(test_process_response_null_buffer); RUN_TEST(test_fault_detection_error_code); RUN_TEST(test_fault_detection_alarm_mask); RUN_TEST(test_fault_cleared_on_good_status); RUN_TEST(test_handle_error_trips_watchdog); RUN_TEST(test_check_timeout_never_polled); RUN_TEST(test_check_timeout_within_window); RUN_TEST(test_check_timeout_exceeded); RUN_TEST(test_check_timeout_uninitialized); RUN_TEST(test_poll_uart_no_data); RUN_TEST(test_poll_null_args); RUN_TEST(test_map_to_zcl_modes); RUN_TEST(test_get_last_status_and_zcl); RUN_TEST(test_successful_poll_resets_retry); return UNITY_END(); }