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