diff --git a/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam b/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam index 233aaeaea1..830d2d810d 100644 --- a/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam +++ b/applications/zpc/components/dotdot_mapper/rules/Thermostat.uam @@ -6,17 +6,6 @@ def zwSENSOR_MULTILEVEL_SCALE 0x3104 def zwSENSOR_MULTILEVEL_SENSOR_VALUE 0x3105 def zwMULTILEVEL_SUPPORTED_SENSOR_TYPES 0x3106 -//Thermostat setpoint CC -def zwTHERMOSTAT_SETPOINT_VERSION 0x4301 -def zwTHERMOSTAT_SUPPORTED_SETPOINT_TYPES 0x4302 -def zwTHERMOSTAT_SETPOINT_TYPE 0x4303 -def zwTHERMOSTAT_SETPOINT_VALUE 0x4304 -def zwTHERMOSTAT_SETPOINT_VALUE_SCALE 0x4305 -def zwTHERMOSTAT_SETPOINT_MIN_VALUE 0x4306 -def zwTHERMOSTAT_SETPOINT_MIN_VALUE_SCALE 0x4307 -def zwTHERMOSTAT_SETPOINT_MAX_VALUE 0x4308 -def zwTHERMOSTAT_SETPOINT_MAX_VALUE_SCALE 0x4309 - def zwTHERMOSTAT_MODE_VERSION 0x4001 def zwTHERMOSTAT_MODE 0x4002 def zwTHERMOSTAT_SUPPORTED_MODES 0x4003 @@ -54,75 +43,10 @@ def zb_ACLouverPosition 0x02010045 def zb_ACCoilTemperature 0x02010046 def zb_ACCapacityFormat 0x02010047 -def thermostat_setpoint_supported (e'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE_SCALE | e'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE_SCALE) - scope 0 { - // We map Setpoint setpoint_type 0x01 (HEATING) and 0x02 (COOLING) - // The Z-Wave units are converted into milli units, in ZigBee is should be - // deci-celsius. - // A value scale of 1 means fahrenheit - - // Heating - r'zb_OccupiedHeatingSetpoint = - if( r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 0 ) - (r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE / 10) - if( r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 1 ) - (((r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE - 32000) * 5) / 90) - undefined - - d'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE = - if( r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 0 ) - (d'zb_OccupiedHeatingSetpoint*10) - if( r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 1 ) - (((d'zb_OccupiedHeatingSetpoint)*90) / 5 + 32000) - undefined - - r'zb_MinHeatSetpointLimit = - if(r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MIN_VALUE_SCALE == 0) - (r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MIN_VALUE / 10) - if(r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MIN_VALUE_SCALE == 1) - (((r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MIN_VALUE - 32000) * 5) / 90) - undefined - - r'zb_MaxHeatSetpointLimit = - if(r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MAX_VALUE_SCALE == 0) - (r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MAX_VALUE / 10) - if(r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MAX_VALUE_SCALE == 1) - (((r'zwTHERMOSTAT_SETPOINT_TYPE[1].zwTHERMOSTAT_SETPOINT_MAX_VALUE - 32000) * 5) / 90) - undefined - - // Cooling - r'zb_OccupiedCoolingSetpoint = - if( r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 0) - (r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE / 10 ) - if( r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 1) - (((r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE - 32000) * 5) / 90) - undefined - - r'zb_MinCoolSetpointLimit = - if(r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MIN_VALUE_SCALE == 0) - (r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MIN_VALUE / 10) - if(r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MIN_VALUE_SCALE == 1) - (((r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MIN_VALUE - 32000) * 5) / 90) - undefined - - r'zb_MaxCoolSetpointLimit = - if(r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MAX_VALUE_SCALE == 0) - (r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MAX_VALUE / 10) - if(r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MAX_VALUE_SCALE == 1) - (((r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_MAX_VALUE - 32000) * 5) / 90) - undefined - - d'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE = - if( r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 0) - (d'zb_OccupiedCoolingSetpoint*10) - if( r'zwTHERMOSTAT_SETPOINT_TYPE[2].zwTHERMOSTAT_SETPOINT_VALUE_SCALE == 1 ) - (((d'zb_OccupiedCoolingSetpoint)*90) / 5 + 32000) - undefined - // Local Temperature r'zb_LocalTemperature = - if(thermostat_setpoint_supported == 0) undefined + // if(thermostat_setpoint_supported == 0) undefined if(r'zwSENSOR_MULTILEVEL_SENSOR_TYPE[1].zwSENSOR_MULTILEVEL_SCALE == 0) (r'zwSENSOR_MULTILEVEL_SENSOR_TYPE[1].zwSENSOR_MULTILEVEL_SENSOR_VALUE / 10) if(r'zwSENSOR_MULTILEVEL_SENSOR_TYPE[1].zwSENSOR_MULTILEVEL_SCALE == 1) diff --git a/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h b/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h index 78ca2909e5..c300e32330 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 @@ -673,23 +673,40 @@ DEFINE_ATTRIBUTE( DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x03)) +// WARNING : applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.cpp +// use the attributes ID to determine value scale and precision. (+1 for scale and +2 for precision) +// Be careful if you change the ID +#define SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET 1 +#define SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET 2 + DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE, ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x04)) +// 0x05 DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE, - ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x05)) + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | (0x04 + SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET))) +// 0x06 +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_PRECISION, + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | (0x04 + SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET))) DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE, - ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x06)) - -DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE_SCALE, ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x07)) +// 0x08 +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE_SCALE, + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | (0x07 + SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET))) +// 0x09 +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE_PRECISION, + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | (0x07 + SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET))) DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE, - ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x08)) - + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x0A)) +// 0x0B DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_SCALE, - ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x09)) + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | (0x0A + SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET))) +// 0x0C +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_PRECISION, + ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | (0x0A + SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET))) + ///////////////////////////////////////////////// // Wakeup command class diff --git a/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_setpoint_types.h b/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_setpoint_types.h new file mode 100644 index 0000000000..42f020510b --- /dev/null +++ b/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_setpoint_types.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * # License + * Copyright 2021 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 zpc_attribute_store_command_classes_types Type definitions for attribute storage of Command Classes + * @ingroup zpc_attribute_store + * @brief Type definitions for Command Classes, used for @ref attribute_store storage. + * + */ + +/** + * @defgroup zwave_command_class_thermostat_setpoint_types Type definitions for attribute storage of the Thermostat SetPoint Command Class + * @ingroup zpc_attribute_store_command_classes_types + * @brief Type definitions for the Thermostat SetPoint Command Class. + * + * @{ + */ + +#ifndef ZWAVE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPES_H +#define ZWAVE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPES_H + +#define SETPOINT_TYPE_HEATING 1 +#define SETPOINT_TYPE_COOLING 2 + +// Should be uint8_t, but kept int8_t for legacy reasons +typedef int8_t thermostat_setpoint_type_t; +typedef int32_t thermostat_setpoint_value_t; +// Should be uint8_t, but kept uint32_t for legacy reasons +typedef uint32_t thermostat_setpoint_scale_t; +typedef uint8_t thermostat_setpoint_precision_t; + + +#endif //ZWAVE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPES_H +/** @} end zwave_command_class_thermostat_setpoint_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 774ba2882d..eabd22e3cd 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 @@ -298,10 +298,16 @@ static const std::vector attribute_schema = { {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, "Thermostat Setpoint Type", ATTRIBUTE_ENDPOINT_ID, I8_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE, "Value", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, I32_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE, "Value Scale", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U32_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_PRECISION, "Value Precision", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE, "Min Value", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, I32_STORAGE_TYPE}, {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_MIN_VALUE_PRECISION, "Min Value Precision", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE, "Max Value", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, I32_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_SCALE, "Max Value Scale", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, U32_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_PRECISION, "Max Value Precision", ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, 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 90bec52fee..654a24788e 100644 --- a/applications/zpc/components/zwave_command_classes/CMakeLists.txt +++ b/applications/zpc/components/zwave_command_classes/CMakeLists.txt @@ -45,7 +45,7 @@ add_library( src/zwave_command_class_switch_color.c 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_setpoint.cpp 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/include/zwave_command_classes_utils.h b/applications/zpc/components/zwave_command_classes/include/zwave_command_classes_utils.h index 7b8c18954e..3ccc5706ee 100644 --- a/applications/zpc/components/zwave_command_classes/include/zwave_command_classes_utils.h +++ b/applications/zpc/components/zwave_command_classes/include/zwave_command_classes_utils.h @@ -56,8 +56,8 @@ typedef struct zwave_minimum_frame { } zwave_minimum_frame_t; // Helper macros -#define FAHRENHEIT_TO_DEGREES(value) ((value - 32.0F) * 5 / 9); -#define DEGREES_TO_FAHRENHEIT(value) (value * 9 / 5.0F) + 32; +#define FAHRENHEIT_TO_DEGREES(value) ((value - 32.0) * 5 / 9); +#define DEGREES_TO_FAHRENHEIT(value) (value * 9 / 5.0) + 32; // Constants /// Additional delay in ms to wait before issuing a Get Command @@ -214,7 +214,33 @@ int32_t get_signed_value_from_frame_and_size(const uint8_t *frame, */ uint32_t get_unsigned_value_from_frame_and_size(const uint8_t *frame, uint8_t size); + /** + * @brief Convert a value from the Z-Wave world (precision = [0..7] and C° + F) into a UCL (Zigbee) world (precision = 2 and C°) + * + * @param zwave_value Current Z-Wave value + * @param zwave_precision Reported Z-Wave precision + * @param zwave_scale Reported Z-Wave scale (0 : C°, 1 : F) + * + * @return int16_t UCL temperature. Rounded down if Z-Wave precision is too high. + */ +int16_t zwave_temperature_to_ucl_temperature(int32_t zwave_value, + uint8_t zwave_precision, + uint8_t zwave_scale); + +/** + * @brief Convert a value from the UCL world (Zigbee) (precision = 2 and C°) to the ZWave world (precision = [0..7] and C° + F) + * + * @param ucl_value Current UCL value + * @param zwave_precision Expected Z-Wave precision + * @param zwave_scale Expected Z-Wave scale (0 : C°, 1 : F) + * + * @return int32_t Z-Wave temperature with given precision and scale. + */ +int32_t ucl_temperature_to_zwave_temperature(int16_t ucl_value, + uint8_t zwave_precision, + uint8_t zwave_scale); + /** * @brief Converts a clock_time_t duration to a Z-Wave Command Class duration * byte * @@ -224,7 +250,7 @@ uint32_t get_unsigned_value_from_frame_and_size(const uint8_t *frame, * @param time The system time duration * @returns uint8_t The corresponding Z-Wave duration encoding. */ -uint8_t time_to_zwave_duration(clock_time_t time); + uint8_t time_to_zwave_duration(clock_time_t time); /** * @brief Converts a duration byte encoded for a Z-Wave command class and returns diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c deleted file mode 100644 index d4629b54a3..0000000000 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c +++ /dev/null @@ -1,698 +0,0 @@ -/****************************************************************************** - * # License - * Copyright 2021 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. - * - *****************************************************************************/ - -// Includes from this component -#include "zwave_command_class_thermostat_setpoint.h" -#include "zwave_command_classes_utils.h" - -// Includes from other components -#include "sl_log.h" -#include "sl_status.h" -#include "zwave_command_class_indices.h" -#include "ZW_classcmd.h" -#include "zwave_command_handler.h" -#include "zpc_attribute_store_network_helper.h" -#include "attribute_store_defined_attribute_types.h" - -#include "attribute_store_helper.h" -#include "attribute_store.h" -#include "attribute_resolver.h" - -// Generic includes -#include - -// Log define -#define LOG_TAG "zwave_command_class_thermostat_setpoint" - -#define ATTRIBUTE(type) ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_##type - -/////////////////////////////////////////////////////////////////////////////// -// Private helper functions -/////////////////////////////////////////////////////////////////////////////// -static void zwave_command_class_thermostat_setpoint_set_default_capabilities( - attribute_store_node_t type_node) -{ - attribute_store_node_t min_value_node - = attribute_store_get_first_child_by_type(type_node, ATTRIBUTE(MIN_VALUE)); - - int32_t min_value = DEFAULT_MIN_VALUE; - attribute_store_set_reported(min_value_node, &min_value, sizeof(min_value)); - - attribute_store_node_t max_value_node - = attribute_store_get_first_child_by_type(type_node, ATTRIBUTE(MAX_VALUE)); - - int32_t max_value = DEFAULT_MAX_VALUE; - attribute_store_set_reported(max_value_node, &max_value, sizeof(max_value)); - - attribute_store_node_t min_value_scale_node - = attribute_store_get_first_child_by_type(type_node, - ATTRIBUTE(MIN_VALUE_SCALE)); - - int32_t scale = DEFAULT_SCALE; - attribute_store_set_reported(min_value_scale_node, &scale, sizeof(scale)); - - attribute_store_node_t max_value_scale_node - = attribute_store_get_first_child_by_type(type_node, - ATTRIBUTE(MAX_VALUE_SCALE)); - - attribute_store_set_reported(max_value_scale_node, &scale, sizeof(scale)); -} - -/** - * @brief Checks the desired value of a setpoint and validate if it is within - * the valid range. - * - * @param value_node Attribute Store node for the value to apply. - * @returns int32_t valid desired value - */ -static int32_t thermostat_setpoint_get_valid_desired_setpoint_value( - attribute_store_node_t value_node) -{ - int32_t value_to_set = 0; - attribute_store_get_desired_else_reported(value_node, - &value_to_set, - sizeof(value_to_set)); - - zwave_cc_version_t supporting_node_version - = zwave_command_class_get_version_from_node( - value_node, - COMMAND_CLASS_THERMOSTAT_SETPOINT_V3); - // V1-v2, we don't know the capabilities, so we allow to try anything: - if (3 > supporting_node_version) { - return value_to_set; - } - - // V3 nodes, fetch min/max - attribute_store_node_t setpoint_type_node - = attribute_store_get_first_parent_with_type(value_node, ATTRIBUTE(TYPE)); - uint32_t value_scale = CELSIUS_SCALE; - attribute_store_get_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_SCALE), - &value_scale, - sizeof(value_scale)); - if (value_scale == FAHRENHEIT_SCALE) { - value_to_set = FAHRENHEIT_TO_DEGREES(value_to_set) - } - - int32_t min_value = DEFAULT_MIN_VALUE; - attribute_store_get_child_reported(setpoint_type_node, - ATTRIBUTE(MIN_VALUE), - &min_value, - sizeof(min_value)); - uint32_t min_value_scale = CELSIUS_SCALE; - attribute_store_get_child_reported(setpoint_type_node, - ATTRIBUTE(MIN_VALUE_SCALE), - &min_value_scale, - sizeof(min_value_scale)); - if (min_value_scale == FAHRENHEIT_SCALE) { - min_value = FAHRENHEIT_TO_DEGREES(min_value) - } - - // Lower bound validation: - if (value_to_set < min_value) { - sl_log_debug(LOG_TAG, - "Attempting to set a setpoint (%d) lower than the " - "minimum (%d). Using the minimum value.", - value_to_set, - min_value); - return min_value; - } - - int32_t max_value = DEFAULT_MAX_VALUE; - attribute_store_get_child_reported(setpoint_type_node, - ATTRIBUTE(MAX_VALUE), - &max_value, - sizeof(max_value)); - uint32_t max_value_scale = CELSIUS_SCALE; - attribute_store_get_child_reported(setpoint_type_node, - ATTRIBUTE(MAX_VALUE_SCALE), - &max_value_scale, - sizeof(max_value_scale)); - if (max_value_scale == FAHRENHEIT_SCALE) { - max_value = FAHRENHEIT_TO_DEGREES(max_value) - } - // Upper bound validation: - if (value_to_set > max_value) { - sl_log_debug(LOG_TAG, - "Attempting to set a setpoint (%d) higher than the " - "maximum (%d). Using the maximum value.", - value_to_set, - max_value); - return max_value; - } - - return value_to_set; -} - -/////////////////////////////////////////////////////////////////////////////// -// Attribute Callback functions -/////////////////////////////////////////////////////////////////////////////// -static void zwave_command_class_thermostat_setpoint_on_version_attribute_update( - attribute_store_node_t updated_node, attribute_store_change_t change) -{ - if (change == ATTRIBUTE_DELETED) { - return; - } - - if (is_zwave_command_class_filtered_for_root_device( - COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, - updated_node) - == true) { - return; - } - - // Check that we have the right type of attribute. - assert(ATTRIBUTE(VERSION) == attribute_store_get_node_type(updated_node)); - - uint8_t supporting_node_version = 0; - attribute_store_read_value(updated_node, - REPORTED_ATTRIBUTE, - &supporting_node_version, - sizeof(supporting_node_version)); - - if (supporting_node_version == 0) { - // Wait for the version to be known. - return; - } - - // Now we know we have a supporting node. - // Just check that the suported setpoint type attribute is present. - attribute_store_node_t endpoint_node - = attribute_store_get_node_parent(updated_node); - - // Let the rest of the command class perform the job. - attribute_store_type_t supported_sensor_types[] - = {ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)}; - attribute_store_add_if_missing(endpoint_node, - supported_sensor_types, - COUNT_OF(supported_sensor_types)); -} - -/////////////////////////////////////////////////////////////////////////////// -// Attribute creation functions -/////////////////////////////////////////////////////////////////////////////// -static attribute_store_node_t - zwave_command_class_thermostat_setpoint_create_type( - attribute_store_node_t endpoint_node, uint8_t type) -{ - // Do we already have the node ? - attribute_store_node_t type_node = attribute_store_emplace(endpoint_node, - ATTRIBUTE(TYPE), - &type, - sizeof(type)); - - // Add the six other nodes under the type. - const attribute_store_type_t additional_nodes[] - = {ATTRIBUTE(VALUE), - ATTRIBUTE(VALUE_SCALE), - ATTRIBUTE(MIN_VALUE), - ATTRIBUTE(MIN_VALUE_SCALE), - ATTRIBUTE(MAX_VALUE), - ATTRIBUTE(MAX_VALUE_SCALE)}; - attribute_store_add_if_missing(type_node, - additional_nodes, - COUNT_OF(additional_nodes)); - return type_node; -} - -/////////////////////////////////////////////////////////////////////////////// -// Attribute resolution functions -/////////////////////////////////////////////////////////////////////////////// -sl_status_t zwave_command_class_thermostat_setpoint_supported_get( - attribute_store_node_t node, uint8_t *frame, uint16_t *frame_len) -{ - // Check that we have the right type of attribute. - assert(ATTRIBUTE(SUPPORTED_SETPOINT_TYPES) - == attribute_store_get_node_type(node)); - - // Default frame length in case of error - *frame_len = 0; - - // Supported Get is the same for all versions. - ZW_THERMOSTAT_SETPOINT_SUPPORTED_GET_V3_FRAME *supported_get_frame - = (ZW_THERMOSTAT_SETPOINT_SUPPORTED_GET_V3_FRAME *)frame; - - supported_get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; - supported_get_frame->cmd = THERMOSTAT_SETPOINT_SUPPORTED_GET_V3; - - *frame_len = sizeof(ZW_THERMOSTAT_SETPOINT_SUPPORTED_GET_V3_FRAME); - return SL_STATUS_OK; -} - -// FIXME: This will be failing CL:0043.01.21.02.1 -sl_status_t zwave_command_class_thermostat_setpoint_capabilities_get( - attribute_store_node_t node, uint8_t *frame, uint16_t *frame_len) -{ - // Check that we have the right type of attribute. - assert(ATTRIBUTE(MIN_VALUE) == attribute_store_get_node_type(node)); - // Default frame length in case of error - *frame_len = 0; - - attribute_store_node_t type_node = attribute_store_get_node_parent(node); - attribute_store_node_t endpoint_node - = attribute_store_get_node_parent(type_node); - attribute_store_node_t version_node = attribute_store_get_first_child_by_type( - endpoint_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VERSION); - - uint8_t supporting_node_version = 0; - attribute_store_read_value(version_node, - REPORTED_ATTRIBUTE, - &supporting_node_version, - sizeof(supporting_node_version)); - - if (supporting_node_version == 0) { - // Something weird happened. Bail out. - sl_log_warning(LOG_TAG, "Cannot read supporting node version."); - return SL_STATUS_FAIL; - } - - if (supporting_node_version <= 2) { - // In version 1-2, they do not have a supported get. - // here there is no substitution, we we will just return no - // frame and fill up dummy values ourselves - zwave_command_class_thermostat_setpoint_set_default_capabilities(type_node); - return SL_STATUS_ALREADY_EXISTS; - - } else { - // From version 3, we can ask nodes what they support. - ZW_THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3_FRAME - *capabilities_get_frame - = (ZW_THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3_FRAME *)frame; - - capabilities_get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; - capabilities_get_frame->cmd = THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3; - attribute_store_read_value(type_node, - REPORTED_ATTRIBUTE, - &capabilities_get_frame->properties1, - sizeof(capabilities_get_frame->properties1)); - - *frame_len = sizeof(ZW_THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3_FRAME); - return SL_STATUS_OK; - } -} - -sl_status_t zwave_command_class_thermostat_setpoint_get( - attribute_store_node_t node, uint8_t *frame, uint16_t *frame_len) -{ - // Check that we have the right type of attribute. - assert(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE - == attribute_store_get_node_type(node)); - - // Default frame length in case of error - *frame_len = 0; - - attribute_store_node_t type_node = attribute_store_get_node_parent(node); - - ZW_THERMOSTAT_SETPOINT_GET_V3_FRAME *get_frame - = (ZW_THERMOSTAT_SETPOINT_GET_V3_FRAME *)frame; - - get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; - get_frame->cmd = THERMOSTAT_SETPOINT_GET_V3; - // get_frame->level ? This is called 4 bits reserved / 4 bits setpoint type. - attribute_store_read_value(type_node, - REPORTED_ATTRIBUTE, - &get_frame->level, - sizeof(get_frame->level)); - - *frame_len = sizeof(ZW_THERMOSTAT_SETPOINT_GET_V3_FRAME); - return SL_STATUS_OK; -} - -sl_status_t zwave_command_class_thermostat_setpoint_set( - attribute_store_node_t node, uint8_t *frame, uint16_t *frame_len) -{ - // Check that we have the right type of attribute. - assert(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE - == attribute_store_get_node_type(node)); - - // Default frame length in case of error - *frame_len = 0; - - attribute_store_node_t type_node = attribute_store_get_node_parent(node); - attribute_store_node_t scale_node = attribute_store_get_first_child_by_type( - type_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE); - - // We will just always use 4 bytes, precision 2. - ZW_THERMOSTAT_SETPOINT_SET_4BYTE_V3_FRAME *set_frame - = (ZW_THERMOSTAT_SETPOINT_SET_4BYTE_V3_FRAME *)frame; - - set_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; - set_frame->cmd = THERMOSTAT_SETPOINT_SET_V3; - // set_frame->level ? This is 4 bits reserved / 4 bits setpoint type. - set_frame->level = 0; - attribute_store_get_reported(type_node, - &set_frame->level, - sizeof(set_frame->level)); - - // set_frame->level2 ? This is Precision (3 bits) / Scale (2 bits) / size (3 bits) - uint32_t setpoint_value_scale = 0; - // Reuse the same scale as current value. - attribute_store_get_reported(scale_node, - &setpoint_value_scale, - sizeof(setpoint_value_scale)); - - set_frame->level2 = SET_DEFAULT_PRECISION; - set_frame->level2 |= SET_DEFAULT_SIZE; - set_frame->level2 |= ((setpoint_value_scale << 3) & SCALE_MASK); - - int32_t setpoint_value_integer - = thermostat_setpoint_get_valid_desired_setpoint_value(node); - - set_frame->value1 = (setpoint_value_integer & 0xFF000000) >> 24; // MSB - set_frame->value2 = (setpoint_value_integer & 0x00FF0000) >> 16; - set_frame->value3 = (setpoint_value_integer & 0x0000FF00) >> 8; - set_frame->value4 = (setpoint_value_integer & 0x000000FF); // LSB - - *frame_len = sizeof(ZW_THERMOSTAT_SETPOINT_SET_4BYTE_V3_FRAME); - return SL_STATUS_OK; -} - -/////////////////////////////////////////////////////////////////////////////// -// Command Handler functions -/////////////////////////////////////////////////////////////////////////////// -static sl_status_t zwave_command_class_thermostat_setpoint_handle_report( - const zwave_controller_connection_info_t *connection_info, - const uint8_t *frame_data, - uint16_t frame_length) -{ - // We expect to have at least 1 byte of value - if (frame_length <= REPORT_VALUE_INDEX) { - return SL_STATUS_FAIL; - } - - attribute_store_node_t endpoint_node - = zwave_command_class_get_endpoint_node(connection_info); - - uint8_t received_type - = frame_data[REPORT_SETPOINT_TYPE_INDEX] & SETPOINT_TYPE_MASK; - - attribute_store_node_t type_node = attribute_store_get_node_child_by_value( - endpoint_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, - REPORTED_ATTRIBUTE, - &received_type, - sizeof(uint8_t), - 0); - - uint8_t size = frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & SIZE_MASK; - int32_t scale - = (frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & SCALE_MASK) >> 3; - uint8_t precision - = (frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & PRECISION_MASK) >> 5; - - // FIXME: Here we just ignore this requirement from the CC spec: - // A receiving node MUST ignore values outside the range advertised in the Thermostat Setpoint - // Capabilities Report Command for the actual Setpoint type. - // Save the scale directly - attribute_store_node_t scale_node = attribute_store_get_first_child_by_type( - type_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE); - - attribute_store_set_reported(scale_node, &scale, sizeof(scale)); - - int32_t setpoint_value - = command_class_get_int32_value(size, - precision, - &frame_data[REPORT_VALUE_INDEX]); - sl_log_debug(LOG_TAG, - "NodeID %d:%d - Thermostat current setpoint value: %.1f", - (int)connection_info->remote.node_id, - (int)connection_info->remote.endpoint_id, - setpoint_value / 1000.0f); - - // Save it in the attribute store, save the value after the scale, that's what the - // mapper is listening to and the scale has to be correct when they get the callback. - attribute_store_node_t setpoint_value_node - = attribute_store_get_first_child_by_type( - type_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE); - - attribute_store_set_reported(setpoint_value_node, - &setpoint_value, - sizeof(setpoint_value)); - // We have a set function for this attribute, so we also align the desired value - attribute_store_set_desired_as_reported(setpoint_value_node); - - return SL_STATUS_OK; -} - -static sl_status_t - zwave_command_class_thermostat_setpoint_handle_supported_report( - const zwave_controller_connection_info_t *connection_info, - const uint8_t *frame_data, - uint16_t frame_length) -{ - // We expect to have at least 1 bitmask - if (frame_length <= SUPPORTED_REPORT_BITMASK_INDEX) { - return SL_STATUS_FAIL; - } - - // First up, save our supported types in the Attribute Store. - attribute_store_node_t endpoint_node - = zwave_command_class_get_endpoint_node(connection_info); - - attribute_store_node_t supported_bitmask_node - = attribute_store_get_first_child_by_type( - endpoint_node, - ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); - - attribute_store_set_reported(supported_bitmask_node, - &frame_data[SUPPORTED_REPORT_BITMASK_INDEX], - frame_length - SUPPORTED_REPORT_BITMASK_INDEX); - - // Now create a type attribute for each supported type - for (uint8_t byte = 0; byte < frame_length - SUPPORTED_REPORT_BITMASK_INDEX; - byte++) { - for (uint8_t bit = 0; bit < 8; bit++) { - if (frame_data[SUPPORTED_REPORT_BITMASK_INDEX + byte] & (1 << bit)) { - // This type is supported. - uint8_t type = byte * 8 + bit; - if (type > 2) { - // Use Bit Interpretation A see Thermostat Setpoint Command Class in - // Z-Wave Application Command Class Specification - type += 4; - } - zwave_command_class_thermostat_setpoint_create_type(endpoint_node, - type); - } - } - } - - return SL_STATUS_OK; -} - -static sl_status_t - zwave_command_class_thermostat_setpoint_handle_capabilities_report( - const zwave_controller_connection_info_t *connection_info, - const uint8_t *frame_data, - uint16_t frame_length) -{ - // Frame too short, ignore - if (frame_length <= CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX) { - return SL_STATUS_FAIL; - } - - // Just retrieve the data and save it. - attribute_store_node_t endpoint_node - = zwave_command_class_get_endpoint_node(connection_info); - - int8_t received_setpoint_type - = frame_data[CAPABILITIES_REPORT_SETPOINT_TYPE_INDEX] & SETPOINT_TYPE_MASK; - attribute_store_node_t type_node = attribute_store_get_node_child_by_value( - endpoint_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, - REPORTED_ATTRIBUTE, - (uint8_t *)&received_setpoint_type, - sizeof(int8_t), - 0); - - if (type_node == ATTRIBUTE_STORE_INVALID_NODE) { - // Hmm it seems that it's a type that we don't know of. - // Let's be nice and create it. - zwave_command_class_thermostat_setpoint_create_type(endpoint_node, - received_setpoint_type); - } - - // Save the Min value - attribute_store_node_t supported_min_value_scale_node - = attribute_store_get_first_child_by_type( - type_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE_SCALE); - - if (supported_min_value_scale_node == ATTRIBUTE_STORE_INVALID_NODE) { - return SL_STATUS_FAIL; - } - - uint32_t received_min_value_scale - = (frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] - & SCALE_MASK) - >> 3; - attribute_store_set_reported(supported_min_value_scale_node, - &received_min_value_scale, - sizeof(received_min_value_scale)); - - attribute_store_node_t supported_min_value_node - = attribute_store_get_first_child_by_type( - type_node, - ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE); - int32_t received_min_value_size - = frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] - & SIZE_MASK; - int32_t received_min_value_precision - = (frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] - & PRECISION_MASK) - >> 5; - - int32_t received_min_value = command_class_get_int32_value( - received_min_value_size, - received_min_value_precision, - &frame_data[CAPABILITIES_REPORT_MIN_VALUE_INDEX]); - - sl_log_debug(LOG_TAG, - "NodeID %d:%d - Thermostat Min supported temperature " - "(3 decimal digits): %d", - (int)connection_info->remote.node_id, - (int)connection_info->remote.endpoint_id, - received_min_value); - - attribute_store_set_reported(supported_min_value_node, - &received_min_value, - sizeof(received_min_value)); - - // Save the Max value - int32_t max_value_precision_scale_size_index - = CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX - + received_min_value_size + 1; - attribute_store_node_t supported_max_value_scale_node - = attribute_store_get_first_child_by_type(type_node, - ATTRIBUTE(MAX_VALUE_SCALE)); - - if (supported_max_value_scale_node == ATTRIBUTE_STORE_INVALID_NODE) { - return SL_STATUS_FAIL; - } - - int32_t received_max_value_scale - = (frame_data[max_value_precision_scale_size_index] & SCALE_MASK) >> 3; - attribute_store_set_reported(supported_max_value_scale_node, - &received_max_value_scale, - sizeof(received_max_value_scale)); - - attribute_store_node_t supported_max_value_node - = attribute_store_get_first_child_by_type(type_node, ATTRIBUTE(MAX_VALUE)); - int32_t received_max_value_size - = frame_data[max_value_precision_scale_size_index] & SIZE_MASK; - int32_t received_max_value_precision - = (frame_data[max_value_precision_scale_size_index] & PRECISION_MASK) >> 5; - - int32_t received_max_value = command_class_get_int32_value( - received_max_value_size, - received_max_value_precision, - &frame_data[max_value_precision_scale_size_index + 1]); - - sl_log_debug(LOG_TAG, - "NodeID %d:%d - Thermostat Max supported temperature " - "(3 decimal digits): %d", - (int)connection_info->remote.node_id, - (int)connection_info->remote.endpoint_id, - received_max_value); - - attribute_store_set_reported(supported_max_value_node, - &received_max_value, - sizeof(received_max_value)); - - return SL_STATUS_OK; -} - -static sl_status_t zwave_command_class_thermostat_setpoint_control_handler( - const zwave_controller_connection_info_t *connection_info, - const uint8_t *frame_data, - uint16_t frame_length) -{ - // Frame too short, it should have not come here. - if (frame_length <= COMMAND_INDEX) { - return SL_STATUS_NOT_SUPPORTED; - } - assert(frame_data[COMMAND_CLASS_INDEX] - == COMMAND_CLASS_THERMOSTAT_SETPOINT_V3); - - switch (frame_data[COMMAND_INDEX]) { - case THERMOSTAT_SETPOINT_REPORT_V3: - return zwave_command_class_thermostat_setpoint_handle_report( - connection_info, - frame_data, - frame_length); - - case THERMOSTAT_SETPOINT_SUPPORTED_REPORT_V3: - return zwave_command_class_thermostat_setpoint_handle_supported_report( - connection_info, - frame_data, - frame_length); - - case THERMOSTAT_SETPOINT_CAPABILITIES_REPORT_V3: - return zwave_command_class_thermostat_setpoint_handle_capabilities_report( - connection_info, - frame_data, - frame_length); - - default: - return SL_STATUS_NOT_SUPPORTED; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Init functions -/////////////////////////////////////////////////////////////////////////////// - -sl_status_t zwave_command_class_thermostat_setpoint_init() -{ - // Resolver functions. - attribute_resolver_register_rule( - ATTRIBUTE(SUPPORTED_SETPOINT_TYPES), - NULL, - zwave_command_class_thermostat_setpoint_supported_get); - - attribute_resolver_register_rule( - ATTRIBUTE(MIN_VALUE), - NULL, - zwave_command_class_thermostat_setpoint_capabilities_get); - - attribute_resolver_register_rule(ATTRIBUTE(VALUE), - zwave_command_class_thermostat_setpoint_set, - zwave_command_class_thermostat_setpoint_get); - - // Listening for supporting nodes - attribute_store_register_callback_by_type( - zwave_command_class_thermostat_setpoint_on_version_attribute_update, - ATTRIBUTE(VERSION)); - - // Register Thermostat Setpoint CC handler to the Z-Wave CC framework - zwave_command_handler_t handler = {}; - handler.control_handler - = &zwave_command_class_thermostat_setpoint_control_handler; - handler.minimal_scheme = ZWAVE_CONTROLLER_ENCAPSULATION_NONE; - handler.command_class = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; - handler.version = THERMOSTAT_SETPOINT_VERSION_V3; - handler.manual_security_validation = false; - handler.command_class_name = "Thermostat Setpoint"; - handler.comments = "Partial Control:
" - "1. No discovery of ambiguous types in v1-v2
" - "2. Only a few setpoints can be configured.
" - "3. Precision/size fields in the set are determined
" - "automatically by the controller. "; - - zwave_command_handler_register_handler(handler); - - return SL_STATUS_OK; -} \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.cpp b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.cpp new file mode 100644 index 0000000000..fde4d1e9de --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.cpp @@ -0,0 +1,1220 @@ +/****************************************************************************** + * # License + * Copyright 2021 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. + * + *****************************************************************************/ + +// Includes from this component +#include "zwave_command_class_thermostat_setpoint.h" +#include "zwave_command_class_thermostat_setpoint_types.h" +#include "zwave_command_classes_utils.h" + +// Includes from other components +#include "sl_log.h" +#include "sl_status.h" +#include "zwave_command_class_indices.h" +#include "ZW_classcmd.h" +#include "zwave_command_handler.h" +#include "zpc_attribute_store_network_helper.h" +#include "attribute_store_defined_attribute_types.h" + +#include "attribute_store_type_registration.h" +#include "attribute_store_helper.h" +#include "attribute_store.h" +#include "attribute_resolver.h" + +// Generic includes +#include +#include +#include +#include + +// Log define +#define LOG_TAG "zwave_command_class_thermostat_setpoint" + +#define ATTRIBUTE(type) ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_##type + +/////////////////////////////////////////////////////////////////////////////// +// Data type +/////////////////////////////////////////////////////////////////////////////// + +// Used to describe a zwave setpoint value +struct zwave_setpoint_value { + thermostat_setpoint_scale_t scale; + thermostat_setpoint_precision_t precision; + thermostat_setpoint_value_t value; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Private helper functions +/////////////////////////////////////////////////////////////////////////////// +static void zwave_command_class_thermostat_setpoint_set_default_capabilities( + attribute_store_node_t type_node) +{ + attribute_store_node_t min_value_scale_node + = attribute_store_get_first_child_by_type(type_node, + ATTRIBUTE(MIN_VALUE_SCALE)); + + thermostat_setpoint_scale_t scale = DEFAULT_SCALE; + attribute_store_set_reported(min_value_scale_node, &scale, sizeof(scale)); + + attribute_store_node_t max_value_scale_node + = attribute_store_get_first_child_by_type(type_node, + ATTRIBUTE(MAX_VALUE_SCALE)); + + attribute_store_set_reported(max_value_scale_node, &scale, sizeof(scale)); + + thermostat_setpoint_precision_t precision = DEFAULT_PRECISION; + attribute_store_node_t min_value_precision_node + = attribute_store_get_first_child_by_type(type_node, + ATTRIBUTE(MIN_VALUE_PRECISION)); + attribute_store_set_reported(min_value_precision_node, + &precision, + sizeof(precision)); + + attribute_store_node_t max_value_precision_node + = attribute_store_get_first_child_by_type(type_node, + ATTRIBUTE(MAX_VALUE_PRECISION)); + attribute_store_set_reported(max_value_precision_node, + &precision, + sizeof(precision)); + + // Set min and max value last so we can get their actual value + attribute_store_node_t min_value_node + = attribute_store_get_first_child_by_type(type_node, ATTRIBUTE(MIN_VALUE)); + + thermostat_setpoint_value_t min_value = DEFAULT_MIN_VALUE; + attribute_store_set_reported(min_value_node, &min_value, sizeof(min_value)); + + attribute_store_node_t max_value_node + = attribute_store_get_first_child_by_type(type_node, ATTRIBUTE(MAX_VALUE)); + + int32_t max_value = DEFAULT_MAX_VALUE; + attribute_store_set_reported(max_value_node, &max_value, sizeof(max_value)); +} + +/** + * @brief Get the current value of a Setpoint Value + * + * @param setpoint_value Setpoint value (scale is ignored) + * + * @return double Current value based on the precision + */ +static double get_current_value(const zwave_setpoint_value &setpoint_value) +{ + double value = setpoint_value.value; + for (uint8_t i = 0; i < setpoint_value.precision; i++) { + value /= 10; + } + + return value; +} + +/** + * @brief Fill setpoint_value with Scale, Precision and Value of value_node + * + * @param value_node One of ATTRIBUTE(VALUE), ATTRIBUTE(MIN_VALUE), ATTRIBUTE(MAX_VALUE) + * @param setpoint_value Empty object that will be filled (by reference) + * + * @return sl_status_t SL_STATUS_OK if every field have been filed correctly, SL_STATUS_FAIL otherwise + */ +static sl_status_t + get_zwave_setpoint_value(attribute_store_node_t value_node, + zwave_setpoint_value &setpoint_value, + attribute_store_node_value_state_t value_node_state + = REPORTED_ATTRIBUTE) +{ + sl_status_t status = SL_STATUS_OK; + + attribute_store_node_t setpoint_type_node + = attribute_store_get_first_parent_with_type(value_node, ATTRIBUTE(TYPE)); + + attribute_store_type_t value_node_type + = attribute_store_get_node_type(value_node); + + status |= attribute_store_read_value(value_node, + value_node_state, + &setpoint_value.value, + sizeof(setpoint_value.value)); + status |= attribute_store_get_child_reported( + setpoint_type_node, + value_node_type + SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET, + &setpoint_value.scale, + sizeof(setpoint_value.scale)); + status |= attribute_store_get_child_reported( + setpoint_type_node, + value_node_type + SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET, + &setpoint_value.precision, + sizeof(setpoint_value.precision)); + return status; +} + +/** + * @brief Checks the desired value of a setpoint and validate if it is within + * the valid range. + * + * @param value_node Attribute Store node for the value to apply. + * @returns int32_t valid desired value + */ +static int32_t thermostat_setpoint_get_valid_desired_setpoint_value( + attribute_store_node_t value_node) +{ + int32_t value_to_set = 0; + attribute_store_get_desired_else_reported(value_node, + &value_to_set, + sizeof(value_to_set)); + + zwave_cc_version_t supporting_node_version + = zwave_command_class_get_version_from_node( + value_node, + COMMAND_CLASS_THERMOSTAT_SETPOINT_V3); + + // V1-v2, we don't know the capabilities, so we allow to try anything: + if (3 > supporting_node_version) { + return value_to_set; + } + + attribute_store_node_t setpoint_type_node + = attribute_store_get_first_parent_with_type(value_node, ATTRIBUTE(TYPE)); + attribute_store_node_t min_value_node + = attribute_store_get_first_child_by_type(setpoint_type_node, + ATTRIBUTE(MIN_VALUE)); + attribute_store_node_t max_value_node + = attribute_store_get_first_child_by_type(setpoint_type_node, + ATTRIBUTE(MAX_VALUE)); + + zwave_setpoint_value setpoint_min_struct; + zwave_setpoint_value setpoint_max_struct; + zwave_setpoint_value setpoint_value_struct; + + sl_status_t status + = get_zwave_setpoint_value(min_value_node, setpoint_min_struct); + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get setpoint MIN value. No check will be performed " + "on the value sent."); + return value_to_set; + } + status = get_zwave_setpoint_value(max_value_node, setpoint_max_struct); + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get setpoint MAX value. No check will be performed " + "on the value sent."); + return value_to_set; + } + status = get_zwave_setpoint_value(value_node, + setpoint_value_struct, + DESIRED_ATTRIBUTE); + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get setpoint CURRENT value. Something went wrong"); + return value_to_set; + } + + // Check for unit mismatch + if (setpoint_min_struct.scale != setpoint_max_struct.scale + || setpoint_max_struct.scale != setpoint_value_struct.scale) { + sl_log_info(LOG_TAG, + "Max, min and current value doesn't have the same unit base. " + "No check will be performed on the value sent."); + return value_to_set; + } + + // Perform check + double current_min_value = get_current_value(setpoint_min_struct); + double current_max_value = get_current_value(setpoint_max_struct); + double current_value = get_current_value(setpoint_value_struct); + + // Lower bound validation: + if (current_value < current_min_value) { + sl_log_info(LOG_TAG, + "Attempting to set a setpoint (%f) lower than the " + "minimum (%f). Using the minimum value.", + current_value, + current_min_value); + + if (setpoint_min_struct.precision == setpoint_value_struct.precision) { + return setpoint_min_struct.value; + } else { + double coeff = pow( + 10, + (setpoint_value_struct.precision - setpoint_min_struct.precision)); + thermostat_setpoint_value_t new_value = setpoint_min_struct.value * coeff; + sl_log_info(LOG_TAG, + "Value and minimum value doesn't have the same precision " + "adjusting value to %d instead of %d", + new_value, + setpoint_min_struct.value); + return new_value; + } + } + + // Upper bound validation: + if (current_value > current_max_value) { + sl_log_debug(LOG_TAG, + "Attempting to set a setpoint (%f) higher than the " + "maximum (%f). Using the maximum value.", + current_value, + current_max_value); + + if (setpoint_max_struct.precision == setpoint_value_struct.precision) { + return setpoint_max_struct.value; + } else { + double coeff = pow( + 10, + (setpoint_value_struct.precision - setpoint_max_struct.precision)); + thermostat_setpoint_value_t new_value = setpoint_max_struct.value * coeff; + sl_log_info(LOG_TAG, + "Value and maximum value doesn't have the same precision " + "adjusting value to %d instead of %d", + new_value, + setpoint_max_struct.value); + return new_value; + } + } + + return value_to_set; +} + +/////////////////////////////////////////////////////////////////////////////// +// Attribute Callback functions +/////////////////////////////////////////////////////////////////////////////// + +// Zigbee data model is only accepting value with precision of 2 in °C. +// So we need to do that here +static void on_zwave_value_update(attribute_store_node_t value_node, + attribute_store_change_t change) +{ + // On the creation of the attribute we don't need to update the value yet + if (change != ATTRIBUTE_UPDATED) { + return; + } + + // Attributes nodes + attribute_store_node_t type_node + = attribute_store_get_node_parent(value_node); + attribute_store_node_t endpoint_node + = attribute_store_get_node_parent(type_node); + + // Current attribute type + attribute_store_type_t attribute_type + = attribute_store_get_node_type(value_node); + const char *attribute_type_name + = attribute_store_get_type_name(attribute_type); + + // Get Setpoint type + uint8_t setpoint_type = 0; + attribute_store_get_reported(type_node, + &setpoint_type, + sizeof(setpoint_type)); + // Get Setpoint value + thermostat_setpoint_value_t setpoint_value = 0; + sl_status_t status = attribute_store_get_reported(value_node, + &setpoint_value, + sizeof(setpoint_value)); + + if (status != SL_STATUS_OK) { + sl_log_debug(LOG_TAG, + "No reported value for setpoint type %d. Waiting for one.", + setpoint_type); + return; + } + + // Check if all attributes are there (value, precision and scale) + for (int i = 0; i < 3; i++) { + auto current_attribute_type = attribute_type + i; + attribute_store_node_t zwave_node + = attribute_store_get_first_child_by_type(type_node, + current_attribute_type); + const char *zwave_attribute_name + = attribute_store_get_type_name(current_attribute_type); + if (!attribute_store_node_exists(zwave_node) + || !attribute_store_is_value_defined(zwave_node, REPORTED_ATTRIBUTE)) { + sl_log_debug(LOG_TAG, + "Setpoint type %d not ready yet. Missing : %s", + setpoint_type, + zwave_attribute_name); + return; + } + } + + sl_log_debug(LOG_TAG, + "on_zwave_value_update called on %s (setpoint type %d)", + attribute_type_name, + setpoint_type); + + // Attributes bindings + // bind the setpoint type to their corresponding ZCL attributes + std::map> + bindings; + + // Heating + bindings[SETPOINT_TYPE_HEATING] + = {{ATTRIBUTE(VALUE), + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_HEATING_SETPOINT}, + {ATTRIBUTE(MIN_VALUE), + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_MIN_HEAT_SETPOINT_LIMIT}, + {ATTRIBUTE(MAX_VALUE), + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_MAX_HEAT_SETPOINT_LIMIT}}; + + // Cooling + bindings[SETPOINT_TYPE_COOLING] + = {{ATTRIBUTE(VALUE), + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_COOLING_SETPOINT}, + {ATTRIBUTE(MIN_VALUE), + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_MIN_COOL_SETPOINT_LIMIT}, + {ATTRIBUTE(MAX_VALUE), + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_MAX_COOL_SETPOINT_LIMIT}}; + + // If Setpoint Type if found, we proceed. Otherwise it's not a big deal. It can happen and we don't really care + if (bindings.find(setpoint_type) != bindings.end()) { + auto attributes = bindings[setpoint_type]; + + // If we don't find our attribute node we need to stop here and report the error + // This should not happen, and if it is happening you need to update the bindings table + if (attributes.find(attribute_type) == attributes.end()) { + sl_log_critical( + LOG_TAG, + "on_zwave_value_update is bounded to an unknown attribute"); + return; + } + + attribute_store_type_t ucl_type = attributes[attribute_type]; + thermostat_setpoint_scale_t scale = 0; + thermostat_setpoint_precision_t precision = 0; + + // Scale ID is base attribute + 1 + status = attribute_store_get_child_reported( + type_node, + attribute_type + SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET, + &scale, + sizeof(scale)); + + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get scale for attribute : %s", + attribute_type_name); + attribute_store_log_node(type_node, true); + return; + } + + // Precision ID is base attribute + 2 + status = attribute_store_get_child_reported( + type_node, + attribute_type + SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET, + &precision, + sizeof(precision)); + + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get precision for attribute : %s", + attribute_type_name); + return; + } + + int16_t ucl_value = zwave_temperature_to_ucl_temperature(setpoint_value, + (uint8_t)precision, + (uint8_t)scale); + + // Update (or create) ucl value + status = attribute_store_set_child_reported(endpoint_node, + ucl_type, + &ucl_value, + sizeof(ucl_value)); + + const char *ucl_type_name = attribute_store_get_type_name(ucl_type); + + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't set ucl value (%s) for attribute : %s", + ucl_type_name, + attribute_type_name); + return; + } + + sl_log_debug(LOG_TAG, + "Update reported attribute %s to : %d", + ucl_type_name, + ucl_value); + + // Once we've updated the UCL value, we set discard their desired value + attribute_store_node_t ucl_node + = attribute_store_get_first_child_by_type(endpoint_node, ucl_type); + + sl_log_debug(LOG_TAG, "Undefine desired value of %s", ucl_type_name); + attribute_store_undefine_desired(ucl_node); + } +} + +// Zigbee data model is only accepting value with precision of 2 in °C. +// So we need to do that here +static void on_ucl_value_update(attribute_store_node_t ucl_value_node, + attribute_store_change_t change) +{ + // We don't care about attribute creation since the Z-Wave stuff is changed first + if (change != ATTRIBUTE_UPDATED) { + return; + } + + attribute_store_type_t node_type + = attribute_store_get_node_type(ucl_value_node); + const char *ucl_attribute_type_name + = attribute_store_get_type_name(node_type); + + sl_log_debug(LOG_TAG, + "on_ucl_value_update called on %s", + ucl_attribute_type_name); + + thermostat_setpoint_type_t setpoint_type = 0; + if (node_type == DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_HEATING_SETPOINT) { + setpoint_type = SETPOINT_TYPE_HEATING; + } else if (node_type + == DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_COOLING_SETPOINT) { + setpoint_type = SETPOINT_TYPE_COOLING; + } else { + // You should not arrive there, if it is the case check the bindings + sl_log_critical( + LOG_TAG, + "Can't found setpoint type mapped with given dotdot attribute : %s", + ucl_attribute_type_name); + return; + } + + attribute_store_node_t endpoint_node + = attribute_store_get_node_parent(ucl_value_node); + + attribute_store_node_t setpoint_type_node + = attribute_store_get_node_child_by_value(endpoint_node, + ATTRIBUTE(TYPE), + REPORTED_ATTRIBUTE, + (uint8_t *)&setpoint_type, + sizeof(setpoint_type), + 0); + + if (setpoint_type_node == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_warning(LOG_TAG, "Can't find setpoint type : %d", setpoint_type); + return; + } + + thermostat_setpoint_precision_t precision; + sl_status_t status + = attribute_store_get_child_reported(setpoint_type_node, + ATTRIBUTE(VALUE_PRECISION), + &precision, + sizeof(precision)); + + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get precision value for UCL attribute %s. Won't " + "update Zwave attribute", + ucl_attribute_type_name); + return; + } + + thermostat_setpoint_scale_t scale; + status = attribute_store_get_child_reported(setpoint_type_node, + ATTRIBUTE(VALUE_SCALE), + &scale, + sizeof(scale)); + + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get scale value for UCL attribute %s. Won't update " + "Zwave attribute", + ucl_attribute_type_name); + return; + } + + // Get the requested desired value and map it to the zwave side + int16_t ucl_value; + status = attribute_store_get_desired(ucl_value_node, + &ucl_value, + sizeof(ucl_value)); + if (status != SL_STATUS_OK) { + sl_log_debug(LOG_TAG, + "Defined value of UCL attribute %s is not defined. Not " + "updating anything.", + ucl_attribute_type_name); + return; + } + + // Read current zwave value + attribute_store_node_t zwave_value_node + = attribute_store_get_first_child_by_type(setpoint_type_node, + ATTRIBUTE(VALUE)); + thermostat_setpoint_value_t current_zwave_value; + status = attribute_store_get_reported(zwave_value_node, + ¤t_zwave_value, + sizeof(current_zwave_value)); + if (status != SL_STATUS_OK) { + sl_log_warning(LOG_TAG, + "Can't get current zwave current value. Not updating it.", + ucl_attribute_type_name); + return; + } + + thermostat_setpoint_value_t new_zwave_value + = ucl_temperature_to_zwave_temperature(ucl_value, precision, scale); + + // If we have the same converted value (e.g C° to F) we set the state ourself + if (current_zwave_value == new_zwave_value) { + sl_log_info(LOG_TAG, + "Trying to set the same desired z-wave value as the reported " + "one. No changes will be set on the device. This behavior can " + "happen when °C is converted to F based on the device " + "precision. Try to set more/less °C in SetpointRaiseOrLower."); + attribute_store_set_reported_as_desired(ucl_value_node); + attribute_store_undefine_desired(ucl_value_node); + return; + } + + status = attribute_store_set_desired(zwave_value_node, + &new_zwave_value, + sizeof(new_zwave_value)); + + if (status != SL_STATUS_OK) { + sl_log_warning( + LOG_TAG, + "Can't set desired setpoint value (zwave) for UCL attribute %s. " + "Won't update Zwave attribute", + ucl_attribute_type_name); + return; + } + + sl_log_debug(LOG_TAG, + "Update desired Z-Wave Value for setpoint type %d to : %d", + setpoint_type, + new_zwave_value); +} + +static void zwave_command_class_thermostat_setpoint_on_version_attribute_update( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + if (change == ATTRIBUTE_DELETED) { + return; + } + + if (is_zwave_command_class_filtered_for_root_device( + COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, + updated_node) + == true) { + return; + } + + // Check that we have the right type of attribute. + assert(ATTRIBUTE(VERSION) == attribute_store_get_node_type(updated_node)); + + uint8_t supporting_node_version = 0; + attribute_store_read_value(updated_node, + REPORTED_ATTRIBUTE, + &supporting_node_version, + sizeof(supporting_node_version)); + + if (supporting_node_version == 0) { + // Wait for the version to be known. + return; + } + + // Now we know we have a supporting node. + // Just check that the suported setpoint type attribute is present. + attribute_store_node_t endpoint_node + = attribute_store_get_node_parent(updated_node); + + // Let the rest of the command class perform the job. + attribute_store_type_t supported_sensor_types[] + = {ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)}; + attribute_store_add_if_missing(endpoint_node, + supported_sensor_types, + COUNT_OF(supported_sensor_types)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Attribute creation functions +/////////////////////////////////////////////////////////////////////////////// +static attribute_store_node_t + zwave_command_class_thermostat_setpoint_create_type( + attribute_store_node_t endpoint_node, uint8_t type) +{ + // Do we already have the node ? + attribute_store_node_t type_node = attribute_store_emplace(endpoint_node, + ATTRIBUTE(TYPE), + &type, + sizeof(type)); + + // Add the six other nodes under the type. + const attribute_store_type_t additional_nodes[] + = {ATTRIBUTE(VALUE), + ATTRIBUTE(VALUE_SCALE), + ATTRIBUTE(VALUE_PRECISION), + ATTRIBUTE(MIN_VALUE), + ATTRIBUTE(MIN_VALUE_SCALE), + ATTRIBUTE(MIN_VALUE_PRECISION), + ATTRIBUTE(MAX_VALUE), + ATTRIBUTE(MAX_VALUE_SCALE), + ATTRIBUTE(MAX_VALUE_PRECISION)}; + attribute_store_add_if_missing(type_node, + additional_nodes, + COUNT_OF(additional_nodes)); + return type_node; +} + +/////////////////////////////////////////////////////////////////////////////// +// Attribute resolution functions +/////////////////////////////////////////////////////////////////////////////// +sl_status_t zwave_command_class_thermostat_setpoint_supported_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + // Check that we have the right type of attribute. + assert(ATTRIBUTE(SUPPORTED_SETPOINT_TYPES) + == attribute_store_get_node_type(node)); + + // Default frame length in case of error + *frame_length = 0; + + // Supported Get is the same for all versions. + ZW_THERMOSTAT_SETPOINT_SUPPORTED_GET_V3_FRAME *supported_get_frame + = (ZW_THERMOSTAT_SETPOINT_SUPPORTED_GET_V3_FRAME *)frame; + + supported_get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + supported_get_frame->cmd = THERMOSTAT_SETPOINT_SUPPORTED_GET_V3; + + *frame_length = sizeof(ZW_THERMOSTAT_SETPOINT_SUPPORTED_GET_V3_FRAME); + return SL_STATUS_OK; +} + +// FIXME: This will be failing CL:0043.01.21.02.1 +sl_status_t zwave_command_class_thermostat_setpoint_capabilities_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + // Check that we have the right type of attribute. + assert(ATTRIBUTE(MIN_VALUE) == attribute_store_get_node_type(node)); + // Default frame length in case of error + *frame_length = 0; + + attribute_store_node_t type_node = attribute_store_get_node_parent(node); + attribute_store_node_t endpoint_node + = attribute_store_get_node_parent(type_node); + attribute_store_node_t version_node = attribute_store_get_first_child_by_type( + endpoint_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VERSION); + + uint8_t supporting_node_version = 0; + attribute_store_read_value(version_node, + REPORTED_ATTRIBUTE, + &supporting_node_version, + sizeof(supporting_node_version)); + + if (supporting_node_version == 0) { + // Something weird happened. Bail out. + sl_log_warning(LOG_TAG, "Cannot read supporting node version."); + return SL_STATUS_FAIL; + } + + if (supporting_node_version <= 2) { + // In version 1-2, they do not have a supported get. + // here there is no substitution, we we will just return no + // frame and fill up dummy values ourselves + zwave_command_class_thermostat_setpoint_set_default_capabilities(type_node); + return SL_STATUS_ALREADY_EXISTS; + + } else { + // From version 3, we can ask nodes what they support. + ZW_THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3_FRAME + *capabilities_get_frame + = (ZW_THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3_FRAME *)frame; + + capabilities_get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + capabilities_get_frame->cmd = THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3; + attribute_store_read_value(type_node, + REPORTED_ATTRIBUTE, + &capabilities_get_frame->properties1, + sizeof(capabilities_get_frame->properties1)); + + *frame_length = sizeof(ZW_THERMOSTAT_SETPOINT_CAPABILITIES_GET_V3_FRAME); + return SL_STATUS_OK; + } +} + +sl_status_t zwave_command_class_thermostat_setpoint_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + // Check that we have the right type of attribute. + assert(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE + == attribute_store_get_node_type(node)); + + // Default frame length in case of error + *frame_length = 0; + + attribute_store_node_t type_node = attribute_store_get_node_parent(node); + + ZW_THERMOSTAT_SETPOINT_GET_V3_FRAME *get_frame + = (ZW_THERMOSTAT_SETPOINT_GET_V3_FRAME *)frame; + + get_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + get_frame->cmd = THERMOSTAT_SETPOINT_GET_V3; + // get_frame->level ? This is called 4 bits reserved / 4 bits setpoint type. + attribute_store_read_value(type_node, + REPORTED_ATTRIBUTE, + &get_frame->level, + sizeof(get_frame->level)); + + *frame_length = sizeof(ZW_THERMOSTAT_SETPOINT_GET_V3_FRAME); + return SL_STATUS_OK; +} + +sl_status_t zwave_command_class_thermostat_setpoint_set( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + // Check that we have the right type of attribute. + assert(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE + == attribute_store_get_node_type(node)); + + // Default frame length in case of error + *frame_length = 0; + + attribute_store_node_t type_node = attribute_store_get_node_parent(node); + + // Get setpoint type + uint8_t setpoint_type = 0; + attribute_store_get_reported(type_node, + &setpoint_type, + sizeof(setpoint_type)); + + // Reuse the same scale as current value. + thermostat_setpoint_scale_t setpoint_value_scale = 0; + attribute_store_get_child_reported( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE, + &setpoint_value_scale, + sizeof(setpoint_value_scale)); + + // Reuse the same precision as current value. + thermostat_setpoint_precision_t setpoint_value_precision = 0; + attribute_store_get_child_reported( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_PRECISION, + &setpoint_value_precision, + sizeof(setpoint_value_precision)); + + uint8_t level2_properties_field + = (setpoint_value_precision << 5) | (setpoint_value_scale << 3); + + thermostat_setpoint_value_t setpoint_value_integer + = thermostat_setpoint_get_valid_desired_setpoint_value(node); + + if (setpoint_value_integer >= INT8_MIN + && setpoint_value_integer <= INT8_MAX) { + ZW_THERMOSTAT_SETPOINT_SET_1BYTE_V3_FRAME *set_frame + = (ZW_THERMOSTAT_SETPOINT_SET_1BYTE_V3_FRAME *)frame; + *frame_length = sizeof(ZW_THERMOSTAT_SETPOINT_SET_1BYTE_V3_FRAME); + + set_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + set_frame->cmd = THERMOSTAT_SETPOINT_SET_V3; + set_frame->level = setpoint_type; + set_frame->level2 = level2_properties_field | 1; + set_frame->value1 = (uint8_t)(setpoint_value_integer & 0x000000FF); + } else if (setpoint_value_integer >= INT16_MIN + && setpoint_value_integer <= INT16_MAX) { + ZW_THERMOSTAT_SETPOINT_SET_2BYTE_V3_FRAME *set_frame + = (ZW_THERMOSTAT_SETPOINT_SET_2BYTE_V3_FRAME *)frame; + *frame_length = sizeof(ZW_THERMOSTAT_SETPOINT_SET_2BYTE_V3_FRAME); + + set_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + set_frame->cmd = THERMOSTAT_SETPOINT_SET_V3; + set_frame->level = setpoint_type; + set_frame->level2 = level2_properties_field | 2; + + set_frame->value1 = (setpoint_value_integer & 0x0000FF00) >> 8; // MSB + set_frame->value2 = (setpoint_value_integer & 0x000000FF); // LSB + + } else if (setpoint_value_integer >= INT32_MIN + && setpoint_value_integer <= INT32_MAX) { + ZW_THERMOSTAT_SETPOINT_SET_4BYTE_V3_FRAME *set_frame + = (ZW_THERMOSTAT_SETPOINT_SET_4BYTE_V3_FRAME *)frame; + *frame_length = sizeof(ZW_THERMOSTAT_SETPOINT_SET_4BYTE_V3_FRAME); + + set_frame->cmdClass = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + set_frame->cmd = THERMOSTAT_SETPOINT_SET_V3; + set_frame->level = setpoint_type; + set_frame->level2 = level2_properties_field | 4; + + set_frame->value1 = (setpoint_value_integer & 0xFF000000) >> 24; // MSB + set_frame->value2 = (setpoint_value_integer & 0x00FF0000) >> 16; + set_frame->value3 = (setpoint_value_integer & 0x0000FF00) >> 8; + set_frame->value4 = (setpoint_value_integer & 0x000000FF); // LSB + + sl_log_warning( + LOG_TAG, + "UCL will convert this value to int16, value might be truncated."); + } else { + sl_log_error(LOG_TAG, "Invalid desired value size"); + return SL_STATUS_NOT_SUPPORTED; + } + + return SL_STATUS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// Command Handler functions +/////////////////////////////////////////////////////////////////////////////// +static sl_status_t zwave_command_class_thermostat_setpoint_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + // We expect to have at least 1 byte of value + if (frame_length <= REPORT_VALUE_INDEX) { + return SL_STATUS_NOT_SUPPORTED; + } + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + thermostat_setpoint_type_t received_type + = frame_data[REPORT_SETPOINT_TYPE_INDEX] & SETPOINT_TYPE_MASK; + + attribute_store_node_t type_node = attribute_store_get_node_child_by_value( + endpoint_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, + REPORTED_ATTRIBUTE, + (uint8_t *)&received_type, + sizeof(received_type), + 0); + + // Add guard in case we don't find it + if (type_node == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_warning( + LOG_TAG, + "Device send setpoint type %d but can't find it in the attribute store", + received_type); + return SL_STATUS_NOT_SUPPORTED; + } + + uint8_t size = frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & SIZE_MASK; + thermostat_setpoint_scale_t scale + = (frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & SCALE_MASK) >> 3; + thermostat_setpoint_precision_t precision + = (frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & PRECISION_MASK) >> 5; + + // FIXME: Here we just ignore this requirement from the CC spec: + // A receiving node MUST ignore values outside the range advertised in the Thermostat Setpoint + // Capabilities Report Command for the actual Setpoint type. + // Save the scale directly + attribute_store_node_t scale_node = attribute_store_get_first_child_by_type( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE); + attribute_store_set_reported(scale_node, &scale, sizeof(scale)); + + // Save precision + attribute_store_set_child_reported( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_PRECISION, + &precision, + sizeof(precision)); + + thermostat_setpoint_value_t setpoint_value + = get_signed_value_from_frame_and_size(&frame_data[REPORT_VALUE_INDEX], + size); + + sl_log_debug( + LOG_TAG, + "NodeID %d:%d - Thermostat current setpoint value: %d (precision %d)", + (int)connection_info->remote.node_id, + (int)connection_info->remote.endpoint_id, + setpoint_value, + precision); + + // Save it in the attribute store, save the value after the scale, that's what the + // mapper is listening to and the scale has to be correct when they get the callback. + attribute_store_node_t setpoint_value_node + = attribute_store_get_first_child_by_type( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE); + + attribute_store_set_reported(setpoint_value_node, + &setpoint_value, + sizeof(setpoint_value)); + // We have a set function for this attribute, so we also align the desired value + //attribute_store_set_desired_as_reported(setpoint_value_node); + + return SL_STATUS_OK; +} + +static sl_status_t + zwave_command_class_thermostat_setpoint_handle_supported_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + // We expect to have at least 1 bitmask + if (frame_length <= SUPPORTED_REPORT_BITMASK_INDEX) { + return SL_STATUS_FAIL; + } + + // First up, save our supported types in the Attribute Store. + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + attribute_store_node_t supported_bitmask_node + = attribute_store_get_first_child_by_type( + endpoint_node, + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES)); + + attribute_store_set_reported(supported_bitmask_node, + &frame_data[SUPPORTED_REPORT_BITMASK_INDEX], + frame_length - SUPPORTED_REPORT_BITMASK_INDEX); + + // Now create a type attribute for each supported type + for (uint8_t byte = 0; byte < frame_length - SUPPORTED_REPORT_BITMASK_INDEX; + byte++) { + for (uint8_t bit = 0; bit < 8; bit++) { + if (frame_data[SUPPORTED_REPORT_BITMASK_INDEX + byte] & (1 << bit)) { + // This type is supported. + uint8_t type = byte * 8 + bit; + if (type > 2) { + // Use Bit Interpretation A see Thermostat Setpoint Command Class in + // Z-Wave Application Command Class Specification + type += 4; + } + zwave_command_class_thermostat_setpoint_create_type(endpoint_node, + type); + } + } + } + + return SL_STATUS_OK; +} + +static sl_status_t + zwave_command_class_thermostat_setpoint_handle_capabilities_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + // Frame too short, ignore + if (frame_length <= CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX) { + return SL_STATUS_FAIL; + } + + // Just retrieve the data and save it. + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + thermostat_setpoint_type_t received_setpoint_type + = frame_data[CAPABILITIES_REPORT_SETPOINT_TYPE_INDEX] & SETPOINT_TYPE_MASK; + attribute_store_node_t type_node = attribute_store_get_node_child_by_value( + endpoint_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_TYPE, + REPORTED_ATTRIBUTE, + (uint8_t *)&received_setpoint_type, + sizeof(int8_t), + 0); + + if (type_node == ATTRIBUTE_STORE_INVALID_NODE) { + // Hmm it seems that it's a type that we don't know of. + // Let's be nice and create it. + zwave_command_class_thermostat_setpoint_create_type(endpoint_node, + received_setpoint_type); + } + + // Save the Min value + attribute_store_node_t supported_min_value_scale_node + = attribute_store_get_first_child_by_type( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE_SCALE); + + if (supported_min_value_scale_node == ATTRIBUTE_STORE_INVALID_NODE) { + return SL_STATUS_FAIL; + } + + thermostat_setpoint_scale_t received_min_value_scale + = (frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] + & SCALE_MASK) + >> 3; + attribute_store_set_reported(supported_min_value_scale_node, + &received_min_value_scale, + sizeof(received_min_value_scale)); + + attribute_store_node_t supported_min_value_node + = attribute_store_get_first_child_by_type( + type_node, + ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE); + uint8_t received_min_value_size + = frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] + & SIZE_MASK; + thermostat_setpoint_precision_t received_min_value_precision + = (frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] + & PRECISION_MASK) + >> 5; + + thermostat_setpoint_value_t received_min_value + = command_class_get_int32_value( + received_min_value_size, + received_min_value_precision, + &frame_data[CAPABILITIES_REPORT_MIN_VALUE_INDEX]); + + sl_log_debug(LOG_TAG, + "NodeID %d:%d - Thermostat Min supported temperature " + "(3 decimal digits): %d", + (int)connection_info->remote.node_id, + (int)connection_info->remote.endpoint_id, + received_min_value); + + attribute_store_set_reported(supported_min_value_node, + &received_min_value, + sizeof(received_min_value)); + + // Save the Max value + int32_t max_value_precision_scale_size_index + = CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX + + received_min_value_size + 1; + attribute_store_node_t supported_max_value_scale_node + = attribute_store_get_first_child_by_type(type_node, + ATTRIBUTE(MAX_VALUE_SCALE)); + + if (supported_max_value_scale_node == ATTRIBUTE_STORE_INVALID_NODE) { + return SL_STATUS_FAIL; + } + + int32_t received_max_value_scale + = (frame_data[max_value_precision_scale_size_index] & SCALE_MASK) >> 3; + attribute_store_set_reported(supported_max_value_scale_node, + &received_max_value_scale, + sizeof(received_max_value_scale)); + + attribute_store_node_t supported_max_value_node + = attribute_store_get_first_child_by_type(type_node, ATTRIBUTE(MAX_VALUE)); + int32_t received_max_value_size + = frame_data[max_value_precision_scale_size_index] & SIZE_MASK; + int32_t received_max_value_precision + = (frame_data[max_value_precision_scale_size_index] & PRECISION_MASK) >> 5; + + int32_t received_max_value = command_class_get_int32_value( + received_max_value_size, + received_max_value_precision, + &frame_data[max_value_precision_scale_size_index + 1]); + + sl_log_debug(LOG_TAG, + "NodeID %d:%d - Thermostat Max supported temperature " + "(3 decimal digits): %d", + (int)connection_info->remote.node_id, + (int)connection_info->remote.endpoint_id, + received_max_value); + + attribute_store_set_reported(supported_max_value_node, + &received_max_value, + sizeof(received_max_value)); + + return SL_STATUS_OK; +} + +static sl_status_t zwave_command_class_thermostat_setpoint_control_handler( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + // Frame too short, it should have not come here. + if (frame_length <= COMMAND_INDEX) { + return SL_STATUS_NOT_SUPPORTED; + } + assert(frame_data[COMMAND_CLASS_INDEX] + == COMMAND_CLASS_THERMOSTAT_SETPOINT_V3); + + switch (frame_data[COMMAND_INDEX]) { + case THERMOSTAT_SETPOINT_REPORT_V3: + return zwave_command_class_thermostat_setpoint_handle_report( + connection_info, + frame_data, + frame_length); + + case THERMOSTAT_SETPOINT_SUPPORTED_REPORT_V3: + return zwave_command_class_thermostat_setpoint_handle_supported_report( + connection_info, + frame_data, + frame_length); + + case THERMOSTAT_SETPOINT_CAPABILITIES_REPORT_V3: + return zwave_command_class_thermostat_setpoint_handle_capabilities_report( + connection_info, + frame_data, + frame_length); + + default: + return SL_STATUS_NOT_SUPPORTED; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Init functions +/////////////////////////////////////////////////////////////////////////////// + +sl_status_t zwave_command_class_thermostat_setpoint_init() +{ + // Resolver functions. + attribute_resolver_register_rule( + ATTRIBUTE(SUPPORTED_SETPOINT_TYPES), + NULL, + &zwave_command_class_thermostat_setpoint_supported_get); + + attribute_resolver_register_rule( + ATTRIBUTE(MIN_VALUE), + NULL, + &zwave_command_class_thermostat_setpoint_capabilities_get); + + attribute_resolver_register_rule( + ATTRIBUTE(VALUE), + &zwave_command_class_thermostat_setpoint_set, + &zwave_command_class_thermostat_setpoint_get); + + // Listening for supporting nodes + attribute_store_register_callback_by_type( + &zwave_command_class_thermostat_setpoint_on_version_attribute_update, + ATTRIBUTE(VERSION)); + + // UAM files are not powerful enough for this case at the moment so we do that here instead + // Mapping zwave <-> ucl + const std::vector zwave_attributes = { + ATTRIBUTE(VALUE), + ATTRIBUTE(MIN_VALUE), + ATTRIBUTE(MAX_VALUE), + }; + const std::vector ucl_attributes + = {DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_HEATING_SETPOINT, + DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_COOLING_SETPOINT}; + + for (auto &zwave_attribute: zwave_attributes) { + attribute_store_register_callback_by_type_and_state(&on_zwave_value_update, + zwave_attribute, + REPORTED_ATTRIBUTE); + } + + for (auto &ucl_attribute: ucl_attributes) { + attribute_store_register_callback_by_type_and_state(&on_ucl_value_update, + ucl_attribute, + DESIRED_ATTRIBUTE); + } + + // Register Thermostat Setpoint CC handler to the Z-Wave CC framework + zwave_command_handler_t handler = {}; + handler.control_handler + = &zwave_command_class_thermostat_setpoint_control_handler; + handler.minimal_scheme = ZWAVE_CONTROLLER_ENCAPSULATION_NONE; + handler.command_class = COMMAND_CLASS_THERMOSTAT_SETPOINT_V3; + handler.version = THERMOSTAT_SETPOINT_VERSION_V3; + handler.manual_security_validation = false; + handler.command_class_name = "Thermostat Setpoint"; + handler.comments = "Partial Control:
" + "1. No discovery of ambiguous types in v1-v2
" + "2. Only a few setpoints can be configured.
"; + + zwave_command_handler_register_handler(handler); + + return SL_STATUS_OK; +} \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.h b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.h index 8be0d09dad..1f58f323a8 100644 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.h +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.h @@ -24,6 +24,7 @@ #define DEFAULT_MIN_VALUE (-10 * 1000) #define DEFAULT_MAX_VALUE (100 * 1000) #define DEFAULT_SCALE CELSIUS_SCALE +#define DEFAULT_PRECISION 3 #define SETPOINT_TYPE_MASK 0x0F @@ -31,7 +32,6 @@ #define SCALE_MASK 0x18 #define PRECISION_MASK 0xE0 -#define SET_DEFAULT_PRECISION (3 << 5) #define SET_DEFAULT_SIZE 4 #define REPORT_SETPOINT_TYPE_INDEX 2 diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_utils.c b/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_utils.c index 158f32fa29..d927408446 100644 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_utils.c +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_utils.c @@ -32,6 +32,7 @@ // Generic includes #include +#include #define LOG_TAG "zwave_command_class_utils" @@ -290,7 +291,8 @@ int32_t command_class_get_int32_value(uint8_t size, return extracted_value; } -int32_t get_signed_value_from_frame_and_size(const uint8_t *frame, uint8_t size) +int32_t + get_signed_value_from_frame_and_size(const uint8_t *frame, uint8_t size) { int32_t extracted_value = get_unsigned_value_from_frame_and_size(frame, size); @@ -313,6 +315,50 @@ uint32_t get_unsigned_value_from_frame_and_size(const uint8_t *frame, return extracted_value; } +// ZCL use precision of 2 +int16_t zwave_temperature_to_ucl_temperature(int32_t value, + uint8_t precision, + uint8_t scale) +{ + // First convert value with precision of 2 + double coeff = pow(10, (2-precision)); + int32_t ucl_value = value * coeff; + + while (ucl_value > INT16_MAX || ucl_value < INT16_MIN ) { + ucl_value /= 10; + sl_log_warning(LOG_TAG, "Reducing precision of temperature value"); + } + + // Need to convert to C° + if (scale == 1) { + double real_value = ucl_value / 100.0; + double f_value = FAHRENHEIT_TO_DEGREES(real_value); + ucl_value = f_value * 100; + } + + return ucl_value; +} + +// ZCL use precision of 2 +int32_t ucl_temperature_to_zwave_temperature(int16_t value, + uint8_t precision, + uint8_t scale) +{ + int32_t zwave_value = value; + // First Need to convert to C° + if (scale == 1) { + double real_value = value / 100.0; + double f_value = DEGREES_TO_FAHRENHEIT(real_value); + zwave_value = f_value * 100; + } + + // Then convert back to our precision + double coeff = pow(10, (precision-2)); + zwave_value *= coeff; + + return zwave_value; +} + /////////////////////////////////////////////////////////////////////////////// // Duration conversion /////////////////////////////////////////////////////////////////////////////// diff --git a/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c index 1042aee5a1..0a0f6e4a37 100644 --- a/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c +++ b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_thermostat_setpoint_test.c @@ -12,6 +12,7 @@ *****************************************************************************/ #include "unity.h" #include "zwave_command_class_thermostat_setpoint.h" +#include "zwave_command_class_thermostat_setpoint_types.h" // Generic includes #include @@ -143,6 +144,318 @@ void setUp() zwave_command_class_thermostat_setpoint_init_verification(); } +/////////////////////////////////////// +// Helpers +/////////////////////////////////////// + +/** + * @brief Create structure of setpoint value (precision, scale and value) under the setpoint_type_node + * + * @param setpoint_type_node Setpoint Type node + * @param value_type Value attribute type (ATTRIBUTE(VALUE), ATTRIBUTE(MIN_VALUE), ATTRIBUTE(MAX_VALUE)) + * @param value Setpoint Value + * @param scale Setpoint Scale (0: °C, 1: F) + * @param precision Setpoint precision + * @param value_state State of setpoint value (reported or desired) + * + * @return attribute_store_node_t Setpoint Value node + */ +attribute_store_node_t helper_create_setpoint_attribute( + attribute_store_node_t setpoint_type_node, + attribute_store_type_t value_type, + thermostat_setpoint_value_t value, + thermostat_setpoint_scale_t scale, + thermostat_setpoint_precision_t precision, + attribute_store_node_value_state_t value_state) +{ + // Define scale & precision first + attribute_store_set_child_reported(setpoint_type_node, + value_type + + SETPOINT_SCALE_ATTRIBUTE_ID_OFFSET, + &scale, + sizeof(scale)); + + attribute_store_set_child_reported(setpoint_type_node, + value_type + + SETPOINT_PRECISION_ATTRIBUTE_ID_OFFSET, + &precision, + sizeof(precision)); + + // Do not add this if already present + attribute_store_node_t value_node + = attribute_store_get_first_child_by_type(setpoint_type_node, value_type); + if (value_node == ATTRIBUTE_STORE_INVALID_NODE) { + value_node = attribute_store_add_node(value_type, setpoint_type_node); + } + + attribute_store_set_node_attribute_value(value_node, + value_state, + (uint8_t *)&value, + sizeof(value)); + + return value_node; +} + +/** + * @brief Create specified setpoint type and set to default capabilities + * @note Force version to 1 + * @param type Setpoint type + * @return setpoint type node + */ +attribute_store_node_t set_default_capabilities(thermostat_setpoint_type_t type) +{ + TEST_ASSERT_NOT_NULL(thermostat_setpoint_capabilities_get); + + attribute_store_node_t setpoint_type_node + = attribute_store_get_node_child_by_value(endpoint_id_node, + ATTRIBUTE(TYPE), + REPORTED_ATTRIBUTE, + (uint8_t *)&type, + sizeof(type), + 0); + // Node already exists we don't need to do this + if (setpoint_type_node != ATTRIBUTE_STORE_INVALID_NODE) { + return setpoint_type_node; + } + + // Simulate version 1 to have default capabilities + const zwave_cc_version_t version = 1; + attribute_store_set_child_reported(endpoint_id_node, + ATTRIBUTE(VERSION), + &version, + sizeof(version)); + + // Create setpoint + + setpoint_type_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(TYPE), + &type, + sizeof(type)); + + // Get function listen to min_value so we have to create it + attribute_store_node_t min_value_node + = attribute_store_emplace(setpoint_type_node, + ATTRIBUTE(MIN_VALUE), + &type, + sizeof(type)); + + sl_status_t status + = thermostat_setpoint_capabilities_get(min_value_node, + received_frame, + &received_frame_size); + + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_ALREADY_EXISTS, + status, + "Get capabilites should have returned SL_STATUS_ALREADY_EXISTS"); + + return setpoint_type_node; +} + +attribute_store_type_t get_ucl_type(thermostat_setpoint_type_t setpoint_type) +{ + attribute_store_type_t ucl_type; + switch (setpoint_type) { + case SETPOINT_TYPE_HEATING: + ucl_type = DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_HEATING_SETPOINT; + break; + case SETPOINT_TYPE_COOLING: + ucl_type = DOTDOT_ATTRIBUTE_ID_THERMOSTAT_OCCUPIED_COOLING_SETPOINT; + break; + default: + TEST_FAIL_MESSAGE("Unkown setpoint type in get_ucl_type"); + } + + return ucl_type; +} + +void helper_test_ucl_bindings(thermostat_setpoint_type_t setpoint_type, + thermostat_setpoint_value_t desired_value, + thermostat_setpoint_precision_t precision, + thermostat_setpoint_scale_t scale, + int16_t expected_ucl_value) +{ + attribute_store_type_t ucl_type = get_ucl_type(setpoint_type); + attribute_store_node_t setpoint_type_node + = set_default_capabilities(setpoint_type); + + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(VALUE), + desired_value, + scale, + precision, + REPORTED_ATTRIBUTE); + + int16_t ucl_value = 0; + attribute_store_get_child_reported( + endpoint_id_node, + ucl_type, + &ucl_value, + sizeof(ucl_value)); + + // Rounded down to precision of 2 + TEST_ASSERT_EQUAL_MESSAGE(expected_ucl_value, + ucl_value, + "UCL value should have it's value defined"); +} + + +void helper_test_zwave_bindings(thermostat_setpoint_type_t setpoint_type, + int16_t ucl_value, + thermostat_setpoint_precision_t precision, + thermostat_setpoint_scale_t scale, + thermostat_setpoint_value_t expected_zwave_value) +{ + attribute_store_type_t ucl_type = get_ucl_type(setpoint_type); + + attribute_store_node_t setpoint_type_node + = set_default_capabilities(setpoint_type); + + attribute_store_node_t value_node = helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(VALUE), + 0, + scale, + precision, + REPORTED_ATTRIBUTE); + + attribute_store_node_t ucl_node + = attribute_store_get_node_child_by_type(endpoint_id_node, ucl_type, 0); + // Simulate ZCL value set + attribute_store_set_desired(ucl_node, &ucl_value, sizeof(ucl_value)); + + // Check if desired value of Zwave is correct + thermostat_setpoint_value_t reported_zwave_value; + attribute_store_get_desired(value_node, + &reported_zwave_value, + sizeof(reported_zwave_value)); + TEST_ASSERT_EQUAL_MESSAGE(expected_zwave_value, + reported_zwave_value, + "ZWave desired value is not set correctly"); + + // Simulate get + attribute_store_set_reported_as_desired(value_node); + attribute_store_undefine_desired(value_node); + + // Check if ucl value is correctly undefined + TEST_ASSERT_FALSE_MESSAGE( + attribute_store_is_desired_defined(ucl_node), + "UCL node should have its value undefined. This allows the calling command " + "to be in a correct state."); +} + +/////////////////////////////////////// +// Binding values +/////////////////////////////////////// +void test_zwave_command_class_thermostat_setpoint_update_ucl_heat_celsius_happy_case() +{ + // No conversion only scale down to precision 2 + helper_test_ucl_bindings(SETPOINT_TYPE_HEATING, + 10123, + 3, + CELSIUS_SCALE, + 1012); +} +void test_zwave_command_class_thermostat_setpoint_update_ucl_heat_fahrenheit_happy_case() +{ + // Scale to precision 2 and convert to C + helper_test_ucl_bindings(SETPOINT_TYPE_HEATING, + 720, + 1, + FAHRENHEIT_SCALE, + 2222); +} +void test_zwave_command_class_thermostat_setpoint_update_ucl_cool_celsius_happy_case() +{ + // No conversion only scale to precision 2 + helper_test_ucl_bindings(SETPOINT_TYPE_COOLING, 12, 0, CELSIUS_SCALE, 1200); +} +void test_zwave_command_class_thermostat_setpoint_update_ucl_cool_fahrenheit_happy_case() +{ + // Scale down to precision 3 and convert to C + helper_test_ucl_bindings(SETPOINT_TYPE_COOLING, + 73255, + 3, + FAHRENHEIT_SCALE, + 2291); +} + +void test_zwave_command_class_thermostat_setpoint_update_zwave_heat_celsius_happy_case() +{ + // No changes + helper_test_zwave_bindings(SETPOINT_TYPE_HEATING, + 1234, // Ucl value + 2, + CELSIUS_SCALE, + 1234 // Zwave value + ); +} +void test_zwave_command_class_thermostat_setpoint_update_zwave_heat_fahrenheit_happy_case() +{ + // Scale to precision 0 + convert into F + helper_test_zwave_bindings(SETPOINT_TYPE_HEATING, + 1200, // Ucl value + 0, + FAHRENHEIT_SCALE, + 53 // Zwave value + ); +} + +void test_zwave_command_class_thermostat_setpoint_update_zwave_cool_celsius_happy_case() +{ + // Scale down to precision 1 + helper_test_zwave_bindings(SETPOINT_TYPE_COOLING, + -1000, // Ucl value + 1, + CELSIUS_SCALE, + -100 // Zwave value + ); +} +void test_zwave_command_class_thermostat_setpoint_update_zwave_cool_fahrenheit_happy_case() +{ + // Scale to precision 3 + convert into F + helper_test_zwave_bindings(SETPOINT_TYPE_COOLING, + -500, // Ucl value + 3, + FAHRENHEIT_SCALE, + 23000 // Zwave value + ); +} + +void test_zwave_command_class_thermostat_setpoint_update_zwave_same_fahrenheit_value() +{ + // First try with a precision of 2 + helper_test_zwave_bindings(SETPOINT_TYPE_COOLING, + -5800, // Ucl value + 2, + FAHRENHEIT_SCALE, + -7240 // Zwave value + ); + // No problem here + helper_test_zwave_bindings(SETPOINT_TYPE_COOLING, + -5810, // Ucl value + 2, + FAHRENHEIT_SCALE, + -7258 // Zwave value + ); + + // Now test with a precision of 0 + helper_test_zwave_bindings(SETPOINT_TYPE_COOLING, + -5812, // Ucl value + 0, + FAHRENHEIT_SCALE, + -72 // Zwave value + ); + // Should be the same value since Zwave is not precise enough + helper_test_zwave_bindings(SETPOINT_TYPE_COOLING, + -5815, // Ucl value + 0, + FAHRENHEIT_SCALE, + -72 // Zwave value + ); +} +/////////////////////////////////////// +// SETPOINT SET/GET/REPORT +/////////////////////////////////////// void test_zwave_command_class_thermostat_setpoint_set_v1() { TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); @@ -153,6 +466,7 @@ void test_zwave_command_class_thermostat_setpoint_set_v1() ATTRIBUTE(VERSION), &version, sizeof(version)); + const uint8_t type = 34; attribute_store_node_t setpoint_type_node = attribute_store_emplace(endpoint_id_node, @@ -160,14 +474,31 @@ void test_zwave_command_class_thermostat_setpoint_set_v1() &type, sizeof(type)); - int32_t desired_value = 123456; + thermostat_setpoint_value_t desired_value = 123456; + thermostat_setpoint_precision_t precision = 7; + thermostat_setpoint_scale_t scale = 1; + attribute_store_node_t value_node - = attribute_store_add_node(ATTRIBUTE(VALUE), setpoint_type_node); - attribute_store_set_desired(value_node, - &desired_value, - sizeof(desired_value)); + = helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(VALUE), + desired_value, + scale, + precision, + DESIRED_ATTRIBUTE); + // Check that those parameters are ignored for versions < 3 + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MIN_VALUE), + 0, + scale, + 0, + REPORTED_ATTRIBUTE); + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MAX_VALUE), + 0, + scale, + 0, + REPORTED_ATTRIBUTE); - TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); TEST_ASSERT_EQUAL( SL_STATUS_OK, thermostat_setpoint_set(value_node, received_frame, &received_frame_size)); @@ -175,7 +506,7 @@ void test_zwave_command_class_thermostat_setpoint_set_v1() const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, THERMOSTAT_SETPOINT_SET_V3, type, - SET_DEFAULT_PRECISION | SET_DEFAULT_SIZE, + (precision << 5) | (scale << 3) | 4, (desired_value & 0xFF000000) >> 24, // MSB (desired_value & 0x00FF0000) >> 16, (desired_value & 0x0000FF00) >> 8, @@ -185,12 +516,94 @@ void test_zwave_command_class_thermostat_setpoint_set_v1() received_frame, sizeof(expected_frame)); } +void test_zwave_command_class_thermostat_setpoint_set_v2() +{ + TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); + + // Version 2 node + const zwave_cc_version_t version = 2; + attribute_store_set_child_reported(endpoint_id_node, + ATTRIBUTE(VERSION), + &version, + sizeof(version)); + const uint8_t type = 1; + attribute_store_node_t setpoint_type_node + = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(TYPE), + &type, + sizeof(type)); + + thermostat_setpoint_value_t desired_value = 12; + thermostat_setpoint_precision_t precision = 5; + thermostat_setpoint_scale_t scale = 1; + + attribute_store_node_t value_node + = helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(VALUE), + desired_value, + scale, + precision, + DESIRED_ATTRIBUTE); + // Check that those parameters are ignored for versions < 3 + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MIN_VALUE), + 0, + scale, + 0, + REPORTED_ATTRIBUTE); + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MAX_VALUE), + 0, + scale, + 0, + REPORTED_ATTRIBUTE); + + TEST_ASSERT_EQUAL( + SL_STATUS_OK, + thermostat_setpoint_set(value_node, received_frame, &received_frame_size)); + + const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, + THERMOSTAT_SETPOINT_SET_V3, + type, + (precision << 5) | (scale << 3) | 1, + (desired_value & 0x000000FF)}; + TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, + received_frame, + sizeof(expected_frame)); +} + +attribute_store_node_t helper_create_setpoint_attribute(attribute_store_node_t setpoint_type_node, + attribute_store_type_t value_type, + thermostat_setpoint_value_t value, + thermostat_setpoint_scale_t scale, + thermostat_setpoint_precision_t precision, + attribute_store_node_value_state_t value_state) +{ + attribute_store_node_t value_node + = attribute_store_add_node(value_type, setpoint_type_node); + attribute_store_set_node_attribute_value(value_node, + value_state, + (uint8_t*)&value, + sizeof(value)); + + attribute_store_set_child_reported(setpoint_type_node, + value_type + 1, + &scale, + sizeof(scale)); + + attribute_store_set_child_reported(setpoint_type_node, + value_type + 2, + &precision, + sizeof(precision)); + + return value_node; +} void test_zwave_command_class_thermostat_setpoint_set_v3_clip_lower_bound() { TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); - // Version 1 node const zwave_cc_version_t version = 3; attribute_store_set_child_reported(endpoint_id_node, ATTRIBUTE(VERSION), @@ -203,18 +616,38 @@ void test_zwave_command_class_thermostat_setpoint_set_v3_clip_lower_bound() &type, sizeof(type)); - int32_t desired_value = DEFAULT_MIN_VALUE - 10; - attribute_store_node_t value_node - = attribute_store_add_node(ATTRIBUTE(VALUE), setpoint_type_node); - attribute_store_set_desired(value_node, - &desired_value, - sizeof(desired_value)); + thermostat_setpoint_value_t desired_value = DEFAULT_MIN_VALUE - 10; + thermostat_setpoint_precision_t precision = 2; + thermostat_setpoint_scale_t scale = 0; - const int32_t min_value = DEFAULT_MIN_VALUE; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(MIN_VALUE), - &min_value, - sizeof(min_value)); + attribute_store_node_t value_node + = helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(VALUE), + desired_value, + scale, + precision, + DESIRED_ATTRIBUTE); + + // Precision offset *10 + // Here we test if the precision is adjusted correctly + thermostat_setpoint_value_t min_value = DEFAULT_MIN_VALUE * 10; + precision = 3; + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MIN_VALUE), + min_value, + scale, + precision, + REPORTED_ATTRIBUTE); + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MAX_VALUE), + DEFAULT_MAX_VALUE, + scale, + precision, + REPORTED_ATTRIBUTE); + + // Precision of 2 + precision = 2; + thermostat_setpoint_value_t expected_value = DEFAULT_MIN_VALUE; TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); TEST_ASSERT_EQUAL( @@ -224,11 +657,9 @@ void test_zwave_command_class_thermostat_setpoint_set_v3_clip_lower_bound() const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, THERMOSTAT_SETPOINT_SET_V3, type, - SET_DEFAULT_PRECISION | SET_DEFAULT_SIZE, - (min_value & 0xFF000000) >> 24, // MSB - (min_value & 0x00FF0000) >> 16, - (min_value & 0x0000FF00) >> 8, - (min_value & 0x000000FF)}; + (precision << 5) | (scale << 3) | 2, + (expected_value & 0x0000FF00) >> 8, + (expected_value & 0x000000FF)}; TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, received_frame, @@ -239,7 +670,6 @@ void test_zwave_command_class_thermostat_setpoint_set_v3_clip_upper_bound() { TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); - // Version 1 node const zwave_cc_version_t version = 3; attribute_store_set_child_reported(endpoint_id_node, ATTRIBUTE(VERSION), @@ -252,18 +682,33 @@ void test_zwave_command_class_thermostat_setpoint_set_v3_clip_upper_bound() &type, sizeof(type)); - int32_t desired_value = DEFAULT_MAX_VALUE + 10; - attribute_store_node_t value_node - = attribute_store_add_node(ATTRIBUTE(VALUE), setpoint_type_node); - attribute_store_set_desired(value_node, - &desired_value, - sizeof(desired_value)); + thermostat_setpoint_value_t desired_value = DEFAULT_MAX_VALUE + 10; + thermostat_setpoint_precision_t precision = 2; + thermostat_setpoint_scale_t scale = 0; - const int32_t max_value = DEFAULT_MAX_VALUE; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(MAX_VALUE), - &max_value, - sizeof(max_value)); + attribute_store_node_t value_node + = helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(VALUE), + desired_value, + scale, + precision, + DESIRED_ATTRIBUTE); + + thermostat_setpoint_value_t max_value = DEFAULT_MAX_VALUE; + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MIN_VALUE), + DEFAULT_MIN_VALUE, + scale, + precision, + REPORTED_ATTRIBUTE); + helper_create_setpoint_attribute(setpoint_type_node, + ATTRIBUTE(MAX_VALUE), + max_value, + scale, + precision, + REPORTED_ATTRIBUTE); + + thermostat_setpoint_value_t expected_value = DEFAULT_MAX_VALUE; TEST_ASSERT_NOT_NULL(thermostat_setpoint_set); TEST_ASSERT_EQUAL( @@ -273,13 +718,81 @@ void test_zwave_command_class_thermostat_setpoint_set_v3_clip_upper_bound() const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, THERMOSTAT_SETPOINT_SET_V3, type, - SET_DEFAULT_PRECISION | SET_DEFAULT_SIZE, - (max_value & 0xFF000000) >> 24, // MSB - (max_value & 0x00FF0000) >> 16, - (max_value & 0x0000FF00) >> 8, - (max_value & 0x000000FF)}; + (precision << 5) | (scale << 3) | 4, + (expected_value & 0xFF000000) >> 24, // MSB + (expected_value & 0x00FF0000) >> 16, + (expected_value & 0x0000FF00) >> 8, + (expected_value & 0x000000FF)}; TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, received_frame, sizeof(expected_frame)); +} + +void test_zwave_command_class_thermostat_setpoint_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; + + uint8_t setpoint_type = 1; + uint8_t precision = 0b010; + uint8_t scale = 0b00; + uint8_t size = 2; + int32_t value = 1212; + + uint8_t report_frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT, + THERMOSTAT_SETPOINT_REPORT, + setpoint_type, + (precision << 5) | (scale << 3) | size, + (value & 0x0000FF00) >> 8, + (value & 0x000000FF)}; + + // Report should fail since the setpoint_type doesn't exists + TEST_ASSERT_EQUAL(SL_STATUS_NOT_SUPPORTED, + thermostat_handler.control_handler(&info, + report_frame, + sizeof(report_frame))); + + // Create setpoint_type + attribute_store_node_t setpoint_type_node + = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(TYPE), + &setpoint_type, + sizeof(setpoint_type)); + + // Need to create an empty value node first since it isn't created automaticaly + attribute_store_add_node(ATTRIBUTE(VALUE), setpoint_type_node); + + // Now we can do it + TEST_ASSERT_EQUAL(SL_STATUS_OK, + thermostat_handler.control_handler(&info, + report_frame, + sizeof(report_frame))); + + int32_t reported_scale = 0; + attribute_store_get_child_reported(setpoint_type_node, + ATTRIBUTE(VALUE_SCALE), + &reported_scale, + sizeof(reported_scale)); + TEST_ASSERT_EQUAL_MESSAGE(scale, reported_scale, "Scale value should match"); + + uint8_t reported_precision = 0; + attribute_store_get_child_reported(setpoint_type_node, + ATTRIBUTE(VALUE_PRECISION), + &reported_precision, + sizeof(reported_precision)); + TEST_ASSERT_EQUAL_MESSAGE(precision, + reported_precision, + "Precision value should match"); + + int32_t reported_value; + attribute_store_get_child_reported(setpoint_type_node, + ATTRIBUTE(VALUE), + &reported_value, + sizeof(reported_value)); + TEST_ASSERT_EQUAL_MESSAGE(value, + reported_value, + "Current value should match"); } \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/test/zwave_command_classes_utils_test.c b/applications/zpc/components/zwave_command_classes/test/zwave_command_classes_utils_test.c index a7248a7d0d..b40b3f603c 100644 --- a/applications/zpc/components/zwave_command_classes/test/zwave_command_classes_utils_test.c +++ b/applications/zpc/components/zwave_command_classes/test/zwave_command_classes_utils_test.c @@ -580,4 +580,38 @@ void test_value_size_precision_extraction() extracted_value = command_class_get_int32_value(4, 2, frame); TEST_ASSERT_EQUAL_UINT32(test_signed_value * 10, extracted_value); +} + +void test_zwave_temp_to_ucl_temperature() { + int32_t current_temp = 25; + for (int i = 0; i < 8; i++) { + printf("Current Z-Wave precision : %d\n", i); + TEST_ASSERT_EQUAL(2500, zwave_temperature_to_ucl_temperature(current_temp, i, 0)); + current_temp *= 10; + } + + TEST_ASSERT_EQUAL(10500, zwave_temperature_to_ucl_temperature(105, 0, 0)); + TEST_ASSERT_EQUAL(1050, zwave_temperature_to_ucl_temperature(105, 1, 0)); + TEST_ASSERT_EQUAL(105, zwave_temperature_to_ucl_temperature(105, 2, 0)); + + // Convert F to C + TEST_ASSERT_EQUAL(2500, zwave_temperature_to_ucl_temperature(77, 0, 1)); + TEST_ASSERT_EQUAL(2527, zwave_temperature_to_ucl_temperature(775, 1, 1)); + TEST_ASSERT_EQUAL(2562, zwave_temperature_to_ucl_temperature(7812, 2, 1)); +} + + +void test_ucl_temp_to_zwave_temperature() { + + TEST_ASSERT_EQUAL(7700, ucl_temperature_to_zwave_temperature(7700, 2, 0)); + TEST_ASSERT_EQUAL(12410, ucl_temperature_to_zwave_temperature(1241, 3, 0)); + TEST_ASSERT_EQUAL(781, ucl_temperature_to_zwave_temperature(7815, 1, 0)); + + // Convert F to C + TEST_ASSERT_EQUAL(69, ucl_temperature_to_zwave_temperature(2111, 0, 1)); + TEST_ASSERT_EQUAL(709, ucl_temperature_to_zwave_temperature(2166, 1, 1)); + TEST_ASSERT_EQUAL(7098, ucl_temperature_to_zwave_temperature(2166, 2, 1)); + TEST_ASSERT_EQUAL(7154, ucl_temperature_to_zwave_temperature(2197, 2, 1)); + TEST_ASSERT_EQUAL(71540, ucl_temperature_to_zwave_temperature(2197, 3, 1)); + } \ No newline at end of file