From 26156eedff416b2b110c565984ffac7981fd8640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Labb=C3=A9?= Date: Thu, 13 Jun 2024 10:25:25 +0200 Subject: [PATCH 1/4] GH-31: Custom Thermostat Cluster Forwarded: https://github.com/SiliconLabs/UnifySDK/pull/31 Bug-SiliconLabs: UIC-3071 Bug-Github: https://github.com/SiliconLabs/UnifySDK/pull/31 --- .../dotdot-xml/Unify_Thermostat.xml | 30 +++++++++++++++++++ components/uic_dotdot/dotdot-xml/library.xml | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 components/uic_dotdot/dotdot-xml/Unify_Thermostat.xml diff --git a/components/uic_dotdot/dotdot-xml/Unify_Thermostat.xml b/components/uic_dotdot/dotdot-xml/Unify_Thermostat.xml new file mode 100644 index 000000000..54b8c7d8e --- /dev/null +++ b/components/uic_dotdot/dotdot-xml/Unify_Thermostat.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/uic_dotdot/dotdot-xml/library.xml b/components/uic_dotdot/dotdot-xml/library.xml index 78c9d8140..41096c633 100644 --- a/components/uic_dotdot/dotdot-xml/library.xml +++ b/components/uic_dotdot/dotdot-xml/library.xml @@ -490,5 +490,5 @@ applicable to this document can be found in the LICENSE.md file. - + From a72ca08e0f337126369376a6f3dfeb5ffd719a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Labb=C3=A9?= Date: Thu, 13 Jun 2024 10:26:07 +0200 Subject: [PATCH 2/4] GH-31: Generated files for custom thermostat cluster Forwarded: https://github.com/SiliconLabs/UnifySDK/pull/31 Bug-SiliconLabs: UIC-3071 Bug-Github: https://github.com/SiliconLabs/UnifySDK/pull/31 --- .../cluster-types/cluster-type-attributes.ts | 2 +- .../cluster-type-attributes.ts.rej | 10 + .../include/dotdot_attribute_id_definitions.h | 2 + .../dotdot_cluster_command_id_definitions.h | 2 + .../include/dotdot_cluster_id_definitions.h | 4 + .../zap-generated/include/zap-types.h | 16 + .../readme_ucl_mqtt_reference.md | 297 +++++++++++++ .../src/dotdot_attribute_id_definitions.c | 33 ++ .../src/dotdot_cluster_id_definitions.c | 5 + .../zap-generated/include/dotdot_mqtt.h | 151 +++++++ .../include/dotdot_mqtt_attributes.h | 22 + .../include/dotdot_mqtt_generated_commands.h | 22 + .../include/dotdot_mqtt_group_commands.h | 17 + .../include/dotdot_mqtt_helpers.h | 7 + .../include/dotdot_mqtt_helpers.hpp | 17 + ...dotdot_mqtt_supported_generated_commands.h | 28 ++ .../zap-generated/src/dotdot_mqtt.cpp | 417 ++++++++++++++++++ .../zap-generated/src/dotdot_mqtt.hpp | 28 ++ .../src/dotdot_mqtt_attributes.cpp | 120 +++++ .../src/dotdot_mqtt_command_helpers.cpp | 16 + .../src/dotdot_mqtt_command_helpers.hpp | 12 + .../src/dotdot_mqtt_generated_commands.cpp | 40 ++ .../src/dotdot_mqtt_group_commands.cpp | 99 +++++ .../zap-generated/src/dotdot_mqtt_helpers.cpp | 86 ++++ ...tdot_mqtt_supported_generated_commands.cpp | 43 ++ .../test/dotdot_mqtt_test.include | 1 + .../include/dotdot_attributes.uam | 3 + .../include/dotdot_attributes_camel_case.uam | 3 + .../unify_dotdot_attribute_store_helpers.h | 134 ++++++ .../unify_dotdot_defined_attribute_types.h | 2 + ...ot_attribute_store_attribute_publisher.cpp | 229 ++++++++++ ..._force_read_attributes_command_callbacks.c | 32 ++ .../unify_dotdot_attribute_store_helpers.cpp | 133 ++++++ ...fy_dotdot_attribute_store_registration.cpp | 24 + ...store_write_attributes_command_callbacks.c | 33 ++ .../test/unify_dotdot_attribute_store_test.c | 27 ++ .../test/unify_dotdot_attribute_store_test.h | 4 + 37 files changed, 2120 insertions(+), 1 deletion(-) create mode 100644 applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts.rej diff --git a/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts b/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts index 15eb5413c..339469d26 100644 --- a/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts +++ b/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts @@ -15863,5 +15863,5 @@ export let ClusterTypeAttrs: any = { commands: [ ] } - } + }, } diff --git a/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts.rej b/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts.rej new file mode 100644 index 000000000..34b0a9a75 --- /dev/null +++ b/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts.rej @@ -0,0 +1,10 @@ +diff a/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts b/applications/dev_ui/dev_gui/zap-generated/src/cluster-types/cluster-type-attributes.ts (rejected hunks) +@@ -1,7 +1,7 @@ + //This file is generated automatically. Don't try to change something here. + //To add support for new clusters, modify addon-helper.js + //To change the stucture of the ClusterTypeAttrs, modify cluster-type-attributes.zapt +- ++ + + //generate ClusterTypes + export let ClusterTypeAttrs: any = { diff --git a/components/uic_dotdot/zap-generated/include/dotdot_attribute_id_definitions.h b/components/uic_dotdot/zap-generated/include/dotdot_attribute_id_definitions.h index 70c0a280c..60293859a 100644 --- a/components/uic_dotdot/zap-generated/include/dotdot_attribute_id_definitions.h +++ b/components/uic_dotdot/zap-generated/include/dotdot_attribute_id_definitions.h @@ -856,6 +856,8 @@ typedef enum { #define DOTDOT_PROTOCOL_CONTROLLER_NETWORK_MANAGEMENT_NETWORK_MANAGEMENT_STATE_ATTRIBUTE_ID ((dotdot_attribute_id_t)0x1) // Definitions for cluster: Descriptor #define DOTDOT_DESCRIPTOR_DEVICE_TYPE_LIST_ATTRIBUTE_ID ((dotdot_attribute_id_t)0x0) +// Definitions for cluster: UnifyThermostat +#define DOTDOT_UNIFY_THERMOSTAT_OPERATING_STATE_ATTRIBUTE_ID ((dotdot_attribute_id_t)0x3) // clang-format on diff --git a/components/uic_dotdot/zap-generated/include/dotdot_cluster_command_id_definitions.h b/components/uic_dotdot/zap-generated/include/dotdot_cluster_command_id_definitions.h index 2833fd954..a553279a6 100644 --- a/components/uic_dotdot/zap-generated/include/dotdot_cluster_command_id_definitions.h +++ b/components/uic_dotdot/zap-generated/include/dotdot_cluster_command_id_definitions.h @@ -364,6 +364,8 @@ // Commands for cluster: Descriptor +// Commands for cluster: UnifyThermostat + #ifdef __cplusplus extern "C" { #endif diff --git a/components/uic_dotdot/zap-generated/include/dotdot_cluster_id_definitions.h b/components/uic_dotdot/zap-generated/include/dotdot_cluster_id_definitions.h index 1878448a5..12890d4ec 100644 --- a/components/uic_dotdot/zap-generated/include/dotdot_cluster_id_definitions.h +++ b/components/uic_dotdot/zap-generated/include/dotdot_cluster_id_definitions.h @@ -254,6 +254,10 @@ #define DOTDOT_DESCRIPTOR_CLUSTER_ID ((dotdot_cluster_id_t)0xFD13) +// Definitions for cluster: UnifyThermostat +#define DOTDOT_UNIFY_THERMOSTAT_CLUSTER_ID ((dotdot_cluster_id_t)0xFD15) + + #ifdef __cplusplus extern "C" { #endif diff --git a/components/uic_dotdot/zap-generated/include/zap-types.h b/components/uic_dotdot/zap-generated/include/zap-types.h index fb82f117c..0310bd487 100644 --- a/components/uic_dotdot/zap-generated/include/zap-types.h +++ b/components/uic_dotdot/zap-generated/include/zap-types.h @@ -1271,6 +1271,22 @@ typedef enum { ZCL_TX_REPORT_TRANSMISSION_SPEED_UNKNOWN = 255, } TxReportTransmissionSpeed; +// Enum for UnifyThermostatOperatingState +typedef enum { + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_OFF = 0, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_HEATING = 1, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_COOLING = 2, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_FAN_ONLY = 3, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_PENDING_HEAT = 4, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_PENDING_COOL = 5, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_VENT_ECONOMIZER = 6, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_AUX_HEATING = 7, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_2ND_STAGE_HEATING = 8, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_2ND_STAGE_COOLING = 9, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_2ND_STAGE_AUX_HEAT = 10, + ZCL_UNIFY_THERMOSTAT_OPERATING_STATE_3RD_STAGE_AUX_HEAT = 11, +} UnifyThermostatOperatingState; + // Enum for WindowCoveringWindowCoveringType typedef enum { ZCL_WINDOW_COVERING_WINDOW_COVERING_TYPE_ROLLERSHADE = 0, diff --git a/components/uic_dotdot/zap-generated/readme_ucl_mqtt_reference.md b/components/uic_dotdot/zap-generated/readme_ucl_mqtt_reference.md index dbdfa36ec..54d2d73e5 100644 --- a/components/uic_dotdot/zap-generated/readme_ucl_mqtt_reference.md +++ b/components/uic_dotdot/zap-generated/readme_ucl_mqtt_reference.md @@ -53949,6 +53949,270 @@ mosquitto_pub -t 'ucl/by-unid///Descriptor/Commands/ForceReadAttribute +


+ + + + + + + +\page unify_thermostat UnifyThermostat Cluster +The following commands and attributes are accepted as JSON payloads for the +UnifyThermostat cluster. + +

+ + + + +\section unify_thermostat_attrs UnifyThermostat Attributes +The following attribute topics are used to retrieve the UnifyThermostat cluster state. + +
+ +\subsection unify_thermostat_attr_operating_state UnifyThermostat/OperatingState Attribute + +**MQTT Topic Pattern:** + +``` +[PREFIX]/UnifyThermostat/Attributes/OperatingState/Reported +[PREFIX]/UnifyThermostat/Attributes/OperatingState/Desired +``` + +**MQTT Payload JSON Schema:** + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnifyThermostat Cluster OperatingState Attribute Properties", + "type": "object", + "properties": { + "value": { + "type": "UnifyThermostatOperatingState" + } + }, + "required": [ + "value" + ] +} +``` + + +**Example Mosquitto CLI Tool Usage** + +To see desired/reported value for OperatingState attribute under the by-unid topic space: + +```console +mosquitto_sub -t 'ucl/by-unid/+/+/UnifyThermostat/Attributes/OperatingState/+' + +# Example output + +ucl/by-unid//ep0/UnifyThermostat/Attributes/OperatingState/Desired { "value": } +ucl/by-unid//ep0/UnifyThermostat/Attributes/OperatingState/Reported { "value": } + +``` + +

+ + +\subsection unify_thermostat_attr_cluster_revision UnifyThermostat/ClusterRevision Attribute + +**MQTT Topic Pattern:** + +``` +[PREFIX]/UnifyThermostat/Attributes/ClusterRevision/Reported +[PREFIX]/UnifyThermostat/Attributes/ClusterRevision/Desired +``` + +**MQTT Payload JSON Schema:** + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnifyThermostat Cluster ClusterRevision Attribute Properties", + "type": "object", + "properties": { + "value": { + "type": "integer" + } + }, + "required": [ + "value" + ] +} +``` + +**Example Mosquitto CLI Tool Usage** + +To see desired/reported value for ClusterRevision attribute under the by-unid topic space: + +```console +mosquitto_sub -t 'ucl/by-unid///UnifyThermostat/Attributes/ClusterRevision/+' +# Example output +ucl/by-unid///UnifyThermostat/Attributes/ClusterRevision/Desired { "value": } +ucl/by-unid///UnifyThermostat/Attributes/ClusterRevision/Reported { "value": } +``` + + + + + +

+ + + + +\section unify_thermostat_recv_cmd_support UnifyThermostat Command Support + +**MQTT Topic Pattern:** + +``` +[PREFIX]/UnifyThermostat/SupportedCommands +[PREFIX]/UnifyThermostat/SupportedGeneratedCommands +``` + +**MQTT Payload JSON Schema:** + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnifyThermostat Command Support Properties", + "type": "object", + "properties": { + "value": { + "type": "array", + "items" : { + "type": "string", + "enum": [ + ] + } + } + } + }, + "required": [ + "value" + ] +} +``` + +**Example Mosquitto CLI Tool Usage** + +To see supported commands for UnifyThermostat cluster under the by-unid topic space: + +```console +mosquitto_sub -t 'ucl/by-unid///UnifyThermostat/SupportedCommands' +# Example output +ucl/by-unid///UnifyThermostat/SupportedCommands { "value": [] } +``` + +To see supported generated commands for UnifyThermostat cluster under the by-unid topic space: + +```console +mosquitto_sub -t 'ucl/by-unid///UnifyThermostat/SupportedGeneratedCommands' +# Example output +ucl/by-unid///UnifyThermostat/SupportedGeneratedCommands { "value": [] } +``` + + + + + +

+ + + + +\section unify_thermostat_cmds UnifyThermostat Commands + +

+ +\subsection unify_thermostat_write_attr_cmd UnifyThermostat/WriteAttributes Command + +**MQTT Topic Pattern:** + +``` +[PREFIX]/UnifyThermostat/Commands/WriteAttributes +``` + +**MQTT Payload JSON Schema:** + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnifyThermostat Cluster WriteAttributes Command Properties", + "type": "object", + "properties": { + }, + "required": [ + "value" + ] +} +``` + +**Example Mosquitto CLI Tool Usage** + +To update all UnifyThermostat attributes under the by-unid topic space: + +```console +mosquitto_pub -t 'ucl/by-unid///UnifyThermostat/Commands/WriteAttributes' -m '{ }' +``` + +> NOTE: Specify only the list of attributes to write in this command. +> Unspecified attributes will not be updated. + +

+ +\subsection unify_thermostat_force_read_attr_cmd UnifyThermostat/ForceReadAttributes Command + +**MQTT Topic Pattern:** + +``` +[PREFIX]/UnifyThermostat/Commands/ForceReadAttributes +``` + +**MQTT Payload JSON Schema:** + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnifyThermostat Cluster ForceReadAttributes Command Properties", + "type": "object", + "properties": { + "value": { + "type": "array" + "items": { + "type": "string", + "enum": [ + "OperatingState" + ] + } + } + }, + "required": [ + "value" + ] +} +``` + +**Example Mosquitto CLI Tool Usage** + +To force read all UnifyThermostat attributes under the by-unid topic space (by sending an empty array): + +```console +mosquitto_pub -t 'ucl/by-unid///UnifyThermostat/Commands/ForceReadAttributes' -m '{ "value": [] }' +``` + +To force read one of the UnifyThermostat attributes under the by-unid topic space: + +```console +mosquitto_pub -t 'ucl/by-unid///UnifyThermostat/Commands/ForceReadAttributes' -m '{ "value": ["OperatingState"] }' +``` + + + + +


@@ -57545,6 +57809,39 @@ mosquitto_pub -t 'ucl/by-unid///Descriptor/Commands/ForceReadAttribute

+ + + +\section enum_unify_thermostat_operating_state UnifyThermostatOperatingState Enum + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnifyThermostatOperatingState Enum Properties", + "type": "string", + "enum": [ + "Off", + "Heating", + "Cooling", + "FanOnly", + "PendingHeat", + "PendingCool", + "Vent/Economizer", + "AuxHeating", + "2ndStageHeating", + "2ndStageCooling", + "2ndStageAuxHeat", + "3rdStageAuxHeat" + ] +} +``` + + + + + +

+ diff --git a/components/uic_dotdot/zap-generated/src/dotdot_attribute_id_definitions.c b/components/uic_dotdot/zap-generated/src/dotdot_attribute_id_definitions.c index cd3e175e6..75a469c49 100644 --- a/components/uic_dotdot/zap-generated/src/dotdot_attribute_id_definitions.c +++ b/components/uic_dotdot/zap-generated/src/dotdot_attribute_id_definitions.c @@ -2025,6 +2025,17 @@ const char *uic_dotdot_get_attribute_name(dotdot_cluster_id_t cluster_id, return "Unknown"; } // clang-format off + case DOTDOT_UNIFY_THERMOSTAT_CLUSTER_ID: + // clang-format on + switch (attribute_id) { + // clang-format off + case DOTDOT_UNIFY_THERMOSTAT_OPERATING_STATE_ATTRIBUTE_ID: + return "OperatingState"; + // clang-format on + default: + return "Unknown"; + } + // clang-format off // clang-format on default: return "Unknown"; @@ -4409,6 +4420,11 @@ dotdot_attribute_id_t return DOTDOT_DESCRIPTOR_DEVICE_TYPE_LIST_ATTRIBUTE_ID; } break; + case DOTDOT_UNIFY_THERMOSTAT_CLUSTER_ID: + if (strcmp ("OperatingState", attribute_name) == 0) { + return DOTDOT_UNIFY_THERMOSTAT_OPERATING_STATE_ATTRIBUTE_ID; + } + break; default: return DOTDOT_INVALID_ATTRIBUTE_ID; } @@ -6426,6 +6442,17 @@ dotdot_attribute_json_type_t return JSON_TYPE_UNKNOWN; } // clang-format off + case DOTDOT_UNIFY_THERMOSTAT_CLUSTER_ID: + // clang-format on + switch (attribute_id) { + // clang-format off + case DOTDOT_UNIFY_THERMOSTAT_OPERATING_STATE_ATTRIBUTE_ID: + return JSON_TYPE_NUMBER; + // clang-format on + default: + return JSON_TYPE_UNKNOWN; + } + // clang-format off // clang-format on default: return JSON_TYPE_UNKNOWN; @@ -6774,5 +6801,11 @@ bool uic_dotdot_attribute_is_enum(dotdot_cluster_id_t cluster_id, if (64787 == cluster_id) { } + if (64789 == cluster_id) { + if (3 == attribute_id) { + return true; + } + } + return false; } diff --git a/components/uic_dotdot/zap-generated/src/dotdot_cluster_id_definitions.c b/components/uic_dotdot/zap-generated/src/dotdot_cluster_id_definitions.c index 18b5402fa..dd4cb03a0 100644 --- a/components/uic_dotdot/zap-generated/src/dotdot_cluster_id_definitions.c +++ b/components/uic_dotdot/zap-generated/src/dotdot_cluster_id_definitions.c @@ -128,6 +128,8 @@ const char* uic_dotdot_get_cluster_name(dotdot_cluster_id_t cluster_id) { return "ProtocolController-NetworkManagement"; case DOTDOT_DESCRIPTOR_CLUSTER_ID: return "Descriptor"; + case DOTDOT_UNIFY_THERMOSTAT_CLUSTER_ID: + return "UnifyThermostat"; default: return "Unknown"; } @@ -299,6 +301,9 @@ dotdot_cluster_id_t uic_dotdot_get_cluster_id(const char* cluster_name) { if (strcmp ("Descriptor", cluster_name) == 0) { return DOTDOT_DESCRIPTOR_CLUSTER_ID; } + if (strcmp ("UnifyThermostat", cluster_name) == 0) { + return DOTDOT_UNIFY_THERMOSTAT_CLUSTER_ID; + } // Return an invalid ID if we did not get any match. return DOTDOT_INVALID_CLUSTER_ID; diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt.h b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt.h index f0dc62b09..591b43c73 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt.h +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt.h @@ -40444,6 +40444,157 @@ void uic_mqtt_dotdot_descriptor_publish_supported_commands( void uic_mqtt_dotdot_descriptor_publish_empty_supported_commands( const dotdot_unid_t unid ,dotdot_endpoint_id_t endpoint); +// Callback types used by the unify_thermostat cluster + +typedef struct { + uint8_t operating_state; +} uic_mqtt_dotdot_unify_thermostat_state_t; + +typedef struct { + bool operating_state; +} uic_mqtt_dotdot_unify_thermostat_updated_state_t; + +typedef sl_status_t (*uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t)( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint, + uic_mqtt_dotdot_callback_call_type_t call_type, + uic_mqtt_dotdot_unify_thermostat_state_t, + uic_mqtt_dotdot_unify_thermostat_updated_state_t +); + +typedef sl_status_t (*uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t)( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint, + uic_mqtt_dotdot_callback_call_type_t call_type, + uic_mqtt_dotdot_unify_thermostat_updated_state_t +); + + + + +/** + * @brief Setup a callback for WriteAttribute to be called when a + * +/unify_thermostat/Commands/WriteAttributes is received. + * + * Setting this callback will not overwrite the previous set callback + * @param callback Function to be called on command reception + */ +void uic_mqtt_dotdot_set_unify_thermostat_write_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t callback +); +/** + * @brief Unsets a callback for WriteAttribute to be called when a + * +/unify_thermostat/Commands/WriteAttributes is received. + * @param callback Function to be no longer called on command reception + */ +void uic_mqtt_dotdot_unset_unify_thermostat_write_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t callback +); +/** + * @brief Clears all callbacks registered for when + * +/unify_thermostat/Commands/WriteAttributes is received. + */ +void uic_mqtt_dotdot_clear_unify_thermostat_write_attributes_callbacks(); + +/** + * @brief Setup a callback for ForceReadAttributes to be called when a + * +/unify_thermostat/Commands/ForceReadAttributes is received. + * + * Setting this callback will not overwrite the previous set callback + * @param callback Function to be called on command reception + */ +void uic_mqtt_dotdot_set_unify_thermostat_force_read_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t callback +); +/** + * @brief Unsets a callback for ForceReadAttributes to be called when a + * +/unify_thermostat/Commands/ForceReadAttributes is received. + * + * @param callback Function to be no longer called on command reception + */ +void uic_mqtt_dotdot_unset_unify_thermostat_force_read_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t callback +); +/** + * @brief Clears all callbacks registered for when + * +/unify_thermostat/Commands/ForceReadAttributes is received. + */ +void uic_mqtt_dotdot_clear_unify_thermostat_force_read_attributes_callbacks(); + +/** + * @brief Publish the attribute; UnifyThermostat/Attributes/OperatingState + * + * @param base_topic topic prefix to publish, /operating_state + * will be appended + * @param value Value to publish + * @param publish_type Whether to publish as Desired, Reported, or Both. + * + * @returns SL_STATUS_OK on success + */ +sl_status_t uic_mqtt_dotdot_unify_thermostat_operating_state_publish( + const char *base_topic, + uint8_t value, + uic_mqtt_dotdot_attribute_publish_type_t publish_type +); + +/** + * @brief Unretains a published attribute; UnifyThermostat/Attributes/OperatingState + * + * @param base_topic topic prefix to publish, /operating_state + * will be appended + * @param publish_type Whether to publish as Desired, Reported, or Both. + * + * @returns SL_STATUS_OK on success + */ +sl_status_t uic_mqtt_dotdot_unify_thermostat_operating_state_unretain( + const char *base_topic, + uic_mqtt_dotdot_attribute_publish_type_t publish_type +); + + +/** + * @brief Publish the UnifyThermostat/ClusterRevision attribute + * + * @param base_topic topic prefix to publish, /UnifyThermostat/Attributes/ClusterRevision + * will be appended. + * @param value Value to publish. + */ +void uic_mqtt_dotdot_unify_thermostat_publish_cluster_revision(const char* base_topic, uint16_t value); + +/** + * @brief Unretain a publication to UnifyThermostat/ClusterRevision attribute + * + * @param base_topic topic prefix to publish, /UnifyThermostat/Attributes/ClusterRevision + * will be appended. + */ +void uic_mqtt_dotdot_unify_thermostat_unretain_cluster_revision(const char* base_topic); + +/** + * @brief Publish the SupportedCommands for UNID/EndPoint for the UnifyThermostat Cluster + * + * This function will iterate over all Commands in the UnifyThermostat Cluster and + * call all registered callback functions with UNID/endpoint, and + * callback_type = UIC_MQTT_DOTDOT_CALLBACK_TYPE_SUPPORT_CHECK. + * All Cluster Command callback functions that return SL_STATUS_OK + * will be added to the list of supported commands and published. + * + * @param unid + * @param endpoint + */ +void uic_mqtt_dotdot_unify_thermostat_publish_supported_commands( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint); + +/** + * @brief Publish an empty array of SupportedCommands for UNID/EndPoint for + * the UnifyThermostat Cluster + * + * @param unid + * @param endpoint ) + */ +void uic_mqtt_dotdot_unify_thermostat_publish_empty_supported_commands( + const dotdot_unid_t unid + ,dotdot_endpoint_id_t endpoint); /** * @brief Publish the SupportedCommands for UNID/EndPoint diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_attributes.h b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_attributes.h index 92daf48a2..afde171fd 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_attributes.h +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_attributes.h @@ -5134,6 +5134,14 @@ typedef sl_status_t (*uic_mqtt_dotdot_descriptor_attribute_device_type_list_call size_t device_type_list_count, const DeviceTypeStruct* device_type_list ); +// Callback types used by the unify_thermostat cluster +typedef sl_status_t (*uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback_t)( + dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint, + bool unretained, + uic_mqtt_dotdot_attribute_update_type_t update_type, + uint8_t operating_state +); #ifdef __cplusplus extern "C" { @@ -9858,6 +9866,20 @@ sl_status_t uic_mqtt_dotdot_descriptor_attributes_init(); void uic_mqtt_dotdot_descriptor_attribute_device_type_list_callback_set(const uic_mqtt_dotdot_descriptor_attribute_device_type_list_callback_t callback); +/** + * Initializes the attributes features for the UnifyThermostat cluster, + * allowing to receive attribute updates from other UNIDs. + */ +sl_status_t uic_mqtt_dotdot_unify_thermostat_attributes_init(); + +/** + * Setup callback to be called when a + * UnifyThermostat/Attributes/operating_state/# is received. Setting + * this callback will overwrite the previous set callback + */ +void uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback_set(const uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback_t callback); + + #ifdef __cplusplus } #endif // __cplusplus diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_generated_commands.h b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_generated_commands.h index 16d97617d..01bbaadea 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_generated_commands.h +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_generated_commands.h @@ -4987,6 +4987,28 @@ void uic_mqtt_dotdot_descriptor_publish_generated_write_attributes_command( ); +/** + * @brief Publishes an incoming/generated WriteAttributes command for + * the UnifyThermostat cluster. + * + * Publication will be made at the following topic + * ucl/by-unid/UNID/epID/UnifyThermostat/GeneratedCommands/WriteAttributes + * + * @param unid The UNID of the node that sent us the command. + * + * @param endpoint The Endpoint ID of the node that sent us the command. + * + * @param attribute_values Values to assign to the attributes + * @param attribute_list List of attributes that are written + */ +void uic_mqtt_dotdot_unify_thermostat_publish_generated_write_attributes_command( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint, + uic_mqtt_dotdot_unify_thermostat_state_t attribute_values, + uic_mqtt_dotdot_unify_thermostat_updated_state_t attribute_list +); + + #ifdef __cplusplus } #endif // __cplusplus diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_group_commands.h b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_group_commands.h index a5c1d17ef..c17a3b068 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_group_commands.h +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_group_commands.h @@ -3709,6 +3709,23 @@ void uic_mqtt_dotdot_by_group_descriptor_write_attributes_callback_set( +typedef void (*uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback_t)( + const dotdot_group_id_t group_id, + uic_mqtt_dotdot_unify_thermostat_state_t, + uic_mqtt_dotdot_unify_thermostat_updated_state_t +); + +/** + * Setup a callback for WriteAttribute to be called when a + * ucl/by-group/+/unify_thermostat/Commands/WriteAttributes is received. + * Setting this callback will overwrite any previously set callback. + */ +void uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback_set( + const uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback_t callback +); + + + #ifdef __cplusplus } #endif // __cplusplus diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.h b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.h index dcc29ad5f..d397d4e97 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.h +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.h @@ -811,6 +811,13 @@ char *tx_report_transmission_speed_get_enum_value_name_c( uint32_t value, char *result, size_t max_result_size); /** Get tx_report_transmission_speed enum representation from string. */ uint32_t tx_report_transmission_speed_get_enum_value_number_c(const char *str); +#define UNIFY_THERMOSTAT_OPERATING_STATE_ENUM_NAME_AVAILABLE 1 + +/** Get unify_thermostat_operating_state string representation from enum. */ +char *unify_thermostat_operating_state_get_enum_value_name_c( + uint32_t value, char *result, size_t max_result_size); +/** Get unify_thermostat_operating_state enum representation from string. */ +uint32_t unify_thermostat_operating_state_get_enum_value_number_c(const char *str); #define WINDOW_COVERING_WINDOW_COVERING_TYPE_ENUM_NAME_AVAILABLE 1 /** Get window_covering_window_covering_type string representation from enum. */ diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.hpp b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.hpp index 8db91a511..5eb54ab03 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.hpp +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_helpers.hpp @@ -1934,6 +1934,23 @@ std::string tx_report_transmission_speed_get_enum_value_name( */ uint32_t tx_report_transmission_speed_get_enum_value_number(const std::string &str); +#define UNIFY_THERMOSTAT_OPERATING_STATE_ENUM_NAME_AVAILABLE 1 + +/** + * @brief Finds the name of a field for the UnifyThermostatOperatingState enum + * + * @returns A string representation of the value. + */ +std::string unify_thermostat_operating_state_get_enum_value_name( + uint32_t value); + +/** + * @brief Finds the enum number of a string representation for the UnifyThermostatOperatingState enum + * + * @returns A number enum value. + */ +uint32_t unify_thermostat_operating_state_get_enum_value_number(const std::string &str); + #define WINDOW_COVERING_WINDOW_COVERING_TYPE_ENUM_NAME_AVAILABLE 1 /** diff --git a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_supported_generated_commands.h b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_supported_generated_commands.h index 3180bd049..af6b1f805 100644 --- a/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_supported_generated_commands.h +++ b/components/uic_dotdot_mqtt/zap-generated/include/dotdot_mqtt_supported_generated_commands.h @@ -1664,6 +1664,34 @@ void uic_mqtt_dotdot_descriptor_publish_supported_generated_commands( ); +/** + * @brief Struct containing the list of commands for UnifyThermostat + */ +typedef struct _uic_mqtt_dotdot_unify_thermostat_supported_commands_ { + bool write_attributes; +} uic_mqtt_dotdot_unify_thermostat_supported_commands_t; + +/** + * @brief Sends/Publishes a the SupportedGenerated commands for + * the UnifyThermostat cluster for a UNID/Endpoint + * + * Publication will be made at the following topic + * ucl/by-unid/UNID/epID/UnifyThermostat/SupportedGeneratedCommands + * + * @param unid The UNID of the node on behalf of which the advertisment is made + * + * @param endpoint The Endpoint ID of the node on behalf of which the advertisment is made + * + * @param command_list Struct pointer with the fields value indicating if + * individual commands can be generated. + */ +void uic_mqtt_dotdot_unify_thermostat_publish_supported_generated_commands( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint, + const uic_mqtt_dotdot_unify_thermostat_supported_commands_t *command_list +); + + #ifdef __cplusplus } diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.cpp index 5dbf0c607..46e857b6e 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.cpp @@ -94739,6 +94739,269 @@ sl_status_t uic_mqtt_dotdot_descriptor_init() return SL_STATUS_OK; } +// Callbacks pointers +static std::set uic_mqtt_dotdot_unify_thermostat_write_attributes_callback; +static std::set uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback; + +// Callbacks setters + +void uic_mqtt_dotdot_set_unify_thermostat_write_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t callback) +{ + if (callback != nullptr) { + uic_mqtt_dotdot_unify_thermostat_write_attributes_callback.insert(callback); + } +} +void uic_mqtt_dotdot_unset_unify_thermostat_write_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t callback) +{ + uic_mqtt_dotdot_unify_thermostat_write_attributes_callback.erase(callback); +} +void uic_mqtt_dotdot_clear_unify_thermostat_write_attributes_callbacks() +{ + uic_mqtt_dotdot_unify_thermostat_write_attributes_callback.clear(); +} +std::set& get_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback() +{ + return uic_mqtt_dotdot_unify_thermostat_write_attributes_callback; +} + +void uic_mqtt_dotdot_set_unify_thermostat_force_read_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t callback) +{ + if (callback != nullptr) { + uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback.insert(callback); + } +} +void uic_mqtt_dotdot_unset_unify_thermostat_force_read_attributes_callback( + const uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t callback) +{ + uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback.erase(callback); +} +void uic_mqtt_dotdot_clear_unify_thermostat_force_read_attributes_callbacks() +{ + uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback.clear(); +} + + +// Callback function for incoming publications on ucl/by-unid/+/+/UnifyThermostat/Commands/WriteAttributes +void uic_mqtt_dotdot_on_unify_thermostat_WriteAttributes( + const char *topic, + const char *message, + const size_t message_length) +{ + if (uic_mqtt_dotdot_unify_thermostat_write_attributes_callback.empty()) { + return; + } + + if (message_length == 0) { + return; + } + + std::string unid; + uint8_t endpoint = 0; // Default value for endpoint-less topics. + if(! uic_dotdot_mqtt::parse_topic(topic,unid,endpoint)) { + sl_log_debug(LOG_TAG, + "Error parsing UNID / Endpoint ID from topic %s. Ignoring", + topic); + return; + } + + uic_mqtt_dotdot_unify_thermostat_state_t new_state = {}; + uic_mqtt_dotdot_unify_thermostat_updated_state_t new_updated_state = {}; + + + nlohmann::json jsn; + try { + jsn = nlohmann::json::parse(std::string(message)); + + uic_mqtt_dotdot_parse_unify_thermostat_write_attributes( + jsn, + new_state, + new_updated_state + ); + } catch (const nlohmann::json::parse_error& e) { + // Catch JSON object field parsing errors + sl_log_debug(LOG_TAG, LOG_FMT_JSON_PARSE_FAIL, "UnifyThermostat", "WriteAttributes"); + return; + } catch (const nlohmann::json::exception& e) { + // Catch JSON object field parsing errors + sl_log_debug(LOG_TAG, LOG_FMT_JSON_ERROR, "UnifyThermostat", "WriteAttributes", e.what()); + return; + } catch (const std::exception& e) { + sl_log_debug(LOG_TAG, LOG_FMT_JSON_ERROR, "UnifyThermostat", "WriteAttributes", ""); + return; + } + + for (const auto& callback: uic_mqtt_dotdot_unify_thermostat_write_attributes_callback){ + callback( + static_cast(unid.c_str()), + endpoint, + UIC_MQTT_DOTDOT_CALLBACK_TYPE_NORMAL, + new_state, + new_updated_state + ); + } + +} + +static void uic_mqtt_dotdot_on_unify_thermostat_force_read_attributes( + const char *topic, + const char *message, + const size_t message_length) +{ + uint8_t endpoint = 0; + std::string unid; + + if ((message_length == 0) || (uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback.empty())) { + return; + } + + if(! uic_dotdot_mqtt::parse_topic(topic, unid, endpoint)) { + sl_log_debug(LOG_TAG, + "Error parsing UNID / Endpoint ID from topic %s. Ignoring", + topic); + return; + } + + try { + uic_mqtt_dotdot_unify_thermostat_updated_state_t force_update = {0}; + bool trigger_handler = false; + + nlohmann::json jsn = nlohmann::json::parse(std::string(message)); + std::vector attributes = jsn["value"].get>(); + + // Assume all attributes to be read on empty array received + if (attributes.size() == 0) { + force_update.operating_state = true; + trigger_handler = true; + } else { + std::unordered_map supported_attrs = { + {"OperatingState", &force_update.operating_state }, + }; + + for (auto& attribute : attributes) { + auto found_attr = supported_attrs.find(attribute); + if (found_attr != supported_attrs.end()) { + *(found_attr->second) = true; + trigger_handler = true; + } + } + } + + if (trigger_handler == true) { + for (const auto& callback: uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback) { + callback( + static_cast(unid.c_str()), + endpoint, + UIC_MQTT_DOTDOT_CALLBACK_TYPE_NORMAL, + force_update + ); + } + } + } catch (...) { + sl_log_debug(LOG_TAG, "UnifyThermostat/Commands/ForceReadAttributes: Unable to parse JSON payload"); + return; + } +} + +sl_status_t uic_mqtt_dotdot_unify_thermostat_operating_state_publish( + const char *base_topic, + uint8_t value, + uic_mqtt_dotdot_attribute_publish_type_t publish_type +) +{ + nlohmann::json jsn; + + // This is a single value + + #ifdef UNIFY_THERMOSTAT_OPERATING_STATE_ENUM_NAME_AVAILABLE + jsn["value"] = unify_thermostat_operating_state_get_enum_value_name((uint32_t)value); + #elif defined(ENUM8_ENUM_NAME_AVAILABLE) + jsn["value"] = enum8_get_enum_value_name((uint32_t)value); + #else + sl_log_warning(LOG_TAG,"Warning: Enum name not available for UNIFY_THERMOSTAT_OPERATING_STATE. Using number instead."); + jsn["value"] = static_cast(value); + #endif + + + std::string payload_str; + try { + // Payload contains data from end nodes, which we cannot control, thus we handle if there are non-utf8 characters + payload_str = jsn.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); + } catch (const nlohmann::json::exception& e) { + sl_log_debug(LOG_TAG, LOG_FMT_JSON_ERROR, "UnifyThermostat/Attributes/OperatingState", e.what()); + return SL_STATUS_OK; + } + + + std::string topic = std::string(base_topic) + "/UnifyThermostat/Attributes/OperatingState"; + if (publish_type & UCL_MQTT_PUBLISH_TYPE_DESIRED) + { + std::string topic_desired = topic + "/Desired"; + uic_mqtt_publish(topic_desired.c_str(), + payload_str.c_str(), + payload_str.length(), + true); + } + if (publish_type & UCL_MQTT_PUBLISH_TYPE_REPORTED) + { + std::string topic_reported = topic + "/Reported"; + uic_mqtt_publish(topic_reported.c_str(), + payload_str.c_str(), + payload_str.length(), + true); + } + return SL_STATUS_OK; +} + +sl_status_t uic_mqtt_dotdot_unify_thermostat_operating_state_unretain( + const char *base_topic, + uic_mqtt_dotdot_attribute_publish_type_t publish_type) +{ + // clang-format on + std::string topic + = std::string(base_topic) + + "/UnifyThermostat/Attributes/OperatingState"; + + if ((publish_type == UCL_MQTT_PUBLISH_TYPE_DESIRED) + || (publish_type == UCL_MQTT_PUBLISH_TYPE_ALL)) { + std::string topic_desired = topic + "/Desired"; + uic_mqtt_publish(topic_desired.c_str(), NULL, 0, true); + } + if ((publish_type == UCL_MQTT_PUBLISH_TYPE_REPORTED) + || (publish_type == UCL_MQTT_PUBLISH_TYPE_ALL)) { + std::string topic_reported = topic + "/Reported"; + uic_mqtt_publish(topic_reported.c_str(), NULL, 0, true); + } + return SL_STATUS_OK; +} +// clang-format off + + +sl_status_t uic_mqtt_dotdot_unify_thermostat_init() +{ + std::string base_topic = "ucl/by-unid/+/+/"; + + std::string subscription_topic; + if(!uic_mqtt_dotdot_unify_thermostat_write_attributes_callback.empty()) { + subscription_topic = base_topic + "UnifyThermostat/Commands/WriteAttributes"; + uic_mqtt_subscribe(subscription_topic.c_str(), uic_mqtt_dotdot_on_unify_thermostat_WriteAttributes); + } + + if(!uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback.empty()) { + subscription_topic = base_topic + "UnifyThermostat/Commands/ForceReadAttributes"; + uic_mqtt_subscribe(subscription_topic.c_str(), uic_mqtt_dotdot_on_unify_thermostat_force_read_attributes); + } + + // Init the attributes for that cluster + uic_mqtt_dotdot_unify_thermostat_attributes_init(); + + uic_mqtt_dotdot_by_group_unify_thermostat_init(); + + return SL_STATUS_OK; +} + sl_status_t uic_mqtt_dotdot_init() { @@ -94952,6 +95215,10 @@ sl_status_t uic_mqtt_dotdot_init() { status_flag = uic_mqtt_dotdot_descriptor_init(); } + if (status_flag == SL_STATUS_OK) { + status_flag = uic_mqtt_dotdot_unify_thermostat_init(); + } + return status_flag; } @@ -95013,6 +95280,7 @@ void uic_mqtt_dotdot_publish_supported_commands( uic_mqtt_dotdot_aox_position_estimation_publish_supported_commands(unid, endpoint_id); uic_mqtt_dotdot_protocol_controller_network_management_publish_supported_commands(unid, 0); uic_mqtt_dotdot_descriptor_publish_supported_commands(unid, endpoint_id); + uic_mqtt_dotdot_unify_thermostat_publish_supported_commands(unid, endpoint_id); } void uic_mqtt_dotdot_publish_empty_supported_commands( @@ -95071,6 +95339,7 @@ void uic_mqtt_dotdot_publish_empty_supported_commands( uic_mqtt_dotdot_aox_position_estimation_publish_empty_supported_commands(unid, endpoint_id); uic_mqtt_dotdot_protocol_controller_network_management_publish_empty_supported_commands(unid); uic_mqtt_dotdot_descriptor_publish_empty_supported_commands(unid, endpoint_id); + uic_mqtt_dotdot_unify_thermostat_publish_empty_supported_commands(unid, endpoint_id); } // Publishing Cluster Revision for Basic Cluster @@ -108533,6 +108802,154 @@ void uic_mqtt_dotdot_descriptor_publish_empty_supported_commands( } } +// Publishing Cluster Revision for UnifyThermostat Cluster +void uic_mqtt_dotdot_unify_thermostat_publish_cluster_revision(const char* base_topic, uint16_t value) +{ + std::string cluster_topic = std::string(base_topic) + "/UnifyThermostat/Attributes/ClusterRevision"; + // Publish Desired + std::string pub_topic_des = cluster_topic + "/Desired"; + std::string payload = std::string(R"({"value": )") + + std::to_string(value) + std::string("}"); + uic_mqtt_publish(pub_topic_des.c_str(), + payload.c_str(), + payload.size(), + true); + // Publish Reported + std::string pub_topic_rep = cluster_topic + "/Reported"; + uic_mqtt_publish(pub_topic_rep.c_str(), + payload.c_str(), + payload.size(), + true); +} + +// Unretain Cluster Revision for UnifyThermostat Cluster +void uic_mqtt_dotdot_unify_thermostat_unretain_cluster_revision(const char* base_topic) +{ + // clang-format on + std::string cluster_topic + = std::string(base_topic) + + "/UnifyThermostat/Attributes/ClusterRevision"; + // Publish Desired + std::string desired_topic = cluster_topic + "/Desired"; + uic_mqtt_publish(desired_topic.c_str(), NULL, 0, true); + // Publish Reported + std::string reported_topic = cluster_topic + "/Reported"; + uic_mqtt_publish(reported_topic.c_str(), NULL, 0, true); + // clang-format off +} + + +static inline bool uic_mqtt_dotdot_unify_thermostat_write_attributes_is_supported( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id) +{ + for (const auto& callback: uic_mqtt_dotdot_unify_thermostat_write_attributes_callback) { + uic_mqtt_dotdot_unify_thermostat_state_t unify_thermostat_new_state = {}; + uic_mqtt_dotdot_unify_thermostat_updated_state_t unify_thermostat_new_updated_state = {}; + + if (callback( + unid, + endpoint_id, + UIC_MQTT_DOTDOT_CALLBACK_TYPE_SUPPORT_CHECK, + unify_thermostat_new_state, + unify_thermostat_new_updated_state + ) == SL_STATUS_OK) { + return true; + } + } + return false; +} + +static inline bool uic_mqtt_dotdot_unify_thermostat_force_read_attributes_is_supported( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id) +{ + for (const auto& callback: uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback) { + uic_mqtt_dotdot_unify_thermostat_updated_state_t unify_thermostat_force_update = {0}; + if (callback( + unid, + endpoint_id, + UIC_MQTT_DOTDOT_CALLBACK_TYPE_SUPPORT_CHECK, + unify_thermostat_force_update + ) == SL_STATUS_OK) { + return true; + } + } + return false; +} + +// Publishing Supported Commands for UnifyThermostat Cluster +void uic_mqtt_dotdot_unify_thermostat_publish_supported_commands( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id) +{ + std::stringstream ss; + bool first_command = true; + ss.str(""); + + // check if there is callback for each command + + // Check for a WriteAttributes Callback + if(uic_mqtt_dotdot_unify_thermostat_write_attributes_is_supported(unid, endpoint_id)) { + if (first_command == false) { + ss << ", "; + } + first_command = false; + ss << R"("WriteAttributes")"; + } + + // Check for a ForceReadAttributes Callback + if (uic_mqtt_dotdot_unify_thermostat_force_read_attributes_is_supported(unid, endpoint_id)) { + if (first_command == false) { + ss << ", "; + } + first_command = false; + ss << R"("ForceReadAttributes")"; + } + + // Publish supported commands + std::string topic = "ucl/by-unid/" + std::string(unid); + topic += "/ep"+ std::to_string(endpoint_id); + topic += "/UnifyThermostat/SupportedCommands"; + std::string payload_str("{\"value\": [" + ss.str() + "]" + "}"); + if (first_command == false) { + uic_mqtt_publish(topic.c_str(), + payload_str.c_str(), + payload_str.length(), + true); + } else if (uic_mqtt_count_topics(topic.c_str()) == 0) { + // There are no supported commands, but make sure we publish some + // SupportedCommands = [] if any attribute has been published for a cluster. + std::string attributes_topic = "ucl/by-unid/" + std::string(unid); + attributes_topic += "/ep"+ std::to_string(endpoint_id); + attributes_topic += "/UnifyThermostat/Attributes"; + + if (uic_mqtt_count_topics(attributes_topic.c_str()) > 0) { + uic_mqtt_publish(topic.c_str(), + EMPTY_VALUE_ARRAY, + strlen(EMPTY_VALUE_ARRAY), + true); + } + } +} + +// Publishing empty/no Supported Commands for UnifyThermostat Cluster +void uic_mqtt_dotdot_unify_thermostat_publish_empty_supported_commands( + const dotdot_unid_t unid + , dotdot_endpoint_id_t endpoint_id) +{ + std::string topic = "ucl/by-unid/" + std::string(unid); + topic += "/ep"+ std::to_string(endpoint_id); + topic += "/UnifyThermostat/SupportedCommands"; + + if (uic_mqtt_count_topics(topic.c_str()) > 0) { + uic_mqtt_publish(topic.c_str(), + EMPTY_VALUE_ARRAY, + strlen(EMPTY_VALUE_ARRAY), + true); + } +} + //////////////////////////////////////////////////////////////////////////////// // Generated Commands publications functions diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.hpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.hpp index 156e7b356..87e323a05 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.hpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt.hpp @@ -367,6 +367,13 @@ sl_status_t uic_mqtt_dotdot_by_group_aox_position_estimation_init(); */ sl_status_t uic_mqtt_dotdot_by_group_descriptor_init(); +/** + * @brief Initialize UnifyThermostat dotdot bygroup command handlers + * + * @returns SL_STATUS_OK on success, error otherwise. + */ +sl_status_t uic_mqtt_dotdot_by_group_unify_thermostat_init(); + // clang-format on @@ -5052,6 +5059,27 @@ void uic_mqtt_dotdot_on_descriptor_WriteAttributes( const size_t message_length); +// clang-format on + +/** + * @brief Retrieves the container with callback pointers for by-unid + * /Commands/WriteAttributes messages + * + * @returns std::set of callbacks. + */ +std::set & get_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback(); + +/** + * @brief MQTT Subscribe handler for incoming publications on: + * ucl/by-unid/+/+/UnifyThermostat/Commands/WriteAttributes + */ +// clang-format off +void uic_mqtt_dotdot_on_unify_thermostat_WriteAttributes( + const char *topic, + const char *message, + const size_t message_length); + + // All bitmaps are defined as the cluster label for the bitmap plus the command/attribute name diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_attributes.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_attributes.cpp index 43090fc98..a079b69c1 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_attributes.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_attributes.cpp @@ -63117,3 +63117,123 @@ void uic_mqtt_dotdot_descriptor_attribute_device_type_list_callback_set(const ui // End of supported cluster. +/////////////////////////////////////////////////////////////////////////////// +// Callback pointers for UnifyThermostat +/////////////////////////////////////////////////////////////////////////////// +static uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback_t uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback = nullptr; + +/////////////////////////////////////////////////////////////////////////////// +// Attribute update handlers for UnifyThermostat +/////////////////////////////////////////////////////////////////////////////// +static void uic_mqtt_dotdot_on_unify_thermostat_operating_state_attribute_update( + const char *topic, + const char *message, + const size_t message_length) { + if (uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback == nullptr) { + return; + } + + std::string unid; + uint8_t endpoint = 0; // Default value for endpoint-less topics. + if(! uic_dotdot_mqtt::parse_topic(topic,unid,endpoint)) { + sl_log_debug(LOG_TAG, + "Error parsing UNID / Endpoint ID from topic %s. Ignoring", + topic); + return; + } + + std::string last_item; + if (SL_STATUS_OK != uic_dotdot_mqtt::get_topic_last_item(topic,last_item)){ + sl_log_debug(LOG_TAG, + "Error parsing last item from topic %s. Ignoring", + topic); + return; + } + + uic_mqtt_dotdot_attribute_update_type_t update_type; + if (last_item == "Reported") { + update_type = UCL_REPORTED_UPDATED; + } else if (last_item == "Desired") { + update_type = UCL_DESIRED_UPDATED; + } else { + sl_log_debug(LOG_TAG, + "Unknown value type (neither Desired/Reported) for topic %s. Ignoring", + topic); + return; + } + + // Empty message means unretained value. + bool unretained = false; + if (message_length == 0) { + unretained = true; + } + + + uint8_t operating_state = {}; + + nlohmann::json json_payload; + try { + + if (unretained == false) { + json_payload = nlohmann::json::parse(std::string(message)); + + if (json_payload.find("value") == json_payload.end()) { + sl_log_debug(LOG_TAG, "UnifyThermostat::OperatingState: Missing attribute element: 'value'\n"); + return; + } +// Start parsing value + uint32_t tmp = get_enum_decimal_value("value", json_payload); + if (tmp == numeric_limits::max()) { + #ifdef UNIFY_THERMOSTAT_OPERATING_STATE_ENUM_NAME_AVAILABLE + tmp = unify_thermostat_operating_state_get_enum_value_number(json_payload.at("value").get()); + #elif defined(OPERATING_STATE_ENUM_NAME_AVAILABLE) + tmp = operating_state_get_enum_value_number(json_payload.at("value").get()); + #endif + } + operating_state = static_cast(tmp); + + // End parsing value + } + + } catch (const std::exception& e) { + sl_log_debug(LOG_TAG, LOG_FMT_JSON_ERROR, "value", message); + return; + } + + uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback( + static_cast(unid.c_str()), + endpoint, + unretained, + update_type, + operating_state + ); + +} + +/////////////////////////////////////////////////////////////////////////////// +// Attribute init functions for UnifyThermostat +/////////////////////////////////////////////////////////////////////////////// +sl_status_t uic_mqtt_dotdot_unify_thermostat_attributes_init() +{ + std::string base_topic = "ucl/by-unid/+/+/"; + + std::string subscription_topic; + if(uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback) { + subscription_topic = base_topic + "UnifyThermostat/Attributes/OperatingState/#"; + uic_mqtt_subscribe(subscription_topic.c_str(), &uic_mqtt_dotdot_on_unify_thermostat_operating_state_attribute_update); + } + + return SL_STATUS_OK; +} + + +/////////////////////////////////////////////////////////////////////////////// +// Callback setters and getters for UnifyThermostat +/////////////////////////////////////////////////////////////////////////////// +void uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback_set(const uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback_t callback) +{ + uic_mqtt_dotdot_unify_thermostat_attribute_operating_state_callback = callback; +} + +// End of supported cluster. + diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.cpp index 0ee153812..32da8bb9b 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.cpp @@ -14797,5 +14797,21 @@ void uic_mqtt_dotdot_parse_descriptor_write_attributes( +} + + +/** + * @brief JSON parser for ::WriteAttributes command arguments. + * + * Parse incoming JSON object to populate command arguments passed in by reference. + */ +void uic_mqtt_dotdot_parse_unify_thermostat_write_attributes( + nlohmann::json &jsn, + uic_mqtt_dotdot_unify_thermostat_state_t &new_state, + uic_mqtt_dotdot_unify_thermostat_updated_state_t &new_updated_state +) { + + + } diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.hpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.hpp index 21f137181..94e4e8594 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.hpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_command_helpers.hpp @@ -6071,6 +6071,18 @@ void uic_mqtt_dotdot_parse_descriptor_write_attributes( ); +/** + * @brief JSON parser for UnifyThermostat WriteAttributes command arguments. + * + * Parse incoming JSON object to populate command arguments passed in by reference. + */ +void uic_mqtt_dotdot_parse_unify_thermostat_write_attributes( + nlohmann::json &jsn, + uic_mqtt_dotdot_unify_thermostat_state_t &new_state, + uic_mqtt_dotdot_unify_thermostat_updated_state_t &new_updated_state +); + + #endif //DOTDOT_MQTT_COMMAND_HELPERS_HPP /** @} end dotdot_mqtt_command_helpers */ diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_generated_commands.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_generated_commands.cpp index a09f1a69c..e2f9193f6 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_generated_commands.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_generated_commands.cpp @@ -11396,3 +11396,43 @@ void uic_mqtt_dotdot_descriptor_publish_generated_write_attributes_command( false); } + + +/** + * @brief Publishes an incoming/generated WriteAttributes command for + * the UnifyThermostat cluster. + * + * Publication will be made at the following topic + * ucl/by-unid/UNID/epID/UnifyThermostat/GeneratedCommands/WriteAttributes + * + * @param unid The UNID of the node that sent us the command. + * + * @param endpoint The Endpoint ID of the node that sent us the command. + * + * @param attribute_values Values to assign to the attributes + * @param attribute_list List of attributes that are written + */ +void uic_mqtt_dotdot_unify_thermostat_publish_generated_write_attributes_command( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint, + uic_mqtt_dotdot_unify_thermostat_state_t attribute_values, + uic_mqtt_dotdot_unify_thermostat_updated_state_t attribute_list +){ + // Create the topic + std::string topic = "ucl/by-unid/"+ std::string(unid) + "/ep" + + std::to_string(endpoint) + "/"; + topic += "UnifyThermostat/GeneratedCommands/WriteAttributes"; + + nlohmann::json json_object = nlohmann::json::object(); + + + // Payload contains data from end nodes, which we cannot control, thus we handle if there are non-utf8 characters + std::string payload = json_object.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); + + // Publish our command + uic_mqtt_publish(topic.c_str(), + payload.c_str(), + payload.size(), + false); +} + diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_group_commands.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_group_commands.cpp index 5d85d166d..b0168f71a 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_group_commands.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_group_commands.cpp @@ -366,6 +366,9 @@ static uic_mqtt_dotdot_by_group_aox_position_estimation_write_attributes_callbac static uic_mqtt_dotdot_by_group_descriptor_write_attributes_callback_t uic_mqtt_dotdot_by_group_descriptor_write_attributes_callback = nullptr; +static uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback_t uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback = nullptr; + + // Callbacks setters @@ -1876,6 +1879,15 @@ void uic_mqtt_dotdot_by_group_descriptor_write_attributes_callback_set( +// Callbacks setters +void uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback_set( + const uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback_t callback) +{ + uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback = callback; +} + + + // Callback function for incoming publications on ucl/by-group/+/Basic/Commands/ResetToFactoryDefaults static void uic_mqtt_dotdot_on_by_group_basic_reset_to_factory_defaults( @@ -23492,6 +23504,91 @@ sl_status_t uic_mqtt_dotdot_by_group_descriptor_init() + +static void uic_mqtt_dotdot_on_by_group_unify_thermostat_WriteAttributes( + const char *topic, + const char *message, + const size_t message_length) +{ + + if ((group_dispatch_callback == nullptr) && (uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback == nullptr)) { + return; + } + if (message_length == 0) { + return; + } + + dotdot_group_id_t group_id = 0U; + if(!uic_dotdot_mqtt::parse_topic_group_id(topic,group_id)) { + sl_log_debug(LOG_TAG, + "Failed to parse GroupId from topic %s. Ignoring", + topic); + return; + } + + if ((group_dispatch_callback != nullptr) && (!get_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback().empty())) { + try { + group_dispatch_callback(group_id, + "UnifyThermostat", + "WriteAttributes", + message, + message_length, + uic_mqtt_dotdot_on_unify_thermostat_WriteAttributes); + + } catch (...) { + sl_log_debug(LOG_TAG, "UnifyThermostat: Unable to parse JSON payload.\n"); + return; + } + } else if (uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback != nullptr) { + + uic_mqtt_dotdot_unify_thermostat_state_t new_state = {}; + uic_mqtt_dotdot_unify_thermostat_updated_state_t new_updated_state = {}; + + + nlohmann::json jsn; + try { + jsn = nlohmann::json::parse(std::string(message)); + + uic_mqtt_dotdot_parse_unify_thermostat_write_attributes( + jsn, + new_state, + new_updated_state + ); + } catch (const nlohmann::json::parse_error& e) { + // Catch JSON object field parsing errors + sl_log_debug(LOG_TAG, LOG_FMT_JSON_PARSE_FAIL, "UnifyThermostat", "WriteAttributes"); + return; + } catch (const nlohmann::json::exception& e) { + // Catch JSON object field parsing errors + sl_log_debug(LOG_TAG, LOG_FMT_JSON_ERROR, "UnifyThermostat", "WriteAttributes", e.what()); + return; + } catch (const std::exception& e) { + sl_log_debug(LOG_TAG, LOG_FMT_JSON_ERROR, "UnifyThermostat", "WriteAttributes", ""); + return; + } + + uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback( + group_id, + new_state, + new_updated_state + ); + } +} + +sl_status_t uic_mqtt_dotdot_by_group_unify_thermostat_init() +{ + std::string subscription_topic; + const std::string topic_bygroup = TOPIC_BY_GROUP_PREFIX; + if(uic_mqtt_dotdot_by_group_unify_thermostat_write_attributes_callback) { + subscription_topic = topic_bygroup + "UnifyThermostat/Commands/WriteAttributes"; + uic_mqtt_subscribe(subscription_topic.c_str(), uic_mqtt_dotdot_on_by_group_unify_thermostat_WriteAttributes); + } + + return SL_STATUS_OK; +} + + + void uic_mqtt_dotdot_set_group_dispatch_callback(group_dispatch_t callback) { // Check for uninitialized value in order to subscribe with on_group handlers @@ -23773,6 +23870,8 @@ void uic_mqtt_dotdot_set_group_dispatch_callback(group_dispatch_t callback) uic_mqtt_subscribe("ucl/by-group/+/Descriptor/Commands/WriteAttributes", uic_mqtt_dotdot_on_by_group_descriptor_WriteAttributes); + uic_mqtt_subscribe("ucl/by-group/+/UnifyThermostat/Commands/WriteAttributes", uic_mqtt_dotdot_on_by_group_unify_thermostat_WriteAttributes); + } group_dispatch_callback = callback; diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_helpers.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_helpers.cpp index e777d585a..6a5b97ece 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_helpers.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_helpers.cpp @@ -5064,6 +5064,63 @@ uint32_t tx_report_transmission_speed_get_enum_value_number(const std::string &s return std::numeric_limits::max(); } +// Enum to string map for UnifyThermostatOperatingState +const std::map unify_thermostat_operating_state_enum_id_to_string_map { + { 0, "Off" }, + { 1, "Heating" }, + { 2, "Cooling" }, + { 3, "FanOnly" }, + { 4, "PendingHeat" }, + { 5, "PendingCool" }, + { 6, "Vent/Economizer" }, + { 7, "AuxHeating" }, + { 8, "2ndStageHeating" }, + { 9, "2ndStageCooling" }, + { 10, "2ndStageAuxHeat" }, + { 11, "3rdStageAuxHeat" }, +}; + +// String to enum map for UnifyThermostatOperatingState +const std::map unify_thermostat_operating_state_enum_string_to_id_map { + { "Off", 0 }, + { "Heating", 1 }, + { "Cooling", 2 }, + { "FanOnly", 3 }, + { "PendingHeat", 4 }, + { "PendingCool", 5 }, + { "Vent/Economizer", 6 }, + { "AuxHeating", 7 }, + { "2ndStageHeating", 8 }, + { "2ndStageCooling", 9 }, + { "2ndStageAuxHeat", 10 }, + { "3rdStageAuxHeat", 11 }, +}; + +std::string unify_thermostat_operating_state_get_enum_value_name( + uint32_t value) +{ + auto it = unify_thermostat_operating_state_enum_id_to_string_map.find(value); + if (it != unify_thermostat_operating_state_enum_id_to_string_map.end()){ + return it->second; + } + + // No known name value is set for this field. + // Set it to a string version of the value. + return std::to_string(value); +} + +uint32_t unify_thermostat_operating_state_get_enum_value_number(const std::string &str) +{ + auto it = unify_thermostat_operating_state_enum_string_to_id_map.find(str); + if (it != unify_thermostat_operating_state_enum_string_to_id_map.end()){ + return it->second; + } + + // No known numeric value is set for this string. + // Return UINT32_MAX to indicate an error. + return std::numeric_limits::max(); +} + // Enum to string map for WindowCoveringWindowCoveringType const std::map window_covering_window_covering_type_enum_id_to_string_map { { 0, "Rollershade" }, @@ -9908,6 +9965,15 @@ std::string get_enum_value_name( #endif } + if (64789 == cluster_id) { + #ifdef UNIFY_THERMOSTAT_OPERATING_STATE_ENUM_NAME_AVAILABLE + if (3 == attribute_id) { + // FIXME: Some attributes don't work because multi-upper case names end up like this: unify_thermostatoperating_state instead of this: unify_thermostat_operating_state + return unify_thermostat_operating_state_get_enum_value_name(value); + } + #endif + } + std::string value_name; return value_name; @@ -14376,6 +14442,15 @@ uint32_t get_enum_name_value( #endif } + if (64789 == cluster_id) { + #ifdef UNIFY_THERMOSTAT_OPERATING_STATE_ENUM_NAME_AVAILABLE + if (3 == attribute_id) { + // FIXME: Some attributes don't work because multi-upper case names end up like this: unify_thermostatoperating_state instead of this: unify_thermostat_operating_state + return unify_thermostat_operating_state_get_enum_value_number(name); + } + #endif + } + // No known numeric value is set for this string. // Return UINT32_MAX to indicate an error. @@ -15616,6 +15691,17 @@ uint32_t tx_report_transmission_speed_get_enum_value_number_c(const char *str) { return tx_report_transmission_speed_get_enum_value_number(std::string(str)); } +char *unify_thermostat_operating_state_get_enum_value_name_c( + uint32_t value, char *result, size_t max_result_size) +{ + snprintf(result, max_result_size, "%s", unify_thermostat_operating_state_get_enum_value_name(value).c_str()); + return result; +} + +uint32_t unify_thermostat_operating_state_get_enum_value_number_c(const char *str) +{ + return unify_thermostat_operating_state_get_enum_value_number(std::string(str)); +} char *window_covering_window_covering_type_get_enum_value_name_c( uint32_t value, char *result, size_t max_result_size) { diff --git a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_supported_generated_commands.cpp b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_supported_generated_commands.cpp index a2d5d754d..c5aa5ff47 100644 --- a/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_supported_generated_commands.cpp +++ b/components/uic_dotdot_mqtt/zap-generated/src/dotdot_mqtt_supported_generated_commands.cpp @@ -2812,3 +2812,46 @@ void uic_mqtt_dotdot_descriptor_publish_supported_generated_commands( } + +/** + * @brief Sends/Publishes a the SupportedGenerated commands for + * the UnifyThermostat cluster for a UNID/Endpoint + * + * Publication will be made at the following topic + * ucl/by-unid/UNID/epID/UnifyThermostat/SupportedGeneratedCommands + * + * @param unid The UNID of the node on behalf of which the advertisment is made + * + * @param endpoint The Endpoint ID of the node on behalf of which the advertisment is made + * + * @param command_list Struct pointer with the fields value indicating if + * individual commands can be generated. + */ +void uic_mqtt_dotdot_unify_thermostat_publish_supported_generated_commands( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint, + const uic_mqtt_dotdot_unify_thermostat_supported_commands_t *command_list) +{ + std::string topic = "ucl/by-unid/" + std::string(unid); + topic += "/ep"+ std::to_string(endpoint); + topic += "/UnifyThermostat/SupportedGeneratedCommands"; + + // Assemble of vector of strings for the Supported Commands: + std::vector command_vector; + if (command_list->write_attributes == true) { + command_vector.emplace_back("WriteAttributes"); + } + + // JSONify, then Stringify + nlohmann::json json_payload; + json_payload["value"] = command_vector; + std::string string_payload = json_payload.dump(); + + // Publish to MQTT + uic_mqtt_publish(topic.c_str(), + string_payload.c_str(), + string_payload.length(), + true); + +} + diff --git a/components/uic_dotdot_mqtt/zap-generated/test/dotdot_mqtt_test.include b/components/uic_dotdot_mqtt/zap-generated/test/dotdot_mqtt_test.include index 4884cdd28..824a87d6a 100644 --- a/components/uic_dotdot_mqtt/zap-generated/test/dotdot_mqtt_test.include +++ b/components/uic_dotdot_mqtt/zap-generated/test/dotdot_mqtt_test.include @@ -3649,6 +3649,7 @@ static void unset_all_callbacks() uic_mqtt_dotdot_protocol_controller_network_management_write_callback_clear(); uic_mqtt_dotdot_clear_protocol_controller_network_management_write_attributes_callbacks(); uic_mqtt_dotdot_clear_descriptor_write_attributes_callbacks(); + uic_mqtt_dotdot_clear_unify_thermostat_write_attributes_callbacks(); } static void reset_callback_counters() diff --git a/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes.uam b/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes.uam index 88967fac0..f2944bc86 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes.uam +++ b/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes.uam @@ -863,3 +863,6 @@ def DOTDOT_ATTRIBUTE_ID_PROTOCOL_CONTROLLER_NETWORK_MANAGEMENT_NETWORK_MANAGEMEN // This represents the attributes in the DotDot Descriptor cluster def DOTDOT_ATTRIBUTE_ID_DESCRIPTOR_DEVICE_TYPE_LIST 0xfd130000 +// This represents the attributes in the DotDot UnifyThermostat cluster +def DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE 0xfd150003 + diff --git a/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes_camel_case.uam b/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes_camel_case.uam index c4ef2c208..4d3667dba 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes_camel_case.uam +++ b/components/unify_dotdot_attribute_store/zap-generated/include/dotdot_attributes_camel_case.uam @@ -863,3 +863,6 @@ def zb_NetworkManagementState 0xfd120001 // This represents short CamelCase labels the attributes in the DotDot Descriptor cluster def zb_DeviceTypeList 0xfd130000 +// This represents short CamelCase labels the attributes in the DotDot UnifyThermostat cluster +def zb_OperatingState 0xfd150003 + diff --git a/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_attribute_store_helpers.h b/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_attribute_store_helpers.h index 16d75f051..db84c6b08 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_attribute_store_helpers.h +++ b/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_attribute_store_helpers.h @@ -77649,6 +77649,140 @@ bool dotdot_is_any_descriptor_writable_attribute_supported( const dotdot_unid_t unid, const dotdot_endpoint_id_t endpoint_id); +//////////////////////////////////////////////////////////////////////////////// +// Start of cluster UnifyThermostat +//////////////////////////////////////////////////////////////////////////////// +// UnifyThermostat OperatingState +/** + * @brief Verifies if the DotDot UnifyThermostat - OperatingState is supported + * under a UNID/EndpoinID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * + * @returns true if OperatingState is supported + * @returns false if OperatingState is not supported + */ +bool dotdot_is_supported_unify_thermostat_operating_state ( + const dotdot_unid_t unid, const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Gets the DotDot UnifyThermostat - OperatingState attribute value under a UNID/EndpoinID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @param value_state value state to get, + * see \ref attribute_store_get_node_attribute_value + * + * + * @returns OperatingState attribute + */ +uint8_t dotdot_get_unify_thermostat_operating_state( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id, + attribute_store_node_value_state_t value_state); + +/** + * @brief Set the DotDot UnifyThermostat - OperatingState attribute under a UNID/EndpoinID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @param value_state value state to write for the node, + * see \ref attribute_store_set_node_attribute_value + * + * @param new_operating_state new value to set + * @returns sl_status_t SL_STATUS_OK on success + */ +sl_status_t dotdot_set_unify_thermostat_operating_state( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id, + attribute_store_node_value_state_t value_state, + uint8_t new_operating_state + ); + +/** + * @brief Undefines the Reported value of the the DotDot UnifyThermostat - OperatingState + * attribute under a UNID/EndpoinID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns sl_status_t SL_STATUS_OK on success + */ +sl_status_t dotdot_unify_thermostat_operating_state_undefine_reported( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Undefines the Desired value of the DotDot + * UnifyThermostat - OperatingState attribute under a UNID/EndpointID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns sl_status_t SL_STATUS_OK on success + */ +sl_status_t dotdot_unify_thermostat_operating_state_undefine_desired( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Checks if the reported value is defined for the DotDot + * UnifyThermostat - OperatingState attribute under a UNID/EndpointID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns true if defined, false is undefined or non-existent + */ +bool dotdot_unify_thermostat_operating_state_is_reported_defined( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Checks if the desired value is defined for the DotDot + * UnifyThermostat - OperatingState attribute under a UNID/EndpointID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns true if defined, false is undefined or non-existent + */ +bool dotdot_unify_thermostat_operating_state_is_desired_defined( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Creates a DotDot UnifyThermostat - OperatingState attribute under a UNID/EndpoinID + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns sl_status_t SL_STATUS_OK on success + */ +sl_status_t dotdot_create_unify_thermostat_operating_state( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Checks if a UNID/Endpoint supports any attribute for the UnifyThermostat + * Cluster + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns true if at least 1 attribute in the Attribute Store, false otherwise + */ +bool dotdot_is_any_unify_thermostat_attribute_supported( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + +/** + * @brief Checks if a UNID/Endpoint supports any writable attribute for the + * UnifyThermostat Cluster + * + * @param unid Node's UNID + * @param endpoint_id Endpoint ID + * @returns true if at least 1 writable attribute in the Attribute Store, false otherwise + */ +bool dotdot_is_any_unify_thermostat_writable_attribute_supported( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id); + #ifdef __cplusplus } #endif // __cplusplus diff --git a/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_defined_attribute_types.h b/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_defined_attribute_types.h index 0118d5ba2..c8228fe1f 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_defined_attribute_types.h +++ b/components/unify_dotdot_attribute_store/zap-generated/include/unify_dotdot_defined_attribute_types.h @@ -794,6 +794,8 @@ DEFINE_ATTRIBUTE(DOTDOT_ATTRIBUTE_ID_AOX_POSITION_ESTIMATION_POSITION , 0xfd1100 DEFINE_ATTRIBUTE(DOTDOT_ATTRIBUTE_ID_PROTOCOL_CONTROLLER_NETWORK_MANAGEMENT_NETWORK_MANAGEMENT_STATE , 0xfd120001) // Attribute Defines for Descriptor DEFINE_ATTRIBUTE(DOTDOT_ATTRIBUTE_ID_DESCRIPTOR_DEVICE_TYPE_LIST , 0xfd130000) +// Attribute Defines for UnifyThermostat +DEFINE_ATTRIBUTE(DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE , 0xfd150003) // Additional manually defined types: diff --git a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_attribute_publisher.cpp b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_attribute_publisher.cpp index 2aba63453..1f6d1186d 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_attribute_publisher.cpp +++ b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_attribute_publisher.cpp @@ -25633,6 +25633,221 @@ static void descriptor_cluster_cluster_revision_callback( } +/** + * @brief Publishes the desired value of an updated attribute store node for + * the UnifyThermostat cluster. + * @param updated_node Updated attribute store node + * @param change Type of change applied + */ +static void unify_thermostat_cluster_publish_desired_value_callback( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + // clang-format on + if (false == is_publish_desired_attribute_values_to_mqtt_enabled()) { + return; + } + if (change == ATTRIBUTE_DELETED || change == ATTRIBUTE_CREATED) { + return; + } + // Scene exception: check that the attribute is not under the Scene Table extension, which is a config and not the node's state. + if (ATTRIBUTE_STORE_INVALID_NODE + != attribute_store_get_first_parent_with_type( + updated_node, + DOTDOT_ATTRIBUTE_ID_SCENES_SCENE_TABLE)) { + return; + } + + // Get the UNID and EndPoint, and prepare the basic topic + char unid[MAXIMUM_UNID_SIZE] = {}; + // clang-format off + // clang-format on + dotdot_endpoint_id_t endpoint_id = 0; + if (SL_STATUS_OK + != unify_dotdot_attributes_get_unid_endpoint()(updated_node, + unid, + &endpoint_id)) { + return; + } + // clang-format off + // clang-format on + + std::string base_topic = "ucl/by-unid/" + std::string(unid); + // clang-format off + base_topic += "/ep" + std::to_string(endpoint_id); + // clang-format on + + attribute_store_type_t type = attribute_store_get_node_type(updated_node); + if (type == ATTRIBUTE_STORE_INVALID_ATTRIBUTE_TYPE) { + sl_log_debug(LOG_TAG, + "Warning: Invalid type for Attribute ID %d, " + "this should not happen.", + updated_node); + return; + } + + // If the value got updated but both Reported and Desired undefined, we skip publication + if (false == attribute_store_is_reported_defined(updated_node) + && false == attribute_store_is_desired_defined(updated_node)) { + sl_log_debug(LOG_TAG, + "Reported/Desired values are undefined. " + "Skipping publication"); + return; + } + + // clang-format off + try { + attribute_store::attribute attr(updated_node); + if (type == DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE) { + uic_mqtt_dotdot_unify_thermostat_operating_state_publish( + base_topic.c_str(), + static_cast(attr.desired_or_reported()), + UCL_MQTT_PUBLISH_TYPE_DESIRED); + return; + } + } catch (std::exception &ex) { + sl_log_warning(LOG_TAG, "Failed to publish the Desired attribute value: %s", ex.what()); + } +} + +/** + * @brief Publishes the reported value of an updated attribute store node for + * the UnifyThermostat cluster. + * @param updated_node Updated attribute store node + * @param change Type of change applied + */ +static void unify_thermostat_cluster_publish_reported_value_callback( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + // clang-format on + if (false == is_publish_reported_attribute_values_to_mqtt_enabled()) { + return; + } + if (change == ATTRIBUTE_CREATED) { + return; + } + // Scene exception: check that the attribute is not under the Scene Table extension, which is a config and not the node's state. + if (ATTRIBUTE_STORE_INVALID_NODE + != attribute_store_get_first_parent_with_type( + updated_node, + DOTDOT_ATTRIBUTE_ID_SCENES_SCENE_TABLE)) { + return; + } + + // Get the UNID and EndPoint, and prepare the basic topic + char unid[MAXIMUM_UNID_SIZE] = {}; + // clang-format off + // clang-format on + dotdot_endpoint_id_t endpoint_id = 0; + if (SL_STATUS_OK + != unify_dotdot_attributes_get_unid_endpoint()(updated_node, + unid, + &endpoint_id)) { + return; + } + // clang-format off + // clang-format on + + std::string base_topic = "ucl/by-unid/" + std::string(unid); + // clang-format off + base_topic += "/ep" + std::to_string(endpoint_id); + // clang-format on + + attribute_store_type_t type = attribute_store_get_node_type(updated_node); + if (type == ATTRIBUTE_STORE_INVALID_ATTRIBUTE_TYPE) { + sl_log_debug(LOG_TAG, + "Warning: Invalid type for Attribute ID %d, " + "this should not happen.", + updated_node); + return; + } + + // Deletion case: + if (change == ATTRIBUTE_DELETED) { + // clang-format off + switch(type) { + case DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE: + // clang-format on + sl_log_debug(LOG_TAG, + "Unretaining UnifyThermostat::OperatingState under topic %s", + base_topic.c_str()); + // clang-format off + uic_mqtt_dotdot_unify_thermostat_operating_state_unretain(base_topic.c_str(), UCL_MQTT_PUBLISH_TYPE_ALL); + break; + default: + break; + } + // clang-format on + return; + } + + // If the value got updated but undefined, we skip publication + if (false == attribute_store_is_reported_defined(updated_node)) { + sl_log_debug(LOG_TAG, "Reported value is undefined. Skipping publication"); + return; + } + + // Else we assume update case: + // clang-format off + try { + attribute_store::attribute attr(updated_node); + if (type == DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE) { + uic_mqtt_dotdot_unify_thermostat_operating_state_publish( + base_topic.c_str(), + static_cast(attr.reported()), + (attr.desired_exists() && !attribute_store_is_value_matched(updated_node)) ? UCL_MQTT_PUBLISH_TYPE_REPORTED : UCL_MQTT_PUBLISH_TYPE_ALL); + return; + } + } catch (std::exception &ex) { + sl_log_warning(LOG_TAG, "Failed to publish the Reported attribute value: %s", ex.what()); + } +} + +static void unify_thermostat_cluster_cluster_revision_callback( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + // clang-format on + if (false == is_publish_reported_attribute_values_to_mqtt_enabled()) { + return; + } + + // Get the UNID and EndPoint, and prepare the basic topic + char unid[MAXIMUM_UNID_SIZE] = {}; + dotdot_endpoint_id_t endpoint_id = 0; + // clang-format off + // clang-format on + if (SL_STATUS_OK + != unify_dotdot_attributes_get_unid_endpoint()(updated_node, + unid, + &endpoint_id)) { + return; + } + // clang-format off + // clang-format on + + std::string base_topic = "ucl/by-unid/" + std::string(unid); + // clang-format off + base_topic += "/ep" + std::to_string(endpoint_id); + + if ((change == ATTRIBUTE_CREATED) || (change == ATTRIBUTE_UPDATED)) { + // On attribute creation, make sure to publish the attribute revision for the first time + std::string cluster_revision_topic = base_topic + "/UnifyThermostat/Attributes/ClusterRevision"; + if (uic_mqtt_count_topics(cluster_revision_topic.c_str()) == 0) { + uic_mqtt_dotdot_unify_thermostat_publish_cluster_revision(base_topic.c_str(), 1); + } + } + + if (change == ATTRIBUTE_DELETED) { + // Check if we just erased the last attribute under a cluster, if yes, unretain + // the Cluster revision too. + if (false == dotdot_is_any_unify_thermostat_attribute_supported(unid, endpoint_id)) { + base_topic += "/UnifyThermostat"; + sl_log_debug(LOG_TAG, "No more attributes supported for UnifyThermostat cluster for UNID %s Endpoint %d. Unretaining leftover topics at %s",unid, endpoint_id, base_topic.c_str()); + uic_mqtt_unretain(base_topic.c_str()); + } + } +} + + // Initialization of the component. sl_status_t unify_dotdot_attribute_store_attribute_publisher_init() @@ -35675,6 +35890,20 @@ sl_status_t unify_dotdot_attribute_store_attribute_publisher_init() attribute_store_register_callback_by_type( descriptor_cluster_cluster_revision_callback, DOTDOT_ATTRIBUTE_ID_DESCRIPTOR_DEVICE_TYPE_LIST); + //Desired attribute state + attribute_store_register_callback_by_type_and_state( + unify_thermostat_cluster_publish_desired_value_callback, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE, + DESIRED_ATTRIBUTE); + //Reported attribute state + attribute_store_register_callback_by_type_and_state( + unify_thermostat_cluster_publish_reported_value_callback, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE, + REPORTED_ATTRIBUTE); + //registering a callback when an attribute is created for publishing cluster revision + attribute_store_register_callback_by_type( + unify_thermostat_cluster_cluster_revision_callback, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); return SL_STATUS_OK; } diff --git a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_force_read_attributes_command_callbacks.c b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_force_read_attributes_command_callbacks.c index 5d488fb5a..5661e49b0 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_force_read_attributes_command_callbacks.c +++ b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_force_read_attributes_command_callbacks.c @@ -4499,6 +4499,36 @@ static sl_status_t uic_mqtt_dotdot_descriptor_force_read_attributes_callback ( } return SL_STATUS_OK; } +//////////////////////////////////////////////////////////////////////////////// +// Start of cluster UnifyThermostat +//////////////////////////////////////////////////////////////////////////////// +static sl_status_t uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback ( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id, + uic_mqtt_dotdot_callback_call_type_t call_type, + uic_mqtt_dotdot_unify_thermostat_updated_state_t attribute_list) { + + if (false == is_force_read_attributes_enabled()){ + return SL_STATUS_FAIL; + } + + if (call_type == UIC_MQTT_DOTDOT_CALLBACK_TYPE_SUPPORT_CHECK) { + if (is_automatic_deduction_of_supported_commands_enabled()) { + return dotdot_is_any_unify_thermostat_attribute_supported(unid, endpoint_id) ? + SL_STATUS_OK : SL_STATUS_FAIL; + } else { + return SL_STATUS_FAIL; + } + } + + // Go and undefine everything that needs to be read again: + if (true == attribute_list.operating_state) { + if (SL_STATUS_OK == dotdot_unify_thermostat_operating_state_undefine_reported(unid, endpoint_id)) { + sl_log_debug(LOG_TAG, "Undefined Reported value of UnifyThermostat::OperatingState under %s - Endpoint %d", unid, endpoint_id); + } + } + return SL_STATUS_OK; +} // clang-format on //////////////////////////////////////////////////////////////////////////////// @@ -4609,6 +4639,8 @@ sl_status_t uic_mqtt_dotdot_set_descriptor_force_read_attributes_callback(&uic_mqtt_dotdot_descriptor_force_read_attributes_callback); + uic_mqtt_dotdot_set_unify_thermostat_force_read_attributes_callback(&uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback); + // clang-format on return SL_STATUS_OK; diff --git a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_helpers.cpp b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_helpers.cpp index e170bb45b..67ea8768f 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_helpers.cpp +++ b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_helpers.cpp @@ -83593,5 +83593,138 @@ bool dotdot_is_any_descriptor_writable_attribute_supported( const dotdot_endpoint_id_t endpoint_id) { + return false; +} +//////////////////////////////////////////////////////////////////////////////// +// Start of cluster UnifyThermostat +//////////////////////////////////////////////////////////////////////////////// +bool dotdot_is_supported_unify_thermostat_operating_state( + const dotdot_unid_t unid, const dotdot_endpoint_id_t endpoint_id) +{ + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + return attribute_store_node_exists(node); +} + +uint8_t dotdot_get_unify_thermostat_operating_state( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id, + attribute_store_node_value_state_t value_state) +{ + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + + uint8_t result = {}; + attribute_store_read_value(node, + value_state, + (uint8_t *)&result, + sizeof(result)); + return result; +} + +sl_status_t dotdot_set_unify_thermostat_operating_state( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id, + attribute_store_node_value_state_t value_state, + uint8_t new_operating_state + ) +{ + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + + return attribute_store_set_node_attribute_value(node, + value_state, + (uint8_t *)&new_operating_state, + sizeof(uint8_t)); + } + +sl_status_t dotdot_unify_thermostat_operating_state_undefine_reported( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) { + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + attribute_store_undefine_reported(node); + return (node != ATTRIBUTE_STORE_INVALID_NODE) ? SL_STATUS_OK : SL_STATUS_FAIL; +} + +sl_status_t dotdot_unify_thermostat_operating_state_undefine_desired( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) { + + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + attribute_store_undefine_desired(node); + return (node != ATTRIBUTE_STORE_INVALID_NODE) ? SL_STATUS_OK : SL_STATUS_FAIL; +} + + +bool dotdot_unify_thermostat_operating_state_is_reported_defined( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) +{ + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + return attribute_store_is_reported_defined(node); +} + +bool dotdot_unify_thermostat_operating_state_is_desired_defined( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) +{ + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node + = attribute_store_get_first_child_by_type( + endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + return attribute_store_is_desired_defined(node); +} + +sl_status_t dotdot_create_unify_thermostat_operating_state( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) { + + attribute_store_node_t endpoint_node = unify_dotdot_attributes_get_endpoint_node()(unid, endpoint_id); + attribute_store_node_t node = + attribute_store_create_child_if_missing(endpoint_node, + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE); + + return (node != ATTRIBUTE_STORE_INVALID_NODE) ? SL_STATUS_OK : SL_STATUS_FAIL; +} + +bool dotdot_is_any_unify_thermostat_attribute_supported( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) { + + if (true == dotdot_is_supported_unify_thermostat_operating_state(unid, endpoint_id)) { + return true; + } + + return false; +} + +bool dotdot_is_any_unify_thermostat_writable_attribute_supported( + const dotdot_unid_t unid, + const dotdot_endpoint_id_t endpoint_id) { + + return false; } diff --git a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_registration.cpp b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_registration.cpp index 0423e9305..566b563cb 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_registration.cpp +++ b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_registration.cpp @@ -17311,6 +17311,30 @@ sl_status_t unify_dotdot_attribute_store_registration_init() // clang-format off // clang-format on + { + // enum8 // enum8 // uint8_t + std::string attribute_type_string = "uint8_t"; + attribute_store_storage_type_t storage_type = UNKNOWN_STORAGE_TYPE; + + // clang-format off + storage_type = attribute_storage_type_conversion(attribute_type_string); + + if (storage_type == UNKNOWN_STORAGE_TYPE) { + sl_log_warning(LOG_TAG, + "Unkown storage type for ZCL UnifyThermostat OperatingState, " + "type: enum8 // uint8_t"); + } + + status |= attribute_store_register_type( + DOTDOT_ATTRIBUTE_ID_UNIFY_THERMOSTAT_OPERATING_STATE, + "ZCL UnifyThermostat OperatingState", + ATTRIBUTE_STORE_INVALID_ATTRIBUTE_TYPE, + storage_type); + } + + // clang-format off + // clang-format on + // Additional attributes: for (auto const &a: zcl_additional_attribute_schema) { status |= attribute_store_register_type(a.type, diff --git a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_write_attributes_command_callbacks.c b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_write_attributes_command_callbacks.c index f45d19784..d607a5be7 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_write_attributes_command_callbacks.c +++ b/components/unify_dotdot_attribute_store/zap-generated/src/unify_dotdot_attribute_store_write_attributes_command_callbacks.c @@ -2585,6 +2585,36 @@ static sl_status_t descriptor_cluster_write_attributes_callback( endpoint_id); return SL_STATUS_OK; } +//////////////////////////////////////////////////////////////////////////////// +// Start of cluster UnifyThermostat +//////////////////////////////////////////////////////////////////////////////// +// WriteAttribute Callbacks unify_thermostat +static sl_status_t unify_thermostat_cluster_write_attributes_callback( + const dotdot_unid_t unid, + dotdot_endpoint_id_t endpoint_id, + uic_mqtt_dotdot_callback_call_type_t call_type, + uic_mqtt_dotdot_unify_thermostat_state_t attributes, + uic_mqtt_dotdot_unify_thermostat_updated_state_t updated_attributes) +{ + if (false == is_write_attributes_enabled()) { + return SL_STATUS_FAIL; + } + + if (call_type == UIC_MQTT_DOTDOT_CALLBACK_TYPE_SUPPORT_CHECK) { + if (is_automatic_deduction_of_supported_commands_enabled()) { + return dotdot_is_any_unify_thermostat_writable_attribute_supported(unid, endpoint_id) ? + SL_STATUS_OK : SL_STATUS_FAIL; + } else { + return SL_STATUS_FAIL; + } + } + + sl_log_debug(LOG_TAG, + "unify_thermostat: Incoming WriteAttributes command for %s, endpoint %d.\n", + unid, + endpoint_id); + return SL_STATUS_OK; +} // clang-format on //////////////////////////////////////////////////////////////////////////////// @@ -2752,6 +2782,9 @@ sl_status_t uic_mqtt_dotdot_set_descriptor_write_attributes_callback( &descriptor_cluster_write_attributes_callback); + uic_mqtt_dotdot_set_unify_thermostat_write_attributes_callback( + &unify_thermostat_cluster_write_attributes_callback); + // clang-format on return SL_STATUS_OK; diff --git a/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.c b/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.c index fda0a48f1..7cec51481 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.c +++ b/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.c @@ -1312,6 +1312,16 @@ uic_mqtt_dotdot_descriptor_write_attributes_callback_t get_uic_mqtt_dotdot_descr return test_uic_mqtt_dotdot_descriptor_write_attributes_callback; } +static uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t test_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback = NULL; +static uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t test_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback = NULL; + +uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t get_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback(){ + return test_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback; +} +uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t get_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback(){ + return test_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback; +} + // clang-format on #define TEST_UNID "test-unid-123" @@ -2878,6 +2888,16 @@ void set_uic_mqtt_dotdot_descriptor_write_attributes_callback_stub( { test_uic_mqtt_dotdot_descriptor_write_attributes_callback = callback; } +void set_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_stub( + const uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t callback, int cmock_num_calls) +{ + test_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback = callback; +} +void set_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_stub( + const uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t callback, int cmock_num_calls) +{ + test_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback = callback; +} // clang-format on // Test functions @@ -3798,6 +3818,12 @@ void setUp() test_uic_mqtt_dotdot_descriptor_write_attributes_callback = NULL; uic_mqtt_dotdot_set_descriptor_write_attributes_callback_Stub( &set_uic_mqtt_dotdot_descriptor_write_attributes_callback_stub); + test_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback = NULL; + uic_mqtt_dotdot_set_unify_thermostat_force_read_attributes_callback_Stub( + &set_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_stub); + test_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback = NULL; + uic_mqtt_dotdot_set_unify_thermostat_write_attributes_callback_Stub( + &set_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_stub); // clang-format on group_command_dispatch = NULL; @@ -4560,6 +4586,7 @@ void test_automatic_deduction_of_supported_commands() TEST_ASSERT_EQUAL(SL_STATUS_OK, dotdot_create_aox_position_estimation_position(expected_unid,expected_endpoint_id) ); TEST_ASSERT_EQUAL(SL_STATUS_OK, dotdot_create_protocol_controller_network_management_network_management_state(expected_unid,expected_endpoint_id) ); TEST_ASSERT_EQUAL(SL_STATUS_OK, dotdot_create_descriptor_device_type_list(expected_unid,expected_endpoint_id) ); + TEST_ASSERT_EQUAL(SL_STATUS_OK, dotdot_create_unify_thermostat_operating_state(expected_unid,expected_endpoint_id) ); // clang-format on // ColorControl checks the value in the bitmask: diff --git a/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.h b/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.h index 5d7b13dc5..ec8e607e0 100644 --- a/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.h +++ b/components/unify_dotdot_attribute_store/zap-generated/test/unify_dotdot_attribute_store_test.h @@ -793,4 +793,8 @@ uic_mqtt_dotdot_descriptor_force_read_attributes_callback_t get_uic_mqtt_dotdot_descriptor_force_read_attributes_callback(); uic_mqtt_dotdot_descriptor_write_attributes_callback_t get_uic_mqtt_dotdot_descriptor_write_attributes_callback(); + + uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback_t get_uic_mqtt_dotdot_unify_thermostat_force_read_attributes_callback(); + uic_mqtt_dotdot_unify_thermostat_write_attributes_callback_t get_uic_mqtt_dotdot_unify_thermostat_write_attributes_callback(); + #endif // UNIFY_DOTDOT_ATTRIBUTE_STORE_TEST_H \ No newline at end of file From 603a2c8ac4aec2604c7e3f149cd4c1b51f8d7a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Labb=C3=A9?= Date: Thu, 13 Jun 2024 10:26:26 +0200 Subject: [PATCH 3/4] GH-31: Thermostat Operating State CC Forwarded: https://github.com/SiliconLabs/UnifySDK/pull/31 Bug-SiliconLabs: UIC-3071 Bug-Github: https://github.com/SiliconLabs/UnifySDK/pull/31 --- .../attribute_store_defined_attribute_types.h | 33 + ...d_class_thermostat_operating_state_types.h | 41 ++ .../zpc_attribute_store_type_registration.cpp | 16 + .../zwave_command_classes/CMakeLists.txt | 1 + ...command_class_thermostat_operating_state.c | 452 ++++++++++++++ ...command_class_thermostat_operating_state.h | 40 ++ .../src/zwave_command_classes_fixt.c | 2 + .../zwave_command_classes/test/CMakeLists.txt | 15 + ...nd_class_thermostat_operating_state_test.c | 580 ++++++++++++++++++ 9 files changed, 1180 insertions(+) create mode 100644 applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_operating_state_types.h create mode 100644 applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.c create mode 100644 applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_operating_state.h create mode 100644 applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_operating_state_test.c 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..12a95d457 --- /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 Thermostat Operating State Command Class + * @ingroup zpc_attribute_store_command_classes_types + * @brief Type definitions for the Thermostat Operating State 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..be71eac1a --- /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_V2; + handler.command_class_name = "Thermostat Operating State"; + handler.comments = "Experimental. Log related functions are not yet exposed to MQTT."; + + 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..85bc3e34a --- /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_V2, 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 From 06032be60d3a091f900f53287db325cf65a11025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Labb=C3=A9?= Date: Thu, 13 Jun 2024 10:26:47 +0200 Subject: [PATCH 4/4] GH-31: Thermostat UAM file --- .../dotdot_mapper/rules/Thermostat.uam | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam b/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam index 233aaeaea..b75a1959d 100644 --- a/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam +++ b/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam @@ -21,6 +21,9 @@ def zwTHERMOSTAT_MODE_VERSION 0x4001 def zwTHERMOSTAT_MODE 0x4002 def zwTHERMOSTAT_SUPPORTED_MODES 0x4003 +//Thermostat Operating State CC +def zwTHERMOSTAT_OPERATING_STATE_MODE 0x4202 + // Thermostat Cluster def zb_LocalTemperature 0x02010000 def zb_HVACSystemTypeConfiguration 0x02010009 @@ -40,6 +43,7 @@ def zb_SystemMode 0x0201001c def zb_TemperatureSetpointHold 0x02010023 def zb_TemperatureSetpointHoldDuration 0x02010024 def zb_ThermostatProgrammingOperationMode 0x02010025 +def zb_ThermostatRunningState 0x02010029 def zb_OccupiedSetback 0x02010034 def zb_OccupiedSetbackMin 0x02010035 def zb_OccupiedSetbackMax 0x02010036 @@ -54,7 +58,11 @@ def zb_ACLouverPosition 0x02010045 def zb_ACCoilTemperature 0x02010046 def zb_ACCapacityFormat 0x02010047 +// Unify thermostat cluster +def zb_OperatingState 0xfd150003 + def thermostat_setpoint_supported (e'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE_SCALE | e'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE_SCALE) +def no_thermostat_operating_state (e'zwTHERMOSTAT_OPERATING_STATE_MODE == 0) scope 0 { // We map Setpoint setpoint_type 0x01 (HEATING) and 0x02 (COOLING) @@ -252,4 +260,21 @@ scope 0 chain_reaction(0) { d'zb_ACCapacityFormat = if (r'zb_ACCapacityFormat != d'zb_ACCapacityFormat) r'zb_ACCapacityFormat undefined + + // Thermostat Operating State + // UCL bindings + r'zb_ThermostatRunningState = + if (no_thermostat_operating_state) undefined + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x00) 0x00 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x01) 0x01 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x02) 0x02 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x03) 0x04 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x08) 0x08 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x09) 0x10 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x0A) 0x20 + if (r'zwTHERMOSTAT_OPERATING_STATE_MODE == 0x0B) 0x40 + undefined // Default state + + // Custom cluster binding + r'zb_OperatingState = r'zwTHERMOSTAT_OPERATING_STATE_MODE }