From 436cc5fd2bdb6049e4c5340e399317f8cae021a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Labb=C3=A9?= Date: Thu, 21 Mar 2024 12:05:57 +0100 Subject: [PATCH] GH-35: Consistency between UCL and ZWave precision UCL precision is now always 2 whild Zwave is trying to adapt to the UCL precision. --- .../dotdot_mapper/rules/Thermostat.uam | 78 +-- .../attribute_store_defined_attribute_types.h | 34 +- ..._command_class_thermostat_setpoint_types.h | 44 ++ .../zpc_attribute_store_type_registration.cpp | 4 + .../zwave_command_classes/CMakeLists.txt | 2 +- .../include/zwave_command_classes_utils.h | 32 +- ...ave_command_class_thermostat_setpoint.cpp} | 659 +++++++++++++++--- .../zwave_command_class_thermostat_setpoint.h | 1 + .../src/zwave_command_classes_utils.c | 48 +- ...e_command_class_thermostat_setpoint_test.c | 623 +++++++++++++---- .../test/zwave_command_classes_utils_test.c | 34 + 11 files changed, 1243 insertions(+), 316 deletions(-) create mode 100644 applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_thermostat_setpoint_types.h rename applications/zpc/components/zwave_command_classes/src/{zwave_command_class_thermostat_setpoint.c => zwave_command_class_thermostat_setpoint.cpp} (53%) 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 f9ed19a135..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,26 +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)) - -DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_SCALE, - ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x09)) - -DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_PRECISION, ((COMMAND_CLASS_THERMOSTAT_SETPOINT << 8) | 0x0A)) +// 0x0B +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MAX_VALUE_SCALE, + ((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 6f18154279..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 @@ -302,8 +302,12 @@ static const std::vector attribute_schema = { {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.cpp similarity index 53% rename from applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c rename to applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.cpp index 6c304586bd..fde4d1e9de 100644 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.c +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_thermostat_setpoint.cpp @@ -13,6 +13,7 @@ // 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 @@ -24,28 +25,72 @@ #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)); - int32_t min_value = DEFAULT_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 @@ -53,19 +98,62 @@ static void zwave_command_class_thermostat_setpoint_set_default_capabilities( 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)); +/** + * @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; + } - int32_t scale = DEFAULT_SCALE; - attribute_store_set_reported(min_value_scale_node, &scale, sizeof(scale)); + return value; +} - attribute_store_node_t max_value_scale_node - = attribute_store_get_first_child_by_type(type_node, - ATTRIBUTE(MAX_VALUE_SCALE)); +/** + * @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_set_reported(max_value_scale_node, &scale, sizeof(scale)); + 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; } /** @@ -87,68 +175,109 @@ static int32_t thermostat_setpoint_get_valid_desired_setpoint_value( = 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) + 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; } - // 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; + // 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; } - 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) + // 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 (value_to_set > max_value) { + if (current_value > current_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; + "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; @@ -157,6 +286,314 @@ static int32_t thermostat_setpoint_get_valid_desired_setpoint_value( /////////////////////////////////////////////////////////////////////////////// // 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) { @@ -215,10 +652,13 @@ static attribute_store_node_t 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_SCALE), + ATTRIBUTE(MAX_VALUE_PRECISION)}; attribute_store_add_if_missing(type_node, additional_nodes, COUNT_OF(additional_nodes)); @@ -348,7 +788,7 @@ sl_status_t zwave_command_class_thermostat_setpoint_set( sizeof(setpoint_type)); // Reuse the same scale as current value. - uint32_t setpoint_value_scale = 0; + thermostat_setpoint_scale_t setpoint_value_scale = 0; attribute_store_get_child_reported( type_node, ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_SCALE, @@ -356,7 +796,7 @@ sl_status_t zwave_command_class_thermostat_setpoint_set( sizeof(setpoint_value_scale)); // Reuse the same precision as current value. - uint8_t setpoint_value_precision = 0; + thermostat_setpoint_precision_t setpoint_value_precision = 0; attribute_store_get_child_reported( type_node, ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_VALUE_PRECISION, @@ -365,7 +805,8 @@ sl_status_t zwave_command_class_thermostat_setpoint_set( uint8_t level2_properties_field = (setpoint_value_precision << 5) | (setpoint_value_scale << 3); - int32_t setpoint_value_integer + + thermostat_setpoint_value_t setpoint_value_integer = thermostat_setpoint_get_valid_desired_setpoint_value(node); if (setpoint_value_integer >= INT8_MIN @@ -408,6 +849,10 @@ sl_status_t zwave_command_class_thermostat_setpoint_set( 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; @@ -432,27 +877,30 @@ static sl_status_t zwave_command_class_thermostat_setpoint_handle_report( attribute_store_node_t endpoint_node = zwave_command_class_get_endpoint_node(connection_info); - uint8_t received_type + 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, - &received_type, - sizeof(uint8_t), + (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, "Can't find setpoint type %d", received_type); + 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; - int32_t scale + thermostat_setpoint_scale_t scale = (frame_data[REPORT_PRECISION_SCALE_SIZE_INDEX] & SCALE_MASK) >> 3; - uint8_t precision + 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: @@ -471,15 +919,17 @@ static sl_status_t zwave_command_class_thermostat_setpoint_handle_report( &precision, sizeof(precision)); - 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); + 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. @@ -492,7 +942,7 @@ static sl_status_t zwave_command_class_thermostat_setpoint_handle_report( &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); + //attribute_store_set_desired_as_reported(setpoint_value_node); return SL_STATUS_OK; } @@ -557,7 +1007,7 @@ static sl_status_t attribute_store_node_t endpoint_node = zwave_command_class_get_endpoint_node(connection_info); - int8_t received_setpoint_type + 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, @@ -584,7 +1034,7 @@ static sl_status_t return SL_STATUS_FAIL; } - uint32_t received_min_value_scale + thermostat_setpoint_scale_t received_min_value_scale = (frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] & SCALE_MASK) >> 3; @@ -596,18 +1046,19 @@ static sl_status_t = attribute_store_get_first_child_by_type( type_node, ATTRIBUTE_COMMAND_CLASS_THERMOSTAT_SETPOINT_MIN_VALUE); - int32_t received_min_value_size + uint8_t received_min_value_size = frame_data[CAPABILITIES_REPORT_MIN_PRECISION_SCALE_SIZE_INDEX] & SIZE_MASK; - int32_t received_min_value_precision + thermostat_setpoint_precision_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]); + 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 " @@ -710,22 +1161,46 @@ sl_status_t zwave_command_class_thermostat_setpoint_init() attribute_resolver_register_rule( ATTRIBUTE(SUPPORTED_SETPOINT_TYPES), NULL, - zwave_command_class_thermostat_setpoint_supported_get); + &zwave_command_class_thermostat_setpoint_supported_get); attribute_resolver_register_rule( ATTRIBUTE(MIN_VALUE), NULL, - zwave_command_class_thermostat_setpoint_capabilities_get); + &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); + 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, + &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 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 bed016ed70..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 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 7d08abab05..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,27 +474,31 @@ void test_zwave_command_class_thermostat_setpoint_set_v1() &type, sizeof(type)); + thermostat_setpoint_value_t desired_value = 123456; + thermostat_setpoint_precision_t precision = 7; + thermostat_setpoint_scale_t scale = 1; - int32_t desired_value = 123456; 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)); - - // Set precision - uint8_t precision = 0b111; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_PRECISION), - &precision, - sizeof(precision)); - int32_t scale = 0b11; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_SCALE), - &scale, - sizeof(scale)); + = 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)); @@ -188,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, - (precision << 5) | (scale << 3) | 4, + (precision << 5) | (scale << 3) | 4, (desired_value & 0xFF000000) >> 24, // MSB (desired_value & 0x00FF0000) >> 16, (desired_value & 0x0000FF00) >> 8, @@ -215,27 +533,31 @@ void test_zwave_command_class_thermostat_setpoint_set_v2() &type, sizeof(type)); + thermostat_setpoint_value_t desired_value = 12; + thermostat_setpoint_precision_t precision = 5; + thermostat_setpoint_scale_t scale = 1; - int32_t desired_value = 12; 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)); - - // Set precision - uint8_t precision = 0b101; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_PRECISION), - &precision, - sizeof(precision)); - int32_t scale = 0b1; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_SCALE), - &scale, - sizeof(scale)); + = 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)); @@ -243,7 +565,7 @@ void test_zwave_command_class_thermostat_setpoint_set_v2() const uint8_t expected_frame[] = {COMMAND_CLASS_THERMOSTAT_SETPOINT_V3, THERMOSTAT_SETPOINT_SET_V3, type, - (precision << 5) | (scale << 3) | 1, + (precision << 5) | (scale << 3) | 1, (desired_value & 0x000000FF)}; TEST_ASSERT_EQUAL(sizeof(expected_frame), received_frame_size); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_frame, @@ -251,11 +573,37 @@ void test_zwave_command_class_thermostat_setpoint_set_v2() 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), @@ -268,30 +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; - // Set precision - uint8_t precision = 0b010; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_PRECISION), - &precision, - sizeof(precision)); - int32_t scale = 0b10; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_SCALE), - &scale, - sizeof(scale)); - - 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( @@ -301,9 +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, - (precision << 5) | (scale << 3) | 2, - (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, @@ -314,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), @@ -327,30 +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)); - - // Set precision - uint8_t precision = 0b001; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_PRECISION), - &precision, - sizeof(precision)); - int32_t scale = 0b00; - attribute_store_set_child_reported(setpoint_type_node, - ATTRIBUTE(VALUE_SCALE), - &scale, - sizeof(scale)); + 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( @@ -360,28 +718,29 @@ 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, - (precision << 5) | (scale << 3) | 4, - (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() { +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 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, @@ -391,9 +750,10 @@ void test_zwave_command_class_thermostat_setpoint_report_happy_case() { (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))); + 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 @@ -406,34 +766,33 @@ void test_zwave_command_class_thermostat_setpoint_report_happy_case() { 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 * 10, // Precision offset - reported_value, - "Value should match"); + 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