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 78ca2909e..d45d4ebe5 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,39 @@ 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)) +///////////////////////////////////////////////// +// Thermostat Operating State Command Class +/// zwave_cc_version_t +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_VERSION, + ZWAVE_CC_VERSION_ATTRIBUTE(COMMAND_CLASS_THERMOSTAT_OPERATING_STATE)) +/// Current State +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x02)) +/// Log supported count (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x03)) +/// Log supported (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x04)) +/// Log count (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x05)) +/// Log State (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x06)) +/// Log Usage Today (hours) (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_HOURS, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x07)) +/// Log Usage Today (min) (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_MIN, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x08)) +/// Log Usage Yesterday (hours) (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_HOURS, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x09)) +/// Log Usage Yesterday (min) (v2) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_MIN, + ((COMMAND_CLASS_THERMOSTAT_OPERATING_STATE << 8) | 0x0A)) + ///////////////////////////////////////////////// // Wakeup command class DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_WAKE_UP_VERSION, diff --git a/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_operating_state_types.h b/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_operating_state_types.h new file mode 100644 index 000000000..8cb6dba87 --- /dev/null +++ b/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_operating_state_types.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +/** + * @defgroup zwave_command_class_thermostat_operating_state_types Type definitions for attribute storage of the Sound Switch Command Class + * @ingroup zpc_attribute_store_command_classes_types + * @brief Type definitions for the Sound Switch Command Class. + * + * @{ + */ + +#ifndef ZWAVE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_TYPES_H +#define ZWAVE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_TYPES_H + +#include + +///> Operating State. uint8_t +typedef uint8_t thermostat_operating_state_t; +///> Usage representation (v2). uint8_t +typedef uint8_t thermostat_operating_state_usage_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif //ZWAVE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_TYPES_H +/** @} end zwave_command_class_thermostat_operating_state_types */ 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 774ba2882..1585c82d3 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 @@ -302,6 +302,22 @@ 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}, + + ///////////////////////////////////////////////////////////////////// + // Thermostat Operating State Command Class attributes + ///////////////////////////////////////////////////////////////////// + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_VERSION, "Thermostat Operating State Version", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE, "Thermostat Operating State", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + // V2 + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK, "Thermostat Operating State Log Supported Count", ATTRIBUTE_ENDPOINT_ID, U32_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED, "Thermostat Operating State Log Supported", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, "Thermostat Operating State Log", ATTRIBUTE_ENDPOINT_ID, U32_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, "Thermostat Operating State Log State", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_HOURS, "Thermostat Operating State Log Usage Today (Hours)", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_MIN, "Thermostat Operating State Log Usage Today (Min)", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_HOURS, "Thermostat Operating State Log Usage Yesterday (Hours)", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_MIN, "Thermostat Operating State Log Usage Yesterday (Hours)", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, U8_STORAGE_TYPE}, + ///////////////////////////////////////////////////////////////////// // Supervision Command Class attributes ///////////////////////////////////////////////////////////////////// diff --git a/applications/zpc/components/zwave_command_classes/CMakeLists.txt b/applications/zpc/components/zwave_command_classes/CMakeLists.txt index 90bec52fe..0172ec1c1 100644 --- a/applications/zpc/components/zwave_command_classes/CMakeLists.txt +++ b/applications/zpc/components/zwave_command_classes/CMakeLists.txt @@ -46,6 +46,7 @@ add_library( src/zwave_command_class_switch_multilevel.c src/zwave_command_class_thermostat_mode.c src/zwave_command_class_thermostat_setpoint.c + src/zwave_command_class_thermostat_operating_state.c src/zwave_command_class_time.c src/zwave_command_class_user_code.c src/zwave_command_class_version.c diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.c b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.c new file mode 100644 index 000000000..0d5fd72d9 --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.c @@ -0,0 +1,452 @@ +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +// System +#include + +#include "zwave_command_class_thermostat_operating_state.h" +#include "zwave_command_class_thermostat_operating_state_types.h" +#include "zwave_command_classes_utils.h" +#include "ZW_classcmd.h" + +// Includes from other ZPC Components +#include "zwave_command_class_indices.h" +#include "zwave_command_handler.h" +#include "zwave_command_class_version_types.h" +#include "zpc_attribute_store_network_helper.h" +#include "attribute_store_defined_attribute_types.h" + +// Unify +#include "attribute_resolver.h" +#include "attribute_store.h" +#include "attribute_store_helper.h" +#include "sl_log.h" + +#define LOG_TAG "zwave_command_class_thermostat_operating_state" + +#define MAX_SUPPORTED_OPERATING_STATE 16 + +///////////////////////////////////////////////////////////////////////////// +// Version & Attribute Creation +///////////////////////////////////////////////////////////////////////////// +static void + zwave_command_class_thermostat_operating_state_on_version_attribute_update( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + if (change == ATTRIBUTE_DELETED) { + return; + } + + zwave_cc_version_t version = 0; + attribute_store_get_reported(updated_node, &version, sizeof(version)); + + if (version == 0) { + return; + } + + attribute_store_node_t endpoint_node + = attribute_store_get_first_parent_with_type(updated_node, + ATTRIBUTE_ENDPOINT_ID); + + // The order of the attribute matter since it defines the order of the + // Z-Wave get command order. + const attribute_store_type_t attributes[] + = {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE}; + + attribute_store_add_if_missing(endpoint_node, + attributes, + COUNT_OF(attributes)); + + if (version == 2) { + const attribute_store_type_t attributes_v2[] = { + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK}; + + attribute_store_add_if_missing(endpoint_node, + attributes_v2, + COUNT_OF(attributes_v2)); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Thermostat Operating State Get/Report +///////////////////////////////////////////////////////////////////////////// + +static sl_status_t zwave_command_class_thermostat_operating_state_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + (void)node; // unused. + ZW_THERMOSTAT_OPERATING_STATE_GET_FRAME *get_frame + = (ZW_THERMOSTAT_OPERATING_STATE_GET_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_OPERATING_STATE; + get_frame->cmd = THERMOSTAT_OPERATING_STATE_GET; + *frame_length = sizeof(ZW_THERMOSTAT_OPERATING_STATE_GET_FRAME); + return SL_STATUS_OK; +} + +sl_status_t zwave_command_class_thermostat_operating_state_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length < 3) { + return SL_STATUS_FAIL; + } + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + thermostat_operating_state_t operating_state = frame_data[2]; + + attribute_store_set_child_reported( + endpoint_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE, + &operating_state, + sizeof(operating_state)); + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Thermostat Operating State Logging Supported Get/Report +///////////////////////////////////////////////////////////////////////////// +static sl_status_t + zwave_command_class_thermostat_operating_state_logging_supported_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + (void)node; // unused. + ZW_THERMOSTAT_OPERATING_STATE_LOGGING_SUPPORTED_GET_V2_FRAME *get_frame + = (ZW_THERMOSTAT_OPERATING_STATE_LOGGING_SUPPORTED_GET_V2_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_OPERATING_STATE; + get_frame->cmd = THERMOSTAT_OPERATING_STATE_LOGGING_SUPPORTED_GET_V2; + *frame_length + = sizeof(ZW_THERMOSTAT_OPERATING_STATE_LOGGING_SUPPORTED_GET_V2_FRAME); + return SL_STATUS_OK; +} + +sl_status_t + zwave_command_class_thermostat_operating_state_logging_supported_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length < 3) { + return SL_STATUS_NOT_SUPPORTED; + } + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + uint32_t notification_types_bits = 0x0000; + uint32_t notification_type_mask = 0x0000; + uint8_t number_of_supported_states = 0; + thermostat_operating_state_t supported_states[MAX_SUPPORTED_OPERATING_STATE]; + uint8_t number_of_bit_masks = frame_length - 2; + + // Since we are using uint32_t we can't have more that 4 bit mask + if (number_of_bit_masks > 4) { + sl_log_error(LOG_TAG, + "Supported Fan mode type Bit Mask length is not supported\n"); + return SL_STATUS_NOT_SUPPORTED; + } + + for (int i = number_of_bit_masks; i > 0; i--) { + notification_types_bits + = (notification_types_bits << 8) | frame_data[1 + i]; + } + + for (size_t i = 0; i <= MAX_SUPPORTED_OPERATING_STATE; i++) { + notification_type_mask = 1 << i; + notification_type_mask &= notification_types_bits; + if (notification_type_mask) { + switch (notification_type_mask) { + case 0b1: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_HEATING; + break; + case 0b10: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_COOLING; + break; + case 0b100: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_FAN_ONLY; + break; + case 0b1000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_PENDING_HEAT; + break; + case 0b10000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_PENDING_COOL; + break; + case 0b100000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_VENT_ECONOMIZER; + break; + case 0b1000000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_AUX_HEATING_V2; + break; + case 0b10000000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_HEATING_V2; + break; + case 0b100000000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_COOLING_V2; + break; + case 0b1000000000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_AUX_HEAT_V2; + break; + case 0b10000000000: + supported_states[number_of_supported_states] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_3RD_STAGE_AUX_HEAT_V2; + break; + default: + continue; + } + number_of_supported_states++; + } + } + + attribute_store_node_t supported_logging_operating_state_bitmask_node + = attribute_store_get_node_child_by_type( + endpoint_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK, + 0); + + if (supported_logging_operating_state_bitmask_node + == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_error(LOG_TAG, + "Can't find supported supported logging state bitmask node."); + return SL_STATUS_NOT_SUPPORTED; + } + + // Clear previously reported values + attribute_store_delete_all_children( + supported_logging_operating_state_bitmask_node); + + // And Set the new ones + for (uint8_t i = 0; i < number_of_supported_states; i++) { + attribute_store_node_t current_supported_fan_mode_node + = attribute_store_add_node( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED, + supported_logging_operating_state_bitmask_node); + + attribute_store_set_node_attribute_value(current_supported_fan_mode_node, + REPORTED_ATTRIBUTE, + &supported_states[i], + sizeof(supported_states[i])); + } + + sl_status_t result = attribute_store_set_reported( + supported_logging_operating_state_bitmask_node, + ¬ification_types_bits, + sizeof(notification_types_bits)); + + if (result != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't set supported logging operating state bitmask."); + } + + attribute_store_node_t log_node = attribute_store_add_node( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, + endpoint_node); + + result = attribute_store_set_desired(log_node, + ¬ification_types_bits, + sizeof(notification_types_bits)); + + if (result != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, "Can't set log desired states."); + } + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Thermostat Operating State Logging Get/Report +///////////////////////////////////////////////////////////////////////////// +static sl_status_t zwave_command_class_thermostat_operating_state_logging_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + // Extract bitmap from value + uint32_t full_bitmask = 0; + sl_status_t result + = attribute_store_get_desired(node, &full_bitmask, sizeof(full_bitmask)); + + if (result != SL_STATUS_OK) { + sl_log_warning( + LOG_TAG, + "Unable to get bitmask for THERMOSTAT_OPERATING_STATE_LOGGING_GET"); + return SL_STATUS_IS_WAITING; + } + + // Convert 32 bit mask into chunk of 8 bit masks + const uint8_t MAX_BITMASKS = 2; + uint8_t bitmasks[MAX_BITMASKS]; + for (int i = 0; i < MAX_BITMASKS; i++) { + uint32_t mask = ((1 << 8) - 1) << 8 * i; + bitmasks[i] = (full_bitmask & mask) >> 8 * i; + } + + ZW_THERMOSTAT_OPERATING_STATE_LOGGING_GET_2BYTE_V2_FRAME *get_frame + = (ZW_THERMOSTAT_OPERATING_STATE_LOGGING_GET_2BYTE_V2_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_OPERATING_STATE; + get_frame->cmd = THERMOSTAT_OPERATING_STATE_LOGGING_GET_V2; + get_frame->bitMask1 = bitmasks[0]; + get_frame->bitMask2 = bitmasks[1]; + *frame_length + = sizeof(ZW_THERMOSTAT_OPERATING_STATE_LOGGING_GET_2BYTE_V2_FRAME); + return SL_STATUS_OK; +} + +attribute_store_node_t helper_add_log_node(attribute_store_node_t log_node, + attribute_store_type_t node_type, + uint8_t value) +{ + attribute_store_node_t new_node + = attribute_store_add_node(node_type, log_node); + + attribute_store_set_reported(new_node, &value, sizeof(value)); + + return new_node; +} + +sl_status_t zwave_command_class_thermostat_operating_state_logging_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length < 3) { + return SL_STATUS_NOT_SUPPORTED; + } + + // Just retrieve the data and save it. + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + attribute_store_node_t log_node = attribute_store_get_first_child_by_type( + endpoint_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK); + + uint8_t report_count = frame_data[2]; + + // Clear previously reported values + attribute_store_delete_all_children(log_node); + + const uint8_t REPORT_FIELD_COUNT = 4; + + for (uint8_t i = 0; i < report_count; i++) { + thermostat_operating_state_t state + = frame_data[3 + (REPORT_FIELD_COUNT * i) + i] + & THERMOSTAT_OPERATING_STATE_REPORT_PROPERTIES1_OPERATING_STATE_MASK_V2; + + attribute_store_node_t state_node = helper_add_log_node( + log_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_STATE, + state); + + const attribute_store_type_t attribute_store_types[] = { + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_HOURS, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_MIN, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_HOURS, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_MIN}; + + for (int j = 0; j < REPORT_FIELD_COUNT; j++) { + const uint8_t base_index = 4 + j; + const uint8_t offset = (REPORT_FIELD_COUNT * i) + i; + thermostat_operating_state_usage_t current_value + = frame_data[base_index + offset]; + + helper_add_log_node(state_node, attribute_store_types[j], current_value); + } + } + + attribute_store_set_reported_as_desired(log_node); + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Control handler (report) +///////////////////////////////////////////////////////////////////////////// +sl_status_t zwave_command_class_thermostat_operating_state_control_handler( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length <= COMMAND_INDEX) { + return SL_STATUS_NOT_SUPPORTED; + } + + switch (frame_data[COMMAND_INDEX]) { + case THERMOSTAT_OPERATING_STATE_REPORT: + return zwave_command_class_thermostat_operating_state_handle_report( + connection_info, + frame_data, + frame_length); + case THERMOSTAT_OPERATING_LOGGING_SUPPORTED_REPORT_V2: + return zwave_command_class_thermostat_operating_state_logging_supported_report( + connection_info, + frame_data, + frame_length); + case THERMOSTAT_OPERATING_STATE_LOGGING_REPORT_V2: + return zwave_command_class_thermostat_operating_state_logging_report( + connection_info, + frame_data, + frame_length); + + default: + return SL_STATUS_NOT_SUPPORTED; + } + + return SL_STATUS_FAIL; +} + +///////////////////////////////////////////////////////////////////////////// +// Init +///////////////////////////////////////////////////////////////////////////// +sl_status_t zwave_command_class_thermostat_operating_state_init() +{ + attribute_store_register_callback_by_type( + &zwave_command_class_thermostat_operating_state_on_version_attribute_update, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_VERSION); + + attribute_resolver_register_rule( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE, + NULL, + &zwave_command_class_thermostat_operating_state_get); + + attribute_resolver_register_rule( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK, + NULL, + &zwave_command_class_thermostat_operating_state_logging_supported_get); + + attribute_resolver_register_rule( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, + NULL, + &zwave_command_class_thermostat_operating_state_logging_get); + + zwave_command_handler_t handler = {}; + handler.support_handler = NULL; + handler.control_handler + = zwave_command_class_thermostat_operating_state_control_handler; + handler.minimal_scheme = ZWAVE_CONTROLLER_ENCAPSULATION_NONE; + handler.manual_security_validation = false; + handler.command_class = COMMAND_CLASS_THERMOSTAT_OPERATING_STATE; + handler.version = THERMOSTAT_OPERATING_STATE_VERSION; + handler.command_class_name = "Thermostat Operating State"; + handler.comments = "Experimental"; + + return zwave_command_handler_register_handler(handler); +} \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.h b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.h new file mode 100644 index 000000000..5cf1568a0 --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +/** + * @defgroup zwave_command_class_thermostat_operating_state + * @brief Thermostat Operating State Command Class handlers and control function + * + * This module implement some of the functions to control the + * Thermostat Operating State Command Class + * + * @{ + */ + +#ifndef ZWAVE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_H +#define ZWAVE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_H + +#include "sl_status.h" + +#ifdef __cplusplus +extern "C" { +#endif + +sl_status_t zwave_command_class_thermostat_operating_state_init(); + +#ifdef __cplusplus +} +#endif + +#endif //ZWAVE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_H +/** @} end zwave_command_class_thermostat_operating_state */ \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c b/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c index cac0a8c66..f07bc8f6a 100644 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c @@ -41,6 +41,7 @@ #include "zwave_command_class_switch_multilevel.h" #include "zwave_command_class_thermostat_mode.h" #include "zwave_command_class_thermostat_setpoint.h" +#include "zwave_command_class_thermostat_operating_state.h" #include "zwave_command_class_version.h" #include "zwave_command_class_wake_up.h" #include "zwave_command_class_time.h" @@ -109,6 +110,7 @@ sl_status_t zwave_command_classes_init() status |= zwave_command_class_switch_multilevel_init(); status |= zwave_command_class_thermostat_mode_init(); status |= zwave_command_class_thermostat_setpoint_init(); + status |= zwave_command_class_thermostat_operating_state_init(); status |= zwave_command_class_time_init(); status |= zwave_command_class_transport_service_init(); status |= zwave_command_class_user_code_init(); diff --git a/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt b/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt index 4d72dfb0e..5f2552a90 100644 --- a/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt +++ b/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt @@ -637,6 +637,21 @@ DEPENDS uic_dotdot_mqtt_mock ) +# Thermostat operating state test +target_add_unittest( + zwave_command_classes + NAME + zwave_command_class_thermostat_operating_state_test + SOURCES + zwave_command_class_thermostat_operating_state_test.c + DEPENDS + zpc_attribute_store_test_helper + zwave_controller + zwave_command_handler_mock + uic_attribute_resolver_mock + zpc_attribute_resolver_mock + uic_dotdot_mqtt_mock) + # Tests for generated command classes set(GEN_TEST_INCLUDES "${CMAKE_SOURCE_DIR}/applications/zpc/components/zwave_controller/include" diff --git a/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_operating_state_test.c b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_operating_state_test.c new file mode 100644 index 000000000..66922936d --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_operating_state_test.c @@ -0,0 +1,580 @@ +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +#include "zwave_command_class_thermostat_operating_state.h" +#include "zwave_command_class_thermostat_operating_state_types.h" +#include "zwave_command_classes_utils.h" +#include "unity.h" + +// Generic includes +#include + +// Includes from other components +#include "datastore.h" +#include "attribute_store.h" +#include "attribute_store_helper.h" +#include "attribute_store_fixt.h" +#include "zpc_attribute_store_type_registration.h" + +// Interface includes +#include "attribute_store_defined_attribute_types.h" +#include "ZW_classcmd.h" +#include "zwave_utils.h" +#include "zwave_controller_types.h" + +// Test helpers +#include "zpc_attribute_store_test_helper.h" + +// Mock includes +#include "attribute_resolver_mock.h" +#include "zpc_attribute_resolver_mock.h" +#include "zwave_command_handler_mock.h" +#include "dotdot_mqtt_mock.h" +#include "dotdot_mqtt_generated_commands_mock.h" + +static zwave_command_handler_t handler = {}; + +static attribute_resolver_function_t operating_state_get = NULL; +static attribute_resolver_function_t logging_supported_get = NULL; +static attribute_resolver_function_t logging_get = NULL; + +// Buffer for frame +static uint8_t received_frame[255] = {}; +static uint16_t received_frame_size = 0; + +// Stub functions +static sl_status_t + attribute_resolver_register_rule_stub(attribute_store_type_t node_type, + attribute_resolver_function_t set_func, + attribute_resolver_function_t get_func, + int cmock_num_calls) +{ + if (node_type + == ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE) { + TEST_ASSERT_NULL(set_func); + TEST_ASSERT_NOT_NULL(get_func); + operating_state_get = get_func; + } else if ( + node_type + == ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK) { + TEST_ASSERT_NULL(set_func); + TEST_ASSERT_NOT_NULL(get_func); + logging_supported_get = get_func; + } else if ( + node_type + == ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK) { + TEST_ASSERT_NULL(set_func); + TEST_ASSERT_NOT_NULL(get_func); + logging_get = get_func; + } + + return SL_STATUS_OK; +} + +static sl_status_t zwave_command_handler_register_handler_stub( + zwave_command_handler_t new_command_class_handler, int cmock_num_calls) +{ + handler = new_command_class_handler; + + TEST_ASSERT_EQUAL(ZWAVE_CONTROLLER_ENCAPSULATION_NONE, + handler.minimal_scheme); + TEST_ASSERT_EQUAL(COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + handler.command_class); + TEST_ASSERT_EQUAL(THERMOSTAT_OPERATING_STATE_VERSION, handler.version); + TEST_ASSERT_NOT_NULL(handler.control_handler); + TEST_ASSERT_NULL(handler.support_handler); + TEST_ASSERT_FALSE(handler.manual_security_validation); + + return SL_STATUS_OK; +} + +/// Setup the test suite (called once before all test_xxx functions are called) +void suiteSetUp() +{ + datastore_init(":memory:"); + attribute_store_init(); + zpc_attribute_store_register_known_attribute_types(); +} + +/// Teardown the test suite (called once after all test_xxx functions are called) +int suiteTearDown(int num_failures) +{ + attribute_store_teardown(); + datastore_teardown(); + return num_failures; +} + +/// Called before each and every test +void setUp() +{ + zpc_attribute_store_test_helper_create_network(); + + // Unset previous definition get/set functions + operating_state_get = NULL; + logging_supported_get = NULL; + logging_get = NULL; + + memset(received_frame, 0, sizeof(received_frame)); + received_frame_size = 0; + // Unset previous definition of handler + memset(&handler, 0, sizeof(zwave_command_handler_t)); + + // Resolution functions + attribute_resolver_register_rule_Stub(&attribute_resolver_register_rule_stub); + // Handler registration + zwave_command_handler_register_handler_Stub( + &zwave_command_handler_register_handler_stub); + // Call init + TEST_ASSERT_EQUAL(SL_STATUS_OK, + zwave_command_class_thermostat_operating_state_init()); +} + +/// Called after each and every test +void tearDown() {} + +//////////////////////////////////////////////////////////////////////////// +// 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_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_VERSION, + endpoint_id_node); + + attribute_store_set_reported(version_node, &version, sizeof(version)); +} + +//////////////////////////////////////////////////////////////////////////// +// Versioning +//////////////////////////////////////////////////////////////////////////// +void test_thermostat_operating_state_v1_happy_case() +{ + set_version(1); + + attribute_store_node_t state_node = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE); + + TEST_ASSERT_NOT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, state_node); + + attribute_store_node_t log_supported_node + = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK); + TEST_ASSERT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, log_supported_node); +} + +void test_thermostat_operating_state_v2_happy_case() +{ + set_version(2); + + attribute_store_node_t state_node = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE); + + TEST_ASSERT_NOT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, state_node); + + attribute_store_node_t log_supported_node + = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK); + TEST_ASSERT_NOT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, log_supported_node); +} + +//////////////////////////////////////////////////////////////////////////// +// Operating State Get/Report +//////////////////////////////////////////////////////////////////////////// +void test_thermostat_operating_state_get_happy_case() +{ + // Ask for a Get Command, should always be the same + TEST_ASSERT_NOT_NULL(operating_state_get); + operating_state_get(0, received_frame, &received_frame_size); + const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_STATE_GET}; + TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, + received_frame, + received_frame_size); +} + +void test_thermostat_operating_state_report_happy_case() +{ + 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(handler.control_handler); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, NULL, 0)); + + thermostat_operating_state_t operating_state + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_HEATING; + const uint8_t frame[] = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_STATE_REPORT, + operating_state}; + + TEST_ASSERT_EQUAL(SL_STATUS_OK, + handler.control_handler(&info, frame, sizeof(frame))); + + attribute_store_node_t operating_state_node + = attribute_store_get_node_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_CURRENT_STATE, + 0); + TEST_ASSERT_EQUAL(operating_state, + attribute_store_get_reported_number(operating_state_node)); +} + +//////////////////////////////////////////////////////////////////////////// +// Supported Log Get/Report +//////////////////////////////////////////////////////////////////////////// +void test_thermostat_operating_state_log_supported_get_happy_case() +{ + // Ask for a Get Command, should always be the same + TEST_ASSERT_NOT_NULL(operating_state_get); + logging_supported_get(0, received_frame, &received_frame_size); + const uint8_t expected_frame[] + = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_STATE_LOGGING_SUPPORTED_GET_V2}; + TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, + received_frame, + received_frame_size); +} + +void helper_thermostat_operating_state_log_supported_report_happy_case( + uint8_t test_case) +{ + printf("test_thermostat_operating_state_log_supported_report_happy_case test " + "#%d\n", + test_case); + + 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(handler.control_handler); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, NULL, 0)); + + uint8_t supported_log_count = 11; + uint8_t bitmask1 = 0b11111111; + uint8_t bitmask2 = 0b11111111; + thermostat_operating_state_t expecting_states[11]; + + switch (test_case) { + case 1: // All types + supported_log_count = 11; + bitmask1 = 0b11111111; + bitmask2 = 0b11111111; + expecting_states[0] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_HEATING; + expecting_states[1] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_COOLING; + expecting_states[2] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_FAN_ONLY; + expecting_states[3] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_PENDING_HEAT; + expecting_states[4] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_PENDING_COOL; + expecting_states[5] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_VENT_ECONOMIZER; + expecting_states[6] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_AUX_HEATING_V2; + expecting_states[7] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_HEATING_V2; + expecting_states[8] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_COOLING_V2; + expecting_states[9] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_AUX_HEAT_V2; + expecting_states[10] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_3RD_STAGE_AUX_HEAT_V2; + break; + + case 2: // Only bit 1 + supported_log_count = 2; + bitmask1 = 0b10000001; + bitmask2 = 0b00000000; + expecting_states[0] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_HEATING; + expecting_states[1] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_HEATING_V2; + break; + + case 3: // Only bit 2 + supported_log_count = 2; + bitmask1 = 0b00000000; + bitmask2 = 0b00000011; + expecting_states[0] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_COOLING_V2; + expecting_states[1] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_2ND_STAGE_AUX_HEAT_V2; + break; + + case 4: // Bit of both + supported_log_count = 4; + bitmask1 = 0b00101010; // 3 Here + bitmask2 = 0b11000100; // Only 1 here + expecting_states[0] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_COOLING; + expecting_states[1] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_PENDING_HEAT; + expecting_states[2] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_VENT_ECONOMIZER; + expecting_states[3] + = THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_3RD_STAGE_AUX_HEAT_V2; + break; + + default: + TEST_MESSAGE("Undefined test_case aborting"); + TEST_ABORT(); + } + + const uint8_t frame[] = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_LOGGING_SUPPORTED_REPORT_V2, + bitmask1, + bitmask2}; + + TEST_ASSERT_EQUAL(SL_STATUS_OK, + handler.control_handler(&info, frame, sizeof(frame))); + + attribute_store_node_t operating_state_node + = attribute_store_get_node_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED_BITMASK, + 0); + + TEST_ASSERT_EQUAL((bitmask2 << 8) + bitmask1, + attribute_store_get_reported_number(operating_state_node)); + + TEST_ASSERT_EQUAL(supported_log_count, + attribute_store_get_node_child_count(operating_state_node)); + + for (int i = 0; i < supported_log_count; i++) { + thermostat_operating_state_t expected_state = expecting_states[i]; + + attribute_store_node_t current_node + = attribute_store_get_node_child_by_type( + operating_state_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_SUPPORTED, + i); + + TEST_ASSERT_NOT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, current_node); + + thermostat_operating_state_t reported_state; + sl_status_t status = attribute_store_get_reported(current_node, + &reported_state, + sizeof(reported_state)); + + TEST_ASSERT_EQUAL(SL_STATUS_OK, status); + TEST_ASSERT_EQUAL(expected_state, reported_state); + } + + // Check if ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK is created once we have the supported states + attribute_store_node_t log_bitmask_node + = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK); + + TEST_ASSERT_NOT_EQUAL(ATTRIBUTE_STORE_INVALID_NODE, log_bitmask_node); +} + +void test_thermostat_operating_state_log_supported_report_happy_case() +{ + set_version(2); + for (int i = 1; i <= 4; i++) { + helper_thermostat_operating_state_log_supported_report_happy_case(i); + } +} + +void test_thermostat_operating_state_log_supported_report_versioning() +{ + 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(handler.control_handler); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, NULL, 0)); + + const uint8_t frame[] = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_LOGGING_SUPPORTED_REPORT_V2, + 0b0000000, + 0b0000000}; + // Undefined in v1 + set_version(1); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, frame, sizeof(frame))); + + set_version(2); + TEST_ASSERT_EQUAL(SL_STATUS_OK, + handler.control_handler(&info, frame, sizeof(frame))); +} + +//////////////////////////////////////////////////////////////////////////// +// Log Get/Report +//////////////////////////////////////////////////////////////////////////// + +void test_thermostat_operating_state_log_get_happy_case() +{ + // Ask for a Get Command, should always be the same + TEST_ASSERT_NOT_NULL(operating_state_get); + sl_status_t status = logging_get(0, received_frame, &received_frame_size); + + // No ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK yet + TEST_ASSERT_EQUAL(SL_STATUS_IS_WAITING, status); + + // We add it and test + attribute_store_node_t log_node = attribute_store_add_node( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, + endpoint_id_node); + + TEST_ASSERT_TRUE(attribute_store_node_exists(log_node)); + + uint32_t expected_bitmask = 0b0000000100000110; + attribute_store_set_desired(log_node, + &expected_bitmask, + sizeof(expected_bitmask)); + + status = logging_get(log_node, received_frame, &received_frame_size); + TEST_ASSERT_EQUAL(SL_STATUS_OK, status); + + const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_STATE_LOGGING_GET_V2, + 0b00000110, + 0b00000001}; + TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, + received_frame, + received_frame_size); +} + +void test_thermostat_operating_state_log_no_report_happy_case() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + attribute_store_add_node( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, + endpoint_id_node); + + TEST_ASSERT_NOT_NULL(handler.control_handler); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, NULL, 0)); + + const uint8_t frame[] = {COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_STATE_LOGGING_REPORT_V2, + 0}; + + TEST_ASSERT_EQUAL(SL_STATUS_OK, + handler.control_handler(&info, frame, sizeof(frame))); +} + +void test_thermostat_operating_state_log_report_happy_case() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + attribute_store_node_t log_node = attribute_store_add_node( + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_BITMASK, + endpoint_id_node); + + TEST_ASSERT_NOT_NULL(handler.control_handler); + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, NULL, 0)); + const uint8_t report_number = 2; + const uint8_t reports[] + = {THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_HEATING, + 11, + 12, + 13, + 14, + THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_VENT_ECONOMIZER, + 21, + 22, + 23, + 24}; + + const uint8_t frame[] = { + COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, + THERMOSTAT_OPERATING_STATE_LOGGING_REPORT_V2, + report_number, + 0b11110001, // Test if we correctly ignore the first 4 bits (THERMOSTAT_OPERATING_STATE_REPORT_OPERATING_STATE_HEATING) + reports[1], + reports[2], + reports[3], + reports[4], + reports[5], + reports[6], + reports[7], + reports[8], + reports[9], + }; + + TEST_ASSERT_EQUAL(SL_STATUS_OK, + handler.control_handler(&info, frame, sizeof(frame))); + + TEST_ASSERT_EQUAL(report_number, + attribute_store_get_node_child_count(log_node)); + + const attribute_store_type_t attribute_store_types[] = { + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_HOURS, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_TODAY_MIN, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_HOURS, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_OPERATING_STATE_LOG_USAGE_YESTERDAY_MIN}; + + for (uint8_t i = 0; i < report_number; i++) { + attribute_store_node_t current_report_node + = attribute_store_get_node_child(log_node, i); + + thermostat_operating_state_t reported_state; + attribute_store_get_reported(current_report_node, + &reported_state, + sizeof(reported_state)); + // Expected index : + // 0 + // 5 + TEST_ASSERT_EQUAL_MESSAGE(reports[(5 * i)], + reported_state, + "Error while checking Operating State Log Type"); + + for (int j = 0; j < 4; j++) { + thermostat_operating_state_usage_t reported_value; + + attribute_store_get_child_reported(current_report_node, + attribute_store_types[j], + &reported_value, + sizeof(reported_value)); + // Expected index : + // 1,2,3,4 (1 + (5*i) + j) + // 6,7,8,9 + const uint8_t index = 1 + (5 * i) + j; + printf("Checking report value of type %d with index %d\n", + attribute_store_types[j], + index); + TEST_ASSERT_EQUAL_MESSAGE( + reports[index], + reported_value, + "Error while checking Operating State Log contents"); + } + } +} \ No newline at end of file