diff --git a/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h b/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h index 78ca2909e5..76515dbda9 100644 --- a/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h +++ b/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h @@ -691,6 +691,9 @@ DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE, DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_SCALE, ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x09)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_USE_B_INTERPRETATION, + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x0A)) + ///////////////////////////////////////////////// // Wakeup command class DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_WAKE_UP_VERSION, diff --git a/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp b/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp index 774ba2882d..12ba7ec935 100644 --- a/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp +++ b/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp @@ -294,7 +294,7 @@ static const std::vector attribute_schema = { // Thermostat Setpoint Command Class attributes ///////////////////////////////////////////////////////////////////// {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VERSION, "Thermostat Setpoint Version", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, - {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_SUPPORTED_SETPOINT_TYPES, "Thermostat Supported Setpoint Types", ATTRIBUTE_ENDPOINT_ID, BYTE_ARRAY_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_SUPPORTED_SETPOINT_TYPES, "Thermostat Supported Setpoint Types", ATTRIBUTE_ENDPOINT_ID, U32_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, "Thermostat Setpoint Type", ATTRIBUTE_ENDPOINT_ID, I8_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE, "Value", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, I32_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE, "Value Scale", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U32_STORAGE_TYPE}, @@ -302,6 +302,8 @@ static const std::vector attribute_schema = { {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE_SCALE, "Min Value Scale", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U32_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE, "Max Value", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, I32_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_SCALE, "Max Value Scale", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U32_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_USE_B_INTERPRETATION, "Use B Interpretation for Supported Setpoint Types", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_SUPPORTED_SETPOINT_TYPES, U8_STORAGE_TYPE}, + ///////////////////////////////////////////////////////////////////// // Supervision Command Class attributes ///////////////////////////////////////////////////////////////////// diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c index d4629b54a3..3042c4c0fb 100644 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c @@ -34,8 +34,80 @@ // Log define #define LOG_TAG "zwave_command_class_thermostat_setpoint" +// Max values in Thermostat Setpoint Supported Report values +#define MAX_SUPPORTED_SETPOINT_MODES 16 + #define ATTRIBUTE(type) ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_##type +static bool is_thermostat_setpoint_mode_compatible_with_version( + uint8_t supported_setpoint_mode_type, zwave_cc_version_t current_version) +{ + bool compatibility = false; + + switch (current_version) { + case 1: + compatibility = (supported_setpoint_mode_type <= 0x0A); + break; + case 2: + compatibility = (supported_setpoint_mode_type <= 0x0D); + break; + case 3: + compatibility = (supported_setpoint_mode_type <= 0x0F); + break; + default: + compatibility = false; + } + + if (!compatibility) { + sl_log_warning(LOG_TAG, + "Thermostat SetPoint mode %#04x is not compatible with " + "Thermostat SetPoint Version %d", + supported_setpoint_mode_type, + current_version); + } + + return compatibility; +} + +/////////////////////////////////////////////////////////////////////////////// +// Attribute creation functions +/////////////////////////////////////////////////////////////////////////////// +static attribute_store_node_t + zwave_command_class_thermostat_setpoint_create_type( + attribute_store_node_t endpoint_node, uint8_t type) +{ + zwave_cc_version_t current_version; + attribute_store_get_child_reported(endpoint_node, + ATTRIBUTE(VERSION), + ¤t_version, + sizeof(current_version)); + + // Check compatibility + if (!is_thermostat_setpoint_mode_compatible_with_version(type, + current_version)) { + return ATTRIBUTE_STORE_INVALID_NODE; + } + + // Do we already have the node ? + attribute_store_node_t type_node = attribute_store_emplace(endpoint_node, + ATTRIBUTE(TYPE), + &type, + sizeof(type)); + + // Add the six other nodes under the type. + const attribute_store_type_t additional_nodes[] + = {ATTRIBUTE(VALUE), + ATTRIBUTE(VALUE_SCALE), + ATTRIBUTE(MIN_VALUE), + ATTRIBUTE(MIN_VALUE_SCALE), + ATTRIBUTE(MAX_VALUE), + ATTRIBUTE(MAX_VALUE_SCALE)}; + attribute_store_add_if_missing(type_node, + additional_nodes, + COUNT_OF(additional_nodes)); + return type_node; +} + /////////////////////////////////////////////////////////////////////////////// // Private helper functions /////////////////////////////////////////////////////////////////////////////// @@ -154,9 +226,136 @@ static int32_t thermostat_setpoint_get_valid_desired_setpoint_value( return value_to_set; } +bool use_b_interpretation(attribute_store_node_t supported_setpoint_types_node) +{ + uint8_t use_b_interpretation = 0; + attribute_store_get_child_reported(supported_setpoint_types_node, + ATTRIBUTE(USE_B_INTERPRETATION), + &use_b_interpretation, + sizeof(use_b_interpretation)); + + return use_b_interpretation > 0; +} + +// Remove all ATTRIBUTE(TYPE) from attribute store +void remove_all_thermostat_setpoint_type_attributes( + attribute_store_node_t endpoint_node) +{ + attribute_store_node_t type_node; + do { + type_node = attribute_store_get_node_child_by_type(endpoint_node, + ATTRIBUTE(TYPE), + 0); + attribute_store_delete_node(type_node); + } while (type_node != ATTRIBUTE_STORE_INVALID_NODE); +} + +// Create ATTRIBUTE(TYPE) based on bitmask in supported_types +// This function will also check version +void create_all_supported_humidity_setpoint_type( + attribute_store_node_t endpoint_node, + uint32_t supported_types, + bool using_b_interpretation) +{ + // Contains current bit tested + uint32_t setpoint_mode_current_bit = 0x0; + // Start with 1 since bit 0 is N/A + for (uint8_t i = 1; i < MAX_SUPPORTED_SETPOINT_MODES; i++) { + setpoint_mode_current_bit = 1 << i; + setpoint_mode_current_bit &= supported_types; + uint8_t current_type; + + // This bit is marked as not supported so we continue + if (setpoint_mode_current_bit == 0) { + continue; + // THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1 && THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_HEATING_1 + // are the same interpretation. + } else if (i <= THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1 + || using_b_interpretation) { + current_type = i; + } else { // Shift 4 for standard interpretation for all values after 0x02 + current_type = i + 4; + } + + // Those values are undefined so we ignore them + if (current_type >= THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_NOT_SUPPORTED1 + && current_type + <= THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_NOT_SUPPORTED4) { + continue; + } + + // If we are here that means we want to create a setpoint type + zwave_command_class_thermostat_setpoint_create_type(endpoint_node, + current_type); + } +} + +sl_status_t + create_setpoint_type_attributes(attribute_store_node_t endpoint_node) +{ + attribute_store_node_t supported_setpoint_types_node + = attribute_store_get_first_child_by_type( + endpoint_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); + + // We might not be ready yet, no worries + if (supported_setpoint_types_node == ATTRIBUTE_STORE_INVALID_NODE) { + return SL_STATUS_IS_WAITING; + } + + // Reported bitmask + uint32_t supported_setpoint_mode_bitmask; + sl_status_t status + = attribute_store_get_reported(supported_setpoint_types_node, + &supported_setpoint_mode_bitmask, + sizeof(supported_setpoint_mode_bitmask)); + + // We might not be ready yet, no worries + if (status != SL_STATUS_OK) { + return SL_STATUS_IS_WAITING; + } + + // First remove all existing type node + // This is done to easily update the bitmask or bitmask interpretation so + // we don't have any leftovers. + remove_all_thermostat_setpoint_type_attributes(endpoint_node); + + // Check if we need to use B Interpretation + const bool using_b_interpretation + = use_b_interpretation(supported_setpoint_types_node); + // Then create attributed based on supported types + create_all_supported_humidity_setpoint_type(endpoint_node, + supported_setpoint_mode_bitmask, + using_b_interpretation); + return SL_STATUS_OK; +} + /////////////////////////////////////////////////////////////////////////////// // Attribute Callback functions /////////////////////////////////////////////////////////////////////////////// +static void + zwave_command_class_thermostat_setpoint_on_b_interpretation_attribute_update( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + if (change != ATTRIBUTE_UPDATED) { + return; + } + + attribute_store_node_t endpoint_node + = attribute_store_get_first_parent_with_type(updated_node, + ATTRIBUTE_ENDPOINT_ID); + + if (endpoint_node == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_warning(LOG_TAG, + "Can't get endpoint node associated with b interpretation " + "flag. Aborting generation."); + return; + } + + // Might fail if some attribute are not ready yet and it's ok, we'll try that later + create_setpoint_type_attributes(endpoint_node); +} + static void zwave_command_class_thermostat_setpoint_on_version_attribute_update( attribute_store_node_t updated_node, attribute_store_change_t change) { @@ -193,36 +392,32 @@ static void zwave_command_class_thermostat_setpoint_on_version_attribute_update( // Let the rest of the command class perform the job. attribute_store_type_t supported_sensor_types[] = {ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)}; + attribute_store_add_if_missing(endpoint_node, supported_sensor_types, COUNT_OF(supported_sensor_types)); -} -/////////////////////////////////////////////////////////////////////////////// -// Attribute creation functions -/////////////////////////////////////////////////////////////////////////////// -static attribute_store_node_t - zwave_command_class_thermostat_setpoint_create_type( - attribute_store_node_t endpoint_node, uint8_t type) -{ - // Do we already have the node ? - attribute_store_node_t type_node = attribute_store_emplace(endpoint_node, - ATTRIBUTE(TYPE), - &type, - sizeof(type)); - - // Add the six other nodes under the type. - const attribute_store_type_t additional_nodes[] - = {ATTRIBUTE(VALUE), - ATTRIBUTE(VALUE_SCALE), - ATTRIBUTE(MIN_VALUE), - ATTRIBUTE(MIN_VALUE_SCALE), - ATTRIBUTE(MAX_VALUE), - ATTRIBUTE(MAX_VALUE_SCALE)}; - attribute_store_add_if_missing(type_node, - additional_nodes, - COUNT_OF(additional_nodes)); - return type_node; + // Create B interpretation flag and set default value + // It is located underneath ATTRIBUTE(SUPPORTED_SETPOINT_TYPES) + attribute_store_type_t supported_node + = attribute_store_get_node_child_by_type( + endpoint_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES), + 0); + + // Create the attribute and set default value if not already there + if (attribute_store_get_node_child_by_type(supported_node, + ATTRIBUTE(USE_B_INTERPRETATION), + 0) + == ATTRIBUTE_STORE_INVALID_NODE) { + // Default value for b interpretation (off) + uint8_t default_b_interpretation_value = 0; + // Will create the node if not existant + attribute_store_set_child_reported(supported_node, + ATTRIBUTE(USE_B_INTERPRETATION), + &default_b_interpretation_value, + sizeof(default_b_interpretation_value)); + } } /////////////////////////////////////////////////////////////////////////////// @@ -468,29 +663,27 @@ static sl_status_t endpoint_node, ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); - attribute_store_set_reported(supported_bitmask_node, - &frame_data[SUPPORTED_REPORT_BITMASK_INDEX], - frame_length - SUPPORTED_REPORT_BITMASK_INDEX); - - // Now create a type attribute for each supported type - for (uint8_t byte = 0; byte < frame_length - SUPPORTED_REPORT_BITMASK_INDEX; - byte++) { - for (uint8_t bit = 0; bit < 8; bit++) { - if (frame_data[SUPPORTED_REPORT_BITMASK_INDEX + byte] & (1 << bit)) { - // This type is supported. - uint8_t type = byte * 8 + bit; - if (type > 2) { - // Use Bit Interpretation A see Thermostat Setpoint Command Class in - // Z-Wave Application Command Class Specification - type += 4; - } - zwave_command_class_thermostat_setpoint_create_type(endpoint_node, - type); - } - } + uint32_t supported_setpoint_mode_bits = 0x0000; + uint8_t bitmask_length = frame_length - 2; + + // Since we are using uint32_t we can't have more that 4 bit mask + if (bitmask_length > 4) { + sl_log_error( + LOG_TAG, + "Supported Thermostat SetPoint type Bit Mask length is not supported\n"); + return SL_STATUS_FAIL; } - return SL_STATUS_OK; + for (int i = bitmask_length; i > 0; i--) { + supported_setpoint_mode_bits + = (supported_setpoint_mode_bits << 8) | frame_data[1 + i]; + } + + attribute_store_set_reported(supported_bitmask_node, + &supported_setpoint_mode_bits, + sizeof(supported_setpoint_mode_bits)); + + return create_setpoint_type_attributes(endpoint_node); } static sl_status_t @@ -521,8 +714,9 @@ static sl_status_t if (type_node == ATTRIBUTE_STORE_INVALID_NODE) { // Hmm it seems that it's a type that we don't know of. // Let's be nice and create it. - zwave_command_class_thermostat_setpoint_create_type(endpoint_node, - received_setpoint_type); + zwave_command_class_thermostat_setpoint_create_type( + endpoint_node, + received_setpoint_type); // Force version 3 to have max compatibility } // Save the Min value @@ -674,8 +868,11 @@ sl_status_t zwave_command_class_thermostat_setpoint_init() // Listening for supporting nodes attribute_store_register_callback_by_type( - zwave_command_class_thermostat_setpoint_on_version_attribute_update, + &zwave_command_class_thermostat_setpoint_on_version_attribute_update, ATTRIBUTE(VERSION)); + attribute_store_register_callback_by_type( + &zwave_command_class_thermostat_setpoint_on_b_interpretation_attribute_update, + ATTRIBUTE(USE_B_INTERPRETATION)); // Register Thermostat Setpoint CC handler to the Z-Wave CC framework zwave_command_handler_t handler = {}; @@ -687,9 +884,8 @@ sl_status_t zwave_command_class_thermostat_setpoint_init() handler.manual_security_validation = false; handler.command_class_name = "Thermostat Setpoint"; handler.comments = "Partial Control:
" - "1. No discovery of ambiguous types in v1-v2
" - "2. Only a few setpoints can be configured.
" - "3. Precision/size fields in the set are determined
" + "1. Only a few setpoints can be configured.
" + "2. Precision/size fields in the set are determined
" "automatically by the controller. "; zwave_command_handler_register_handler(handler); diff --git a/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c index 1042aee5a1..4e874046a8 100644 --- a/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c +++ b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c @@ -96,6 +96,206 @@ static sl_status_t zwave_command_handler_register_handler_stub( return SL_STATUS_OK; } +//////////////////////////////////////////////////////////////////////////// +// UTILS +//////////////////////////////////////////////////////////////////////////// +// Set version and thus initialize the attribute tree +void set_version(zwave_cc_version_t version) +{ + attribute_store_node_t version_node + = attribute_store_add_node(ATTRIBUTE(VERSION), endpoint_id_node); + + attribute_store_set_reported(version_node, &version, sizeof(version)); +} + +// Test if all the types in setpoint_mode_expected_types are present in the attribute tree +void helper_setpoint_type_validator(const uint8_t* setpoint_mode_expected_types, + uint8_t setpoint_mode_excepted_length) +{ + size_t attribute_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(TYPE)); + + TEST_ASSERT_EQUAL_MESSAGE( + setpoint_mode_excepted_length, + attribute_count, + "Incorrect supported thermostat setpoint mode NODE count"); + + for (int i = 0; i < setpoint_mode_excepted_length; i++) { + attribute_store_node_t fan_mode_supported_node + = attribute_store_get_node_child_by_type(endpoint_id_node, + ATTRIBUTE(TYPE), + i); + + uint8_t reported_type; + attribute_store_get_reported(fan_mode_supported_node, + &reported_type, + sizeof(reported_type)); + + TEST_ASSERT_EQUAL_MESSAGE( + setpoint_mode_expected_types[i], + reported_type, + "Incorrect reported thermostat setpoint type supported"); + } +} + +// happy_case : if true only accepted bit are sent +// if false fill all unused bit with 1 to see if they are ignored correctly +void helper_thermostat_setpoint_mode_supported_report( + zwave_cc_version_t version, bool happy_case, bool use_b_interpretation) +{ + set_version(version); + + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + TEST_ASSERT_NOT_NULL(thermostat_handler.control_handler); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + thermostat_handler.control_handler(&info, NULL, 0)); + + uint8_t supported_bit1 = 0b00000000; + uint8_t supported_bit2 = 0b00000000; + size_t setpoint_mode_excepted_length = 0; + uint8_t setpoint_mode_expected_types[16]; + + uint8_t use_b_interpretation_value = use_b_interpretation; + + attribute_store_node_t thermostat_setpoint_bitmask_node + = attribute_store_get_node_child_by_type( + endpoint_id_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES), + 0); + + sl_status_t result + = attribute_store_set_child_reported(thermostat_setpoint_bitmask_node, + ATTRIBUTE(USE_B_INTERPRETATION), + &use_b_interpretation_value, + sizeof(use_b_interpretation_value)); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + result, + "Can't set b interpretation flag"); + + switch (version) { + case 1: + if (use_b_interpretation) { + supported_bit1 = happy_case ? 0b10000110 : 0b11111111; + supported_bit2 = happy_case ? 0b00000111 : 0b11111111; + } else { + supported_bit1 = happy_case ? 0b01111110 : 0b11111111; + supported_bit2 = happy_case ? 0b00000000 : 0b11111111; + } + setpoint_mode_excepted_length = 6; + setpoint_mode_expected_types[0] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_HEATING_1; + setpoint_mode_expected_types[1] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1; + setpoint_mode_expected_types[2] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_FURNACE_V3; + setpoint_mode_expected_types[3] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_DRY_AIR_V3; + setpoint_mode_expected_types[4] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_MOIST_AIR_V3; + setpoint_mode_expected_types[5] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AUTO_CHANGEOVER_V3; + break; + + case 2: + if (use_b_interpretation) { + supported_bit1 = happy_case ? 0b10000110 : 0b11111111; + supported_bit2 = happy_case ? 0b00111111 : 0b11111111; + } else { + supported_bit1 = happy_case ? 0b11111110 : 0b11111111; + supported_bit2 = happy_case ? 0b00000011 : 0b11111111; + } + setpoint_mode_excepted_length = 9; + setpoint_mode_expected_types[0] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_HEATING_1_V2; + setpoint_mode_expected_types[1] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1_V2; + setpoint_mode_expected_types[2] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_FURNACE_V2; + setpoint_mode_expected_types[3] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_DRY_AIR_V2; + setpoint_mode_expected_types[4] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_MOIST_AIR_V2; + setpoint_mode_expected_types[5] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AUTO_CHANGEOVER_V2; + setpoint_mode_expected_types[6] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_ENERGY_SAVE_HEATING_V2; + setpoint_mode_expected_types[7] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_ENERGY_SAVE_COOLING_V2; + setpoint_mode_expected_types[8] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AWAY_HEATING_V2; + break; + case 3: + if (use_b_interpretation) { + supported_bit1 = happy_case ? 0b10000110 : 0b11111111; + supported_bit2 = happy_case ? 0b11111111 : 0b11111111; + } else { + supported_bit1 = happy_case ? 0b11111110 : 0b11111111; + supported_bit2 = happy_case ? 0b00001111 : 0b11111111; + } + setpoint_mode_excepted_length = 11; + setpoint_mode_expected_types[0] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_HEATING_1_V3; + setpoint_mode_expected_types[1] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1_V3; + setpoint_mode_expected_types[2] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_FURNACE_V3; + setpoint_mode_expected_types[3] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_DRY_AIR_V3; + setpoint_mode_expected_types[4] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_MOIST_AIR_V3; + setpoint_mode_expected_types[5] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AUTO_CHANGEOVER_V3; + setpoint_mode_expected_types[6] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_ENERGY_SAVE_HEATING_V3; + setpoint_mode_expected_types[7] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_ENERGY_SAVE_COOLING_V3; + setpoint_mode_expected_types[8] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AWAY_HEATING_V3; + setpoint_mode_expected_types[9] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AWAY_COOLING_V3; + setpoint_mode_expected_types[10] + = THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_FULL_POWER_V3; + break; + } + + const uint8_t frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT, + THERMOSTAT_SETPOINT_SUPPORTED_REPORT, + supported_bit1, + supported_bit2}; + + TEST_ASSERT_EQUAL( + SL_STATUS_OK, + thermostat_handler.control_handler(&info, frame, sizeof(frame))); + + + + TEST_ASSERT_NOT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, + thermostat_setpoint_bitmask_node); + + uint32_t reported_bitmask = 0; + attribute_store_get_reported(thermostat_setpoint_bitmask_node, + &reported_bitmask, + sizeof(reported_bitmask)); + uint32_t expected_bitmask = (supported_bit2 << 8) | supported_bit1; + TEST_ASSERT_EQUAL_MESSAGE( + expected_bitmask, + reported_bitmask, + "Incorrect supported thermostat setpoint mode bitmask"); + + helper_setpoint_type_validator(setpoint_mode_expected_types, + setpoint_mode_excepted_length); +} + +//////////////////////////////////////////////////////////////////////////// +// Setup +//////////////////////////////////////////////////////////////////////////// + /// Setup the test suite (called once before all test_xxx functions are called) void suiteSetUp() { @@ -282,4 +482,211 @@ void test_zwave_command_class_thermostat_setpoint_set_v3_clip_upper_bound() TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, received_frame, sizeof(expected_frame)); +} + +void test_thermostat_setpoint_mode_supported_report_v1_happy_case() +{ + helper_thermostat_setpoint_mode_supported_report(THERMOSTAT_SETPOINT_VERSION, + true, + false); +} +void test_thermostat_setpoint_mode_supported_report_v2_happy_case() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V2, + true, + false); +} +void test_thermostat_setpoint_mode_supported_report_v3_happy_case() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V3, + true, + false); +} + +void test_thermostat_setpoint_mode_supported_report_v1_b_interpretation_happy_case() +{ + helper_thermostat_setpoint_mode_supported_report(THERMOSTAT_SETPOINT_VERSION, + true, + true); +} +void test_thermostat_setpoint_mode_supported_report_v2_b_interpretation_happy_case() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V2, + true, + true); +} +void test_thermostat_setpoint_mode_supported_report_v3_b_interpretation_happy_case() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V3, + true, + true); +} + +void test_thermostat_setpoint_mode_supported_report_v1_bit_mismatch() +{ + helper_thermostat_setpoint_mode_supported_report(THERMOSTAT_SETPOINT_VERSION, + false, + false); +} +void test_thermostat_setpoint_mode_supported_report_v2_bit_mismatch() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V2, + false, + false); +} +void test_thermostat_setpoint_mode_supported_report_v3_bit_mismatch() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V3, + false, + false); +} + +void test_thermostat_setpoint_mode_supported_report_v1_b_interpretation_bit_mismatch() +{ + helper_thermostat_setpoint_mode_supported_report(THERMOSTAT_SETPOINT_VERSION, + false, + true); +} +void test_thermostat_setpoint_mode_supported_report_v2_b_interpretation_bit_mismatch() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V2, + false, + true); +} + +void test_thermostat_setpoint_mode_supported_report_v3_b_interpretation_bit_mismatch() +{ + helper_thermostat_setpoint_mode_supported_report( + THERMOSTAT_SETPOINT_VERSION_V3, + false, + true); +} + +void test_thermostat_setpoint_mode_change_interpretation() +{ + set_version(2); + + attribute_store_node_t supported_setpoint_types_node + = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); + attribute_store_node_t b_interpretation_node + = attribute_store_get_first_child_by_type(supported_setpoint_types_node, + ATTRIBUTE(USE_B_INTERPRETATION)); + + uint32_t setpoint_bitmask = 0b11111110; + sl_status_t status = attribute_store_set_reported(supported_setpoint_types_node, + &setpoint_bitmask, + sizeof(setpoint_bitmask)); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, status, "Should be able to set supported setpoint types"); + + + // Check with B interpretation (flag = on) + uint8_t flag = 1; + // This should make the attribute store to update based on the new interpretation + status = attribute_store_set_reported(b_interpretation_node, + &flag, + sizeof(flag)); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, status, "Should be able to set b interpretation flag to 1"); + + const uint8_t b_interpretation_expected_types[] = { + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_HEATING_1, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_FURNACE_V2 + }; + helper_setpoint_type_validator(b_interpretation_expected_types, + sizeof(b_interpretation_expected_types)); + + // Check with A interpretation (flag = off) + flag = 0; + // This should make the attribute store to update based on the new interpretation + status = attribute_store_set_reported(b_interpretation_node, + &flag, + sizeof(flag)); + + const uint8_t a_interpretation_expected_types[] = { + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_HEATING_1, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_COOLING_1, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_FURNACE_V3, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_DRY_AIR_V3, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_MOIST_AIR_V3, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_AUTO_CHANGEOVER_V3, + THERMOSTAT_SETPOINT_REPORT_SETPOINT_TYPE_ENERGY_SAVE_HEATING_V3 + }; + helper_setpoint_type_validator(a_interpretation_expected_types, + sizeof(a_interpretation_expected_types)); + +} + +void test_thermostat_setpoint_mode_attribute_creation() { + attribute_store_node_t supported_setpoint_types_node = + attribute_store_get_first_child_by_type(endpoint_id_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); + + attribute_store_node_t b_interpretation_node + = attribute_store_get_first_child_by_type(supported_setpoint_types_node, + ATTRIBUTE(USE_B_INTERPRETATION)); + + TEST_ASSERT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + b_interpretation_node, + "USE_B_INTERPRETATION attribute should not exist yet"); + TEST_ASSERT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + supported_setpoint_types_node, + "SUPPORTED_SETPOINT_TYPES attribute should not exist yet"); + + // Trigger creation version attributes + set_version(1); + + supported_setpoint_types_node = + attribute_store_get_first_child_by_type(endpoint_id_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); + + b_interpretation_node + = attribute_store_get_first_child_by_type(supported_setpoint_types_node, + ATTRIBUTE(USE_B_INTERPRETATION)); + + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + b_interpretation_node, + "USE_B_INTERPRETATION attribute should exist by now"); + + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + supported_setpoint_types_node, + "SUPPORTED_SETPOINT_TYPES attribute should exist by now"); + + + uint8_t b_interpretation_value = 1; + attribute_store_get_reported(b_interpretation_node, + &b_interpretation_value, + sizeof(b_interpretation_value)); + TEST_ASSERT_EQUAL_MESSAGE(0, + b_interpretation_value, + "Default value of USE_B_INTERPRETATION should be 0"); + + b_interpretation_value = 1; + sl_status_t status + = attribute_store_set_reported(b_interpretation_node, + &b_interpretation_value, + sizeof(b_interpretation_value)); + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + status, + "Should be able to modify b interpretation_node to 1"); + + // Trigger version change to see if flag value is not reset back to 0 + set_version(2); + + attribute_store_get_reported(b_interpretation_node, + &b_interpretation_value, + sizeof(b_interpretation_value)); + TEST_ASSERT_EQUAL_MESSAGE(1, + b_interpretation_value, + "Current value of USE_B_INTERPRETATION should be 1"); } \ No newline at end of file