From 71c03b29e84c8b68bd591f6eb74b2e9301aae68b Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 4 Sep 2025 15:04:42 -0500 Subject: [PATCH] Bugfix/lighting-output-internal-process-api (#1086) * Fixed Lighting_Command to ignore write priority and use its own. * Fixed BACnetLightingOperation reserved range. * Refactor overridden into the lighting command module. Added Overridden status flags API. Added Lighting Output API to implement a momentary override to the output that is cleared at the next lighting command. Integrated lighting command overridden behavior into the lighting output object. * Added Trim_Fade_Time, High_End_Trim, Low_End_Trim, Last_On_Value and Default_On_Value properties to lighting output. Added TRIM_ACTIVE flag to lighting command. Added Last_On_Value and Default_On_Value to lighting command for restore and toggle. --- src/bacnet/bacenum.h | 6 +- src/bacnet/bactext.c | 4 + src/bacnet/basic/object/lo.c | 1230 ++++++++++++----- src/bacnet/basic/object/lo.h | 36 +- src/bacnet/basic/sys/lighting_command.c | 477 +++++-- src/bacnet/basic/sys/lighting_command.h | 54 + src/bacnet/property.c | 8 + test/bacnet/basic/object/lo/CMakeLists.txt | 1 + test/bacnet/basic/object/lo/src/main.c | 336 ++++- .../basic/sys/lighting_command/src/main.c | 216 ++- 10 files changed, 1854 insertions(+), 514 deletions(-) diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index b213eaa1..f2abe1f4 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -2400,17 +2400,13 @@ typedef enum BACnetLightingOperation { BACNET_LIGHTS_WARN_OFF = 8, BACNET_LIGHTS_WARN_RELINQUISH = 9, BACNET_LIGHTS_STOP = 10, -#if (BACNET_PROTOCOL_REVISION >= 28) /* Addendum cj to ANSI/ASHRAE Standard 135-2020 */ BACNET_LIGHTS_RESTORE_ON = 11, BACNET_LIGHTS_DEFAULT_ON = 12, BACNET_LIGHTS_TOGGLE_RESTORE = 13, BACNET_LIGHTS_TOGGLE_DEFAULT = 14, BACNET_LIGHTS_RESERVED_MIN = 15, -#else - BACNET_LIGHTS_RESERVED_MIN = 11, -#endif - BACNET_LIGHTS_RESERVED_MAX = 63, + BACNET_LIGHTS_RESERVED_MAX = 255, /* Enumerated values 0-255 are reserved for definition by ASHRAE. Enumerated values 256-65535 may be used by others subject to the procedures and constraints described in Clause 23 */ diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index 3b2e469c..96c8a0b8 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -2222,6 +2222,10 @@ INDTEXT_DATA bacnet_lighting_operation_names[] = { { BACNET_LIGHTS_WARN_OFF, "warn-off" }, { BACNET_LIGHTS_WARN_RELINQUISH, "warn-relinquish" }, { BACNET_LIGHTS_STOP, "stop" }, + { BACNET_LIGHTS_RESTORE_ON, "restore-on" }, + { BACNET_LIGHTS_DEFAULT_ON, "default-on" }, + { BACNET_LIGHTS_TOGGLE_RESTORE, "toggle-restore" }, + { BACNET_LIGHTS_TOGGLE_DEFAULT, "toggle-default" }, { 0, NULL } }; diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c index 886d5f3f..1a1008e4 100644 --- a/src/bacnet/basic/object/lo.c +++ b/src/bacnet/basic/object/lo.c @@ -36,6 +36,7 @@ struct object_data { float Physical_Value; uint32_t Egress_Time_Seconds; uint32_t Default_Fade_Time; + uint32_t Trim_Fade_Time; float Default_Ramp_Rate; float Default_Step_Increment; BACNET_LIGHTING_TRANSITION Transition; @@ -85,6 +86,10 @@ static const int Properties_Required[] = { PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY, #if (BACNET_PROTOCOL_REVISION >= 17) PROP_CURRENT_COMMAND_PRIORITY, +#endif +#if (BACNET_PROTOCOL_REVISION >= 28) + PROP_LAST_ON_VALUE, + PROP_DEFAULT_ON_VALUE, #endif -1 }; @@ -96,12 +101,29 @@ static const int Properties_Optional[] = { PROP_COLOR_OVERRIDE, PROP_COLOR_REFERENCE, PROP_OVERRIDE_COLOR_REFERENCE, +#endif +#if (BACNET_PROTOCOL_REVISION >= 28) + PROP_HIGH_END_TRIM, + PROP_LOW_END_TRIM, + PROP_TRIM_FADE_TIME, #endif -1 }; static const int Properties_Proprietary[] = { -1 }; +/** + * @brief compare two floating point values to 3 decimal places + * + * @param x1 - first comparison value + * @param x2 - second comparison value + * @return true if the value is the same to 3 decimal points + */ +static bool is_float_equal(float x1, float x2) +{ + return fabs(x1 - x2) < 0.001; +} + /** * Returns the list of required, optional, and proprietary properties. * Used by ReadPropertyMultiple service. @@ -355,6 +377,20 @@ Present_Value_Relinquish(struct object_data *pObject, unsigned priority) return status; } +/** + * @brief Relinquish the present-value at every priority + * @param pObject [in] object instance + */ +static void Present_Value_Relinquish_All(struct object_data *pObject) +{ + unsigned priority; + if (pObject) { + for (priority = 1; priority <= BACNET_MAX_PRIORITY; priority++) { + Present_Value_Relinquish(pObject, priority); + } + } +} + /** * For a given object instance, sets the present-value at a given * priority 1..16. @@ -401,36 +437,6 @@ unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) return priority; } -/** - * For a given object instance-number, sets the present-value at a given - * priority 1..16. - * - * @param object_instance - object-instance number of the object - * @param value - floating point analog value - * @param priority - priority 1..16 - * - * @return true if values are within range and present-value is set. - */ -bool Lighting_Output_Present_Value_Set( - uint32_t object_instance, float value, unsigned priority) -{ - bool status = false; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (pObject) { - if (priority && (priority <= BACNET_MAX_PRIORITY) && - (priority != 6 /* reserved */)) { - priority--; - BIT_SET(pObject->Priority_Active_Bits, priority); - pObject->Priority_Array[priority] = value; - status = true; - } - } - - return status; -} - /** * @brief Set the lighting command if the priority is active * @param object [in] BACnet object instance @@ -614,6 +620,204 @@ static void Lighting_Command_Step( } } +/** + * @brief Set the lighting command using default values when the priority + * is active + * @param object [in] BACnet object instance + * @param priority [in] BACnet priority array value 1..16 + * @param value - floating point analog value 0.0%, 1.0%-100.0% + */ +static void Lighting_Command_Transition_Default( + struct object_data *pObject, unsigned priority, float value) +{ + unsigned current_priority; + + if (!pObject) { + return; + } + current_priority = Present_Value_Priority(pObject); + if (priority <= current_priority) { + /* we have priority - configure the Lighting Command */ + if (pObject->Transition == BACNET_LIGHTING_TRANSITION_FADE) { + Lighting_Command_Fade_To( + pObject, priority, value, pObject->Default_Fade_Time); + } else if (pObject->Transition == BACNET_LIGHTING_TRANSITION_RAMP) { + Lighting_Command_Ramp_To( + pObject, priority, value, pObject->Default_Ramp_Rate); + } else { + Lighting_Command_Fade_To(pObject, priority, value, 0); + } + } +} + +#if (BACNET_PROTOCOL_REVISION >= 28) +/** + * @brief Set the lighting command if the priority is active + * @details Commands Present_Value to the value of the Last_On_Value property. + * @param object [in] BACnet object instance + * @param priority [in] BACnet priority array value 1..16 + */ +static void +Lighting_Command_Restore_On(struct object_data *pObject, unsigned priority) +{ + float value; + + if (!pObject) { + return; + } + value = pObject->Lighting_Command.Last_On_Value; + Lighting_Command_Transition_Default(pObject, priority, value); +} + +/** + * @brief Set the lighting command if the priority is active + * @details Commands Present_Value to the value of the Default_On_Value + * property. + * @param object [in] BACnet object instance + * @param priority [in] BACnet priority array value 1..16 + */ +static void +Lighting_Command_Default_On(struct object_data *pObject, unsigned priority) +{ + float value; + + if (!pObject) { + return; + } + value = pObject->Lighting_Command.Default_On_Value; + Lighting_Command_Transition_Default(pObject, priority, value); +} + +/** + * @brief Set the lighting command if the priority is active + * @details Commands Present_Value to toggle its Last_On_Value + * @param object [in] BACnet object instance + * @param priority [in] BACnet priority array value 1..16 + */ +static void +Lighting_Command_Toggle_Restore(struct object_data *pObject, unsigned priority) +{ + float present_value, toggle_value; + + if (!pObject) { + return; + } + present_value = Priority_Array_Next_Value(pObject, 0); + if (is_float_equal(present_value, 0.0)) { + /* Prior to the execution of this command, if Present_Value is 0.0%, + write the Last_On_Value to the specified slot in the priority + array. */ + toggle_value = pObject->Lighting_Command.Last_On_Value; + } else { + /* Prior to the execution of this command, if Present_Value is not 0.0%, + write 0.0% to the specified slot in the priority array. */ + toggle_value = 0.0f; + } + Lighting_Command_Transition_Default(pObject, priority, toggle_value); +} + +/** + * @brief Set the lighting command if the priority is active + * @details Commands Present_Value to change its “default on” level + * @param object [in] BACnet object instance + * @param priority [in] BACnet priority array value 1..16 + */ +static void +Lighting_Command_Toggle_Default(struct object_data *pObject, unsigned priority) +{ + float present_value, toggle_value; + + if (!pObject) { + return; + } + present_value = Priority_Array_Next_Value(pObject, 0); + if (is_float_equal(present_value, 0.0)) { + /* Prior to the execution of this command, if Present_Value is 0.0%, + write the Default_On_Value to the specified slot in the priority + array. */ + toggle_value = pObject->Lighting_Command.Default_On_Value; + } else { + /* Prior to the execution of this command, if Present_Value is not 0.0%, + write 0.0% to the specified slot in the priority array. */ + toggle_value = 0.0f; + } + Lighting_Command_Transition_Default(pObject, priority, toggle_value); +} +#endif + +/** + * For a given object instance-number, sets the present-value at a given + * priority 1..16. + * + * @param object_instance - object-instance number of the object + * @param value - floating point analog value + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +bool Lighting_Output_Present_Value_Set( + uint32_t object_instance, float value, unsigned priority) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */)) { + /* Note: Writing a special value has the same effect as writing + the corresponding lighting command and is subject to the same + restrictions. The special value itself is not written to the + priority array. */ + if (is_float_equal(value, -1.0)) { + /* Provides the same functionality as the + WARN lighting command. */ + Lighting_Command_Warn(pObject, priority); + status = true; + } else if (is_float_equal(value, -2.0)) { + /* Provides the same functionality as the + WARN_RELINQUISH lighting command. */ + Lighting_Command_Warn_Relinquish(pObject, priority); + status = true; + } else if (is_float_equal(value, -3.0)) { + /* Provides the same functionality as the + WARN_OFF lighting command. */ + Lighting_Command_Warn_Off(pObject, priority); + status = true; +#if (BACNET_PROTOCOL_REVISION >= 28) + } else if (is_float_equal(value, -4.0)) { + /* Provides the same functionality as the + RESTORE_ON lighting command. */ + Lighting_Command_Restore_On(pObject, priority); + status = true; + } else if (is_float_equal(value, -5.0)) { + /* Provides the same functionality as the + DEFAULT_ON lighting command. */ + Lighting_Command_Default_On(pObject, priority); + status = true; + } else if (is_float_equal(value, -6.0)) { + /* Provides the same functionality as the + TOGGLE_RESTORE lighting command. */ + Lighting_Command_Toggle_Restore(pObject, priority); + status = true; + } else if (is_float_equal(value, -7.0)) { + /* Provides the same functionality as the + TOGGLE_DEFAULT lighting command. */ + Lighting_Command_Toggle_Default(pObject, priority); + status = true; +#endif + } else if ( + isgreaterequal(value, 0.0) && islessequal(value, 100.0)) { + Present_Value_Set(pObject, value, priority); + Lighting_Command_Transition_Default(pObject, priority, value); + status = true; + } + } + } + + return status; +} + /** * For a given object instance-number, writes the present-value * @@ -633,70 +837,23 @@ static bool Lighting_Output_Present_Value_Write( BACNET_ERROR_CODE *error_code) { bool status = false; - struct object_data *pObject; - uint8_t current_priority; - pObject = Keylist_Data(Object_List, object_instance); - if (pObject) { - if (priority == 6) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { - /* Note: Writing a special value has the same effect as writing - the corresponding lighting command and is subject to the same - restrictions. The special value itself is not written to the - priority array. */ - if (!islessgreater(value, -1.0)) { - /* Provides the same functionality as the - WARN lighting command. */ - Lighting_Command_Warn(pObject, priority); - status = true; - } else if (!islessgreater(value, -2.0)) { - /* Provides the same functionality as the - WARN_RELINQUISH lighting command. */ - Lighting_Command_Warn_Relinquish(pObject, priority); - status = true; - } else if (!islessgreater(value, -3.0)) { - /* Provides the same functionality as the - WARN_OFF lighting command. */ - Lighting_Command_Warn_Off(pObject, priority); - status = true; - } else if ( - isgreaterequal(value, 0.0) && islessequal(value, 100.0)) { - Present_Value_Set(pObject, value, priority); - current_priority = Present_Value_Priority(pObject); - if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ - if (pObject->Transition == - BACNET_LIGHTING_TRANSITION_FADE) { - Lighting_Command_Fade_To( - pObject, priority, value, - pObject->Default_Fade_Time); - } else if ( - pObject->Transition == - BACNET_LIGHTING_TRANSITION_RAMP) { - Lighting_Command_Ramp_To( - pObject, priority, value, - pObject->Default_Ramp_Rate); - } else { - Lighting_Command_Fade_To(pObject, priority, value, 0); - } - } - status = true; - } else { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } else { + if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + status = + Lighting_Output_Present_Value_Set(object_instance, value, priority); + if (!status) { *error_class = ERROR_CLASS_PROPERTY; *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } } else { - *error_class = ERROR_CLASS_OBJECT; - *error_code = ERROR_CODE_UNKNOWN_OBJECT; + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } return status; @@ -709,17 +866,54 @@ static bool Lighting_Output_Present_Value_Write( * @param object_instance - object-instance number of the object * @param priority - priority 1..16 * - * @return true if values are within range and present-value is set. + * @return true if priority is within range and priority-array slot is + * relinquished. */ bool Lighting_Output_Present_Value_Relinquish( uint32_t object_instance, unsigned priority) { bool status = false; struct object_data *pObject; + uint8_t old_priority, new_priority; + float value; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { + old_priority = Present_Value_Priority(pObject); status = Present_Value_Relinquish(pObject, priority); + new_priority = Present_Value_Priority(pObject); + if (status && (old_priority != new_priority)) { + if (new_priority > BACNET_MAX_PRIORITY) { + /* BACNET_LIGHTS_WARN_RELINQUISH? */ + value = + (float)Lighting_Output_Relinquish_Default(object_instance); + } else { + value = (float)Lighting_Output_Present_Value_Priority( + object_instance); + } + /* we have priority - configure the Lighting Command */ + Lighting_Command_Transition_Default(pObject, new_priority, value); + } + } + + return status; +} + +/** + * @brief For a given object instance-number, relinquishes the present-value + * at every priority 1..16. + * @param object_instance - object-instance number of the object + * @return true if values are within range and present-value is set. + */ +bool Lighting_Output_Present_Value_Relinquish_All(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + Present_Value_Relinquish_All(pObject); + status = true; } return status; @@ -743,54 +937,23 @@ static bool Lighting_Output_Present_Value_Relinquish_Write( BACNET_ERROR_CODE *error_code) { bool status = false; - struct object_data *pObject; - uint8_t old_priority, new_priority; - float value; - pObject = Keylist_Data(Object_List, object_instance); - if (pObject) { - if (priority == 6) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { - old_priority = Present_Value_Priority(pObject); + if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + status = Lighting_Output_Present_Value_Relinquish(object_instance, priority); - new_priority = - Lighting_Output_Present_Value_Priority(object_instance); - if (old_priority != new_priority) { - if (new_priority > BACNET_MAX_PRIORITY) { - /* BACNET_LIGHTS_WARN_RELINQUISH? */ - value = (float)Lighting_Output_Relinquish_Default( - object_instance); - } else { - value = (float)Lighting_Output_Present_Value_Priority( - object_instance); - } - /* we have priority - configure the Lighting Command */ - if (pObject->Transition == BACNET_LIGHTING_TRANSITION_FADE) { - Lighting_Command_Fade_To( - pObject, new_priority, value, - pObject->Default_Fade_Time); - } else if ( - pObject->Transition == BACNET_LIGHTING_TRANSITION_RAMP) { - Lighting_Command_Ramp_To( - pObject, new_priority, value, - pObject->Default_Ramp_Rate); - } else { - Lighting_Command_Fade_To(pObject, new_priority, value, 0); - } - } - status = true; - } else { + if (!status) { *error_class = ERROR_CLASS_PROPERTY; *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } } else { - *error_class = ERROR_CLASS_OBJECT; - *error_code = ERROR_CODE_UNKNOWN_OBJECT; + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } return status; @@ -936,94 +1099,11 @@ Lighting_Command_Stop(struct object_data *pObject, unsigned priority) } } -#if (BACNET_PROTOCOL_REVISION >= 28) -/** - * @brief Set the lighting command if the priority is active - * @param object [in] BACnet object instance - * @param priority [in] BACnet priority array value 1..16 - */ -static void -Lighting_Command_Restore_On(struct object_data *pObject, unsigned priority) -{ - unsigned current_priority; - - if (!pObject) { - return; - } - current_priority = Present_Value_Priority(pObject); - if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ - lighting_command_restore_on(&pObject->Lighting_Command); - } -} - -/** - * @brief Set the lighting command if the priority is active - * @param object [in] BACnet object instance - * @param priority [in] BACnet priority array value 1..16 - */ -static void -Lighting_Command_Restore_Off(struct object_data *pObject, unsigned priority) -{ - unsigned current_priority; - - if (!pObject) { - return; - } - current_priority = Present_Value_Priority(pObject); - if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ - lighting_command_restore_off(&pObject->Lighting_Command); - } -} - -/** - * @brief Set the lighting command if the priority is active - * @param object [in] BACnet object instance - * @param priority [in] BACnet priority array value 1..16 - */ -static void -Lighting_Command_Toggle_Restore(struct object_data *pObject, unsigned priority) -{ - unsigned current_priority; - - if (!pObject) { - return; - } - current_priority = Present_Value_Priority(pObject); - if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ - lighting_command_toggle_restore(&pObject->Lighting_Command); - } -} - -/** - * @brief Set the lighting command if the priority is active - * @param object [in] BACnet object instance - * @param priority [in] BACnet priority array value 1..16 - */ -static void -Lighting_Command_Toggle_Default(struct object_data *pObject, unsigned priority) -{ - unsigned current_priority; - - if (!pObject) { - return; - } - current_priority = Present_Value_Priority(pObject); - if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ - lighting_command_toggle_default(&pObject->Lighting_Command); - } -} -#endif - /** * For a given object instance-number, writes the present-value * * @param object_instance - object-instance number of the object * @param value - property value to write - * @param priority - priority-array index value 1..16 * @param error_class - the BACnet error class * @param error_code - BACnet Error code * @@ -1032,38 +1112,87 @@ Lighting_Command_Toggle_Default(struct object_data *pObject, unsigned priority) static bool Lighting_Output_Lighting_Command_Write( uint32_t object_instance, const BACNET_LIGHTING_COMMAND *value, - uint8_t priority, BACNET_ERROR_CLASS *error_class, BACNET_ERROR_CODE *error_code) { bool status = false; - struct object_data *pObject; + unsigned priority; if (!value) { *error_class = ERROR_CLASS_PROPERTY; *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; return status; } - if (priority == 6) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - return status; - } - if ((priority < BACNET_MIN_PRIORITY) || (priority > BACNET_MAX_PRIORITY)) { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - return status; + if (value->use_priority) { + priority = value->priority; + if ((priority < BACNET_MIN_PRIORITY) || + (priority > BACNET_MAX_PRIORITY)) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return status; + } } if (value->operation >= BACNET_LIGHTS_RESERVED_MIN) { *error_class = ERROR_CLASS_PROPERTY; *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; return status; } + status = Lighting_Output_Lighting_Command_Set(object_instance, value); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + + return status; +} + +/** + * For a given object instance-number, gets the lighting-command. + * + * @note The Lighting_Command property shall indicate the last written + * value or NONE if it has not yet been written. + * + * @param object_instance - object-instance number of the object + * @param value - holds the lighting command value + * + * @return true if lighting command was retrieved + */ +bool Lighting_Output_Lighting_Command( + uint32_t object_instance, BACNET_LIGHTING_COMMAND *value) +{ + bool status = false; + struct object_data *pObject; + pObject = Keylist_Data(Object_List, object_instance); if (pObject) { + status = lighting_command_copy(value, &pObject->Last_Lighting_Command); + } + + return status; +} + +/** + * For a given object instance-number, sets the lighting-command. + * + * @param object_instance - object-instance number of the object + * @param value - holds the lighting command value + * + * @return true if lighting command was set + */ +bool Lighting_Output_Lighting_Command_Set( + uint32_t object_instance, const BACNET_LIGHTING_COMMAND *value) +{ + bool status = false; + struct object_data *pObject; + unsigned priority; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value->use_priority) { + priority = value->priority; + } else { + priority = pObject->Lighting_Command_Default_Priority; + } debug_printf( "LO[%u]: Lighting-Command@%u: %s\n", object_instance, priority, bactext_lighting_operation_name(value->operation)); @@ -1130,8 +1259,8 @@ static bool Lighting_Output_Lighting_Command_Write( Lighting_Command_Restore_On(pObject, priority); status = true; break; - case BACNET_LIGHTS_RESTORE_OFF: - Lighting_Command_Restore_Off(pObject, priority); + case BACNET_LIGHTS_DEFAULT_ON: + Lighting_Command_Default_On(pObject, priority); status = true; break; case BACNET_LIGHTS_TOGGLE_RESTORE: @@ -1143,107 +1272,6 @@ static bool Lighting_Output_Lighting_Command_Write( status = true; break; #endif - default: - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - break; - } - } else { - *error_class = ERROR_CLASS_OBJECT; - *error_code = ERROR_CODE_UNKNOWN_OBJECT; - } - - return status; -} - -/** - * For a given object instance-number, gets the lighting-command. - * - * @note The Lighting_Command property shall indicate the last written - * value or NONE if it has not yet been written. - * - * @param object_instance - object-instance number of the object - * @param value - holds the lighting command value - * - * @return true if lighting command was retrieved - */ -bool Lighting_Output_Lighting_Command( - uint32_t object_instance, BACNET_LIGHTING_COMMAND *value) -{ - bool status = false; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (pObject) { - status = lighting_command_copy(value, &pObject->Last_Lighting_Command); - } - - return status; -} - -/** - * For a given object instance-number, sets the lighting-command. - * - * @param object_instance - object-instance number of the object - * @param value - holds the lighting command value - * - * @return true if lighting command was set - */ -bool Lighting_Output_Lighting_Command_Set( - uint32_t object_instance, const BACNET_LIGHTING_COMMAND *value) -{ - bool status = false; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (pObject) { - switch (value->operation) { - case BACNET_LIGHTS_NONE: - status = true; - break; - case BACNET_LIGHTS_FADE_TO: - Lighting_Command_Fade_To( - pObject, value->priority, value->target_level, - value->fade_time); - status = true; - break; - case BACNET_LIGHTS_RAMP_TO: - Lighting_Command_Ramp_To( - pObject, value->priority, value->target_level, - value->ramp_rate); - status = true; - break; - case BACNET_LIGHTS_STEP_UP: - case BACNET_LIGHTS_STEP_DOWN: - case BACNET_LIGHTS_STEP_ON: - case BACNET_LIGHTS_STEP_OFF: - Lighting_Command_Step( - pObject, value->priority, value->operation, - value->step_increment); - status = true; - break; - case BACNET_LIGHTS_WARN: - /* Provides the same functionality as the - WARN lighting command. */ - Lighting_Command_Warn(pObject, value->priority); - status = true; - break; - case BACNET_LIGHTS_WARN_OFF: - /* Provides the same functionality as the - WARN_OFF lighting command. */ - Lighting_Command_Warn_Off(pObject, value->priority); - status = true; - break; - case BACNET_LIGHTS_WARN_RELINQUISH: - /* Provides the same functionality as the - WARN_RELINQUISH lighting command. */ - Lighting_Command_Warn_Relinquish(pObject, value->priority); - status = true; - break; - case BACNET_LIGHTS_STOP: - Lighting_Command_Stop(pObject, value->priority); - status = true; - break; default: break; } @@ -1577,7 +1605,7 @@ bool Lighting_Output_Default_Fade_Time_Set( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - if ((milliseconds >= 100) && (milliseconds <= 86400000)) { + if ((milliseconds >= 100UL) && (milliseconds <= 86400000UL)) { pObject->Default_Fade_Time = milliseconds; status = true; } @@ -1610,7 +1638,7 @@ static bool Lighting_Output_Default_Fade_Time_Write( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { (void)priority; - if ((value >= 100) && (value <= 86400000)) { + if ((value >= 100UL) && (value <= 86400000UL)) { pObject->Default_Fade_Time = value; status = true; } else { @@ -1974,6 +2002,461 @@ static bool Lighting_Output_Relinquish_Default_Write( return status; } +/** + * @brief For a given object instance-number, returns the last-on-value + * property value + * @param object_instance - object-instance number of the object + * @return last-on-value property value + */ +float Lighting_Output_Last_On_Value(uint32_t object_instance) +{ + float value = 100.0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Lighting_Command.Last_On_Value; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the last-on-value + * property value + * @param object_instance - object-instance number of the object + * @param value - floating point last-on-value value + * @return true if the last-on-value property value was set + */ +bool Lighting_Output_Last_On_Value_Set(uint32_t object_instance, float value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) { + pObject->Lighting_Command.Last_On_Value = value; + status = true; + } + } + + return status; +} + +/** + * @brief Handle a WriteProperty to the last-on-value property value + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * @return true if values are within range and property value is set. + */ +static bool Lighting_Output_Last_On_Value_Write( + uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status; + + (void)priority; + status = Lighting_Output_Last_On_Value_Set(object_instance, value); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the default-on-value + * property value + * @param object_instance - object-instance number of the object + * @return default-on-value property value + */ +float Lighting_Output_Default_On_Value(uint32_t object_instance) +{ + float value = 100.0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Lighting_Command.Default_On_Value; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the default-on-value + * property value + * @param object_instance - object-instance number of the object + * @param value - floating point default-on-value value + * @return true if the default-on-value property value was set + */ +bool Lighting_Output_Default_On_Value_Set(uint32_t object_instance, float value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) { + pObject->Lighting_Command.Default_On_Value = value; + status = true; + } + } + + return status; +} + +/** + * @brief Handle a WriteProperty to the default-on-value property value + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * @return true if values are within range and property value is set. + */ +static bool Lighting_Output_Default_On_Value_Write( + uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status; + + (void)priority; + status = Lighting_Output_Default_On_Value_Set(object_instance, value); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the high-end-trim + * property value + * @param object_instance - object-instance number of the object + * @return high=end-trim property value + */ +float Lighting_Output_High_End_Trim(uint32_t object_instance) +{ + float value = 100.0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Lighting_Command.High_Trim_Value; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the high-end-trim + * property value + * @param object_instance - object-instance number of the object + * @param value - floating point high-end-trim value + * @return true if the high-end-trim property value was set + */ +bool Lighting_Output_High_End_Trim_Set(uint32_t object_instance, float value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) { + pObject->Lighting_Command.High_Trim_Value = value; + status = true; + } + } + + return status; +} + +/** + * @brief Handle a WriteProperty to the high-end-trim property value + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * @return true if values are within range and property value is set. + */ +static bool Lighting_Output_High_End_Trim_Write( + uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status; + + (void)priority; + status = Lighting_Output_High_End_Trim_Set(object_instance, value); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the low-end-trim + * property value + * @param object_instance - object-instance number of the object + * @return low-end-trim property value + */ +float Lighting_Output_Low_End_Trim(uint32_t object_instance) +{ + float value = 100.0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Lighting_Command.Low_Trim_Value; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the low-end-trim + * property value + * @param object_instance - object-instance number of the object + * @param value - floating point low-end-trim value + * @return true if the low-end-trim property value was set + */ +bool Lighting_Output_Low_End_Trim_Set(uint32_t object_instance, float value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) { + pObject->Lighting_Command.Low_Trim_Value = value; + status = true; + } + } + + return status; +} + +/** + * @brief Handle a WriteProperty to the low-end-trim property value + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * @return true if values are within range and property value is set. + */ +static bool Lighting_Output_Low_End_Trim_Write( + uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status; + + (void)priority; + status = Lighting_Output_Low_End_Trim_Set(object_instance, value); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the trim-fade-time + * property value + * @param object_instance - object-instance number of the object + * @return trim-fade-time property value + */ +uint32_t Lighting_Output_Trim_Fade_Time(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Trim_Fade_Time; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the trim-fade-time + * property value + * @param object_instance - object-instance number of the object + * @param value - trim-fade-time value + * @return true if the trim-fade-time property value was set + */ +bool Lighting_Output_Trim_Fade_Time_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value <= 86400000UL) { + pObject->Trim_Fade_Time = value; + status = true; + } + } + + return status; +} + +/** + * @brief Handle a WriteProperty to the trim-fade-time property value + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * @return true if values are within range and property value is set. + */ +static bool Lighting_Output_Trim_Fade_Time_Write( + uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status; + + (void)priority; + status = Lighting_Output_Trim_Fade_Time_Set(object_instance, value); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + + return status; +} + +/** + * @brief Set the overridden state of the lighting output + * @note For HOA (hand off-auto) control where the override + * is permanent and prevents lighting-command or present-value + * control of the output. + * @param object_instance [in] BACnet object instance + * @param value [in] new value to set + * @return true if successful, false if not + */ +bool Lighting_Output_Overridden_Set(uint32_t object_instance, float value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Lighting_Command.Overridden_Momentary = false; + pObject->Lighting_Command.Overridden = true; + lighting_command_override(&pObject->Lighting_Command, value); + status = true; + } + + return status; +} + +/** + * @brief Clear the overridden state of the lighting output + * @note For HOA (hand off-auto) control where the override + * is permanent and prevents lighting-command or present-value + * control of the output. + * @param object_instance [in] BACnet object instance + * @return true if successful, false if not + */ +bool Lighting_Output_Overridden_Clear(uint32_t object_instance) +{ + bool status = false; + float value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Lighting_Command.Overridden = false; + pObject->Lighting_Command.Overridden_Momentary = false; + value = Priority_Array_Next_Value(pObject, 0); + lighting_command_override(&pObject->Lighting_Command, value); + status = true; + } + + return status; +} + +/** + * @brief Set the overridden state of the lighting output + * @note For HOA (hand off-auto) control where the override + * is temporary until the next lighting-command or present-value + * is received from BACnet services. + * @param object_instance [in] BACnet object instance + * @param value [in] new value to set + * @return true if successful, false if not + */ +bool Lighting_Output_Overridden_Momentary(uint32_t object_instance, float value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + /* set the override */ + pObject->Lighting_Command.Overridden_Momentary = true; + pObject->Lighting_Command.Overridden = true; + lighting_command_override(&pObject->Lighting_Command, value); + status = true; + } + + return status; +} + +/** + * @brief Check if the lighting output is overridden + * @note From 135-2024-12.54.9 Status_Flags + * OVERRIDDEN Logical TRUE (1) if the point has been overridden + * by some mechanism local to the BACnet device. + * In this context, "overridden" is taken to mean + * that the Present_Value property is not changeable + * through BACnet services. Otherwise, the value is + * logical FALSE (0). + * + * @param object_instance [in] BACnet object instance + * @return true if overridden, false if not + */ +bool Lighting_Output_Overridden_Status(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = pObject->Lighting_Command.Overridden || + pObject->Lighting_Command.Overridden_Momentary; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -2094,6 +2577,7 @@ bool Lighting_Output_Color_Override_Set(uint32_t object_instance, bool value) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { pObject->Color_Override = value; + status = true; } return status; @@ -2295,7 +2779,8 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) bitstring_init(&bit_string); bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + state = Lighting_Output_Overridden_Status(rpdata->object_instance); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, state); state = Lighting_Output_Out_Of_Service(rpdata->object_instance); bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state); apdu_len = encode_application_bitstring(&apdu[0], &bit_string); @@ -2367,6 +2852,28 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_null(&apdu[0]); } break; + case PROP_LAST_ON_VALUE: + real_value = Lighting_Output_Last_On_Value(rpdata->object_instance); + apdu_len = encode_application_real(&apdu[0], real_value); + break; + case PROP_DEFAULT_ON_VALUE: + real_value = + Lighting_Output_Default_On_Value(rpdata->object_instance); + apdu_len = encode_application_real(&apdu[0], real_value); + break; + case PROP_HIGH_END_TRIM: + real_value = Lighting_Output_High_End_Trim(rpdata->object_instance); + apdu_len = encode_application_real(&apdu[0], real_value); + break; + case PROP_LOW_END_TRIM: + real_value = Lighting_Output_Low_End_Trim(rpdata->object_instance); + apdu_len = encode_application_real(&apdu[0], real_value); + break; + case PROP_TRIM_FADE_TIME: + unsigned_value = + Lighting_Output_Trim_Fade_Time(rpdata->object_instance); + apdu_len = encode_application_unsigned(&apdu[0], unsigned_value); + break; case PROP_COLOR_OVERRIDE: apdu_len = encode_application_boolean( &apdu[0], @@ -2453,8 +2960,7 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) if (status) { status = Lighting_Output_Lighting_Command_Write( wp_data->object_instance, &value.type.Lighting_Command, - wp_data->priority, &wp_data->error_class, - &wp_data->error_code); + &wp_data->error_class, &wp_data->error_code); } break; case PROP_OUT_OF_SERVICE: @@ -2515,6 +3021,56 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) &wp_data->error_code); } break; + case PROP_LAST_ON_VALUE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + if (status) { + status = Lighting_Output_Last_On_Value_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_DEFAULT_ON_VALUE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + if (status) { + status = Lighting_Output_Default_On_Value_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_HIGH_END_TRIM: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + if (status) { + status = Lighting_Output_High_End_Trim_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_LOW_END_TRIM: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + if (status) { + status = Lighting_Output_Low_End_Trim_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_TRIM_FADE_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Lighting_Output_Trim_Fade_Time_Write( + wp_data->object_instance, value.type.Unsigned_Int, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; case PROP_BLINK_WARN_ENABLE: status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); diff --git a/src/bacnet/basic/object/lo.h b/src/bacnet/basic/object/lo.h index 9cdf5d30..f757e722 100644 --- a/src/bacnet/basic/object/lo.h +++ b/src/bacnet/basic/object/lo.h @@ -45,13 +45,47 @@ bool Lighting_Output_Present_Value_Set( BACNET_STACK_EXPORT bool Lighting_Output_Present_Value_Relinquish( uint32_t object_instance, unsigned priority); - +BACNET_STACK_EXPORT +bool Lighting_Output_Present_Value_Relinquish_All(uint32_t object_instance); BACNET_STACK_EXPORT float Lighting_Output_Relinquish_Default(uint32_t object_instance); BACNET_STACK_EXPORT bool Lighting_Output_Relinquish_Default_Set( uint32_t object_instance, float value); +BACNET_STACK_EXPORT +float Lighting_Output_Last_On_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_Last_On_Value_Set(uint32_t object_instance, float value); +BACNET_STACK_EXPORT +float Lighting_Output_Default_On_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_Default_On_Value_Set( + uint32_t object_instance, float value); +BACNET_STACK_EXPORT +float Lighting_Output_High_End_Trim(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_High_End_Trim_Set(uint32_t object_instance, float value); +BACNET_STACK_EXPORT +float Lighting_Output_Low_End_Trim(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_Low_End_Trim_Set(uint32_t object_instance, float value); +BACNET_STACK_EXPORT +uint32_t Lighting_Output_Trim_Fade_Time(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_Trim_Fade_Time_Set( + uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +bool Lighting_Output_Overridden_Set(uint32_t object_instance, float value); +BACNET_STACK_EXPORT +bool Lighting_Output_Overridden_Clear(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_Overridden_Status(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Lighting_Output_Overridden_Momentary( + uint32_t object_instance, float value); + BACNET_STACK_EXPORT bool Lighting_Output_Change_Of_Value(uint32_t instance); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/sys/lighting_command.c b/src/bacnet/basic/sys/lighting_command.c index 51d75a91..67a75cf9 100644 --- a/src/bacnet/basic/sys/lighting_command.c +++ b/src/bacnet/basic/sys/lighting_command.c @@ -17,11 +17,11 @@ /** * @brief call the lighting command tracking value callbacks - * @param data - dimmer data + * @param data - dimmer data structure * @param old_value - value prior to write * @param value - value of the write */ -static void lighting_command_tracking_value_handler( +static void lighting_command_tracking_value_notify( struct bacnet_lighting_command_data *data, float old_value, float value) { struct lighting_command_notification *head; @@ -37,6 +37,7 @@ static void lighting_command_tracking_value_handler( /** * @brief Add a Lighting Command notification callback + * @param data - dimmer data structure * @param cb - notification callback to be added */ void lighting_command_notification_add( @@ -59,26 +60,188 @@ void lighting_command_notification_add( } while (head); } +/** + * @brief call the lighting command tracking value callbacks + * @param data - dimmer data structure + * @param old_value - value prior to write + * @param value - value of the write + */ +static void lighting_command_timer_notify( + struct bacnet_lighting_command_data *data, uint16_t milliseconds) +{ + struct lighting_command_timer_notification *head; + + head = &data->Timer_Notification_Head; + do { + if (head->callback) { + head->callback(data, milliseconds); + } + head = head->next; + } while (head); +} + +/** + * @brief Add a Lighting Command notification callback + * @param data - dimmer data structure + * @param cb - notification callback to be added + */ +void lighting_command_timer_notfication_add( + struct bacnet_lighting_command_data *data, + struct lighting_command_timer_notification *notification) +{ + struct lighting_command_timer_notification *head; + + head = &data->Timer_Notification_Head; + do { + if (head->next == notification) { + /* already here! */ + break; + } else if (!head->next) { + /* first available free node */ + head->next = notification; + break; + } + head = head->next; + } while (head); +} + +/** + * @brief Clamps the step increment value + * @param step_increment [in] step increment value + * @return clamped step increment value + */ +float lighting_command_step_increment_clamp(float step_increment) +{ + if (isless(step_increment, 0.1f)) { + step_increment = 0.1f; + } + if (isgreater(step_increment, 100.0f)) { + step_increment = 100.0f; + } + + return step_increment; +} + +/** + * @brief Clamp the value within the operating range between low and high + * end trim values. + * @details The Operating Range is a subset of the Normalized Range, + * that represents the range of acceptable values for control of the object. + * The Operating Range is defined by the High_End_Trim and Low_End_Trim + * property values. When values are written outside of the Operating Range, + * the Tracking_Value will reflect the actual, clamped normalized light + * output while the Present_Value will reflect the original target value. + * @param data - dimmer data structure + * @param value the value that will be subject to clamping + */ +float lighting_command_operating_range_clamp( + struct bacnet_lighting_command_data *data, float value) +{ + /* clamp value within trim values, if non-zero */ + if (isless(value, 1.0f)) { + /* jump target to OFF if below normalized min */ + value = 0.0f; + } else if (isgreater(value, data->High_Trim_Value)) { + value = data->High_Trim_Value; + data->In_Progress = BACNET_LIGHTING_TRIM_ACTIVE; + } else if (isless(value, data->Low_Trim_Value)) { + value = data->Low_Trim_Value; + data->In_Progress = BACNET_LIGHTING_TRIM_ACTIVE; + } + + return value; +} + +/** + * @brief Clamp the value within the normalized ON range 1% to 100%. + * @details The physical output level, or non-normalized range, + * is specified as the linearized percentage (0..100%) + * of the possible light output range with 0.0% being off, + * 1.0% being dimmest, and 100.0% being brightest. + * The actual range represents the subset of physical output levels + * defined by Min_Actual_Value and Max_Actual_Value + * (or 1.0 to 100.0% if these properties are not present). + * The normalized range is always 0.0 to 100.0% where + * 1.0% = bottom of the actual range and 100.0% = top of the actual range. + * @param data - dimmer data structure + * @param value [in] value to normalize + * @return normalized value within the range defined by Min_Actual_Value + * and Max_Actual_Value + */ +float lighting_command_normalized_on_range_clamp( + struct bacnet_lighting_command_data *data, float value) +{ + /* clamp value within trim values, if non-zero */ + if (isgreater(value, data->Max_Actual_Value)) { + value = data->Max_Actual_Value; + } else if (isless(value, data->Min_Actual_Value)) { + value = data->Min_Actual_Value; + } + + return value; +} + +/** + * @brief Normalize the value to the min/max range + * @details The physical output level, or non-normalized range, + * is specified as the linearized percentage (0..100%) + * of the possible light output range with 0.0% being off, + * 1.0% being dimmest, and 100.0% being brightest. + * The actual range represents the subset of physical output levels + * defined by Min_Actual_Value and Max_Actual_Value + * (or 1.0 to 100.0% if these properties are not present). + * The normalized range is always 0.0 to 100.0% where + * 1.0% = bottom of the actual range and 100.0% = top of the actual range. + * @param data - dimmer data structure + * @param value [in] value to normalize + * @return normalized value within the range defined by + * 0.0%, Min_Actual_Value, and Max_Actual_Value + */ +float lighting_command_normalized_range_clamp( + struct bacnet_lighting_command_data *data, float value) +{ + float normalized_value; + + /* clamp value within normalized values, if non-zero */ + if (isless(value, 1.0f)) { + /* jump target to OFF if below normalized min */ + normalized_value = 0.0f; + } else if (isgreater(value, data->Max_Actual_Value)) { + normalized_value = data->Max_Actual_Value; + } else if (isless(value, data->Min_Actual_Value)) { + normalized_value = data->Min_Actual_Value; + } else { + normalized_value = value; + } + + return normalized_value; +} + /** * @brief Callback for tracking value updates - * @param data - dimmer data + * @param data - dimmer data structure * @param old_value - value prior to write * @param value - value of the write */ -static void lighting_command_tracking_value_notify( +static void lighting_command_tracking_value_event( struct bacnet_lighting_command_data *data, float old_value, float value) { - if (!data->Out_Of_Service) { - /* clamp value within trim values, if non-zero */ - if (isless(value, 1.0f)) { - /* jump target to OFF if below normalized min */ - value = 0.0f; - } else if (isgreater(value, data->High_Trim_Value)) { - value = data->High_Trim_Value; - } else if (isless(value, data->Low_Trim_Value)) { - value = data->Low_Trim_Value; + if (data->Overridden) { + value = lighting_command_operating_range_clamp(data, value); + if (isgreaterequal(value, 1.0)) { + data->Last_On_Value = value; } - lighting_command_tracking_value_handler(data, old_value, value); + lighting_command_tracking_value_notify(data, old_value, value); + if (data->Overridden_Momentary) { + data->Overridden = false; + } + } else if (!data->Out_Of_Service) { + data->Overridden_Momentary = false; + value = lighting_command_operating_range_clamp(data, value); + if (isgreaterequal(value, 1.0)) { + data->Last_On_Value = value; + } + lighting_command_tracking_value_notify(data, old_value, value); } else { debug_printf( "Lighting-Command[%lu]-Out-of-Service\n", (unsigned long)data->Key); @@ -88,7 +251,7 @@ static void lighting_command_tracking_value_notify( /** * Handles the timing for a single Lighting Output object Fade * - * @param data [in] dimmer data + * @param data - dimmer data structure * @param milliseconds - number of milliseconds elapsed since previously * called. Works best when called about every 10 milliseconds. */ @@ -97,19 +260,11 @@ static void lighting_command_fade_handler( { float old_value; float x1, x2, x3, y1, y3; - float target_value, min_value, max_value; + float target_value; old_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; - target_value = data->Target_Level; - /* clamp target within min/max */ - if (isgreater(target_value, max_value)) { - target_value = max_value; - } - if (isless(target_value, min_value)) { - target_value = min_value; - } + target_value = + lighting_command_normalized_on_range_clamp(data, data->Target_Level); if ((milliseconds >= data->Fade_Time) || (!islessgreater(data->Tracking_Value, target_value))) { /* stop fading */ @@ -127,8 +282,8 @@ static void lighting_command_fade_handler( x1 = 0.0f; x2 = (float)milliseconds; x3 = (float)data->Fade_Time; - if (isless(old_value, min_value)) { - y1 = min_value; + if (isless(old_value, data->Min_Actual_Value)) { + y1 = data->Min_Actual_Value; } else { y1 = old_value; } @@ -137,7 +292,7 @@ static void lighting_command_fade_handler( data->Fade_Time -= milliseconds; data->In_Progress = BACNET_LIGHTING_FADE_ACTIVE; } - lighting_command_tracking_value_notify( + lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -153,25 +308,17 @@ static void lighting_command_fade_handler( * progress of the ramp. shall be clamped to * Min_Actual_Value and Max_Actual_Value. * - * @param data [in] dimmer data + * @param data - dimmer data structure * @param milliseconds - number of milliseconds elapsed */ static void lighting_command_ramp_handler( struct bacnet_lighting_command_data *data, uint16_t milliseconds) { - float old_value, target_value, min_value, max_value, step_value, steps; + float old_value, target_value, step_value, steps; old_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; - target_value = data->Target_Level; - /* clamp target within min/max, if needed */ - if (isgreater(target_value, max_value)) { - target_value = max_value; - } - if (isless(target_value, min_value)) { - target_value = min_value; - } + target_value = + lighting_command_normalized_on_range_clamp(data, data->Target_Level); /* determine the number of steps */ if (milliseconds <= 1000) { /* percent per second */ @@ -213,12 +360,8 @@ static void lighting_command_ramp_handler( data->Lighting_Operation = BACNET_LIGHTS_STOP; } /* clamp target within min/max, if needed */ - if (isgreater(step_value, max_value)) { - step_value = max_value; - } - if (isless(step_value, min_value)) { - step_value = min_value; - } + step_value = + lighting_command_normalized_on_range_clamp(data, step_value); if (data->Lighting_Operation == BACNET_LIGHTS_STOP) { if (isless(data->Target_Level, 1.0f)) { /* jump target to OFF if below normalized min */ @@ -232,28 +375,10 @@ static void lighting_command_ramp_handler( data->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE; } } - lighting_command_tracking_value_notify( + lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } -/** - * Clamps the step increment value - * - * @param step_increment [in] step increment value - * @return clamped step increment value - */ -static float lighting_command_step_increment_clamp(float step_increment) -{ - if (isless(step_increment, 0.1f)) { - step_increment = 0.1f; - } - if (isgreater(step_increment, 100.0f)) { - step_increment = 100.0f; - } - - return step_increment; -} - /** * Updates the object tracking value while stepping * @@ -266,58 +391,22 @@ static float lighting_command_step_increment_clamp(float step_increment) static void lighting_command_step_up_handler(struct bacnet_lighting_command_data *data) { - float old_value, target_value, min_value, max_value, step_value; + float old_value, target_value, step_value; old_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; step_value = lighting_command_step_increment_clamp(data->Step_Increment); /* inhibit ON if the value is already OFF */ - if (isgreaterequal(old_value, min_value)) { + if (isgreaterequal(old_value, data->Min_Actual_Value)) { target_value = old_value + step_value; - /* clamp target within min/max, if needed */ - if (isgreater(target_value, max_value)) { - target_value = max_value; - } - if (isless(target_value, min_value)) { - target_value = min_value; - } - data->Tracking_Value = target_value; + data->Tracking_Value = + lighting_command_normalized_on_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; - lighting_command_tracking_value_notify( + lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } } -/** - * Normalize the value to the min/max range - * @param value [in] value to normalize - * @param min_value [in] minimum value - * @param max_value [in] maximum value - * @return normalized value - */ -static float -lighting_command_normalize_value(float value, float min_value, float max_value) -{ - float normalized_value; - /* clamp target within min/max, if needed */ - if (isgreater(value, max_value)) { - /* clamp target within max */ - normalized_value = max_value; - } else if (isless(value, 1.0f)) { - /* jump target to OFF if below normalized min */ - normalized_value = 0.0f; - } else if (isless(value, min_value)) { - /* clamp target within min */ - normalized_value = min_value; - } else { - normalized_value = value; - } - - return normalized_value; -} - /** * Updates the object tracking value while stepping * @@ -330,30 +419,20 @@ lighting_command_normalize_value(float value, float min_value, float max_value) static void lighting_command_step_down_handler(struct bacnet_lighting_command_data *data) { - float old_value, target_value, min_value, max_value, step_value; + float old_value, target_value, step_value; old_value = target_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; step_value = lighting_command_step_increment_clamp(data->Step_Increment); if (isgreaterequal(target_value, step_value)) { target_value -= step_value; } else { - target_value = 0.0f; + target_value = data->Min_Actual_Value; } - /* clamp target within min/max, if needed */ - if (isgreater(target_value, max_value)) { - /* clamp target within max */ - target_value = max_value; - } - if (isless(target_value, min_value)) { - /* clamp target within min */ - target_value = min_value; - } - data->Tracking_Value = target_value; + data->Tracking_Value = + lighting_command_normalized_on_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; - lighting_command_tracking_value_notify( + lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -369,18 +448,16 @@ lighting_command_step_down_handler(struct bacnet_lighting_command_data *data) static void lighting_command_step_on_handler(struct bacnet_lighting_command_data *data) { - float old_value, target_value, min_value, max_value, step_value; + float old_value, target_value, step_value; old_value = target_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; step_value = lighting_command_step_increment_clamp(data->Step_Increment); target_value += step_value; data->Tracking_Value = - lighting_command_normalize_value(target_value, min_value, max_value); + lighting_command_normalized_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; - lighting_command_tracking_value_notify( + lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -396,11 +473,9 @@ lighting_command_step_on_handler(struct bacnet_lighting_command_data *data) static void lighting_command_step_off_handler(struct bacnet_lighting_command_data *data) { - float old_value, target_value, min_value, max_value, step_value; + float old_value, target_value, step_value; old_value = target_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; step_value = lighting_command_step_increment_clamp(data->Step_Increment); if (isgreaterequal(target_value, step_value)) { target_value -= step_value; @@ -408,10 +483,10 @@ lighting_command_step_off_handler(struct bacnet_lighting_command_data *data) target_value = 0.0f; } data->Tracking_Value = - lighting_command_normalize_value(target_value, min_value, max_value); + lighting_command_normalized_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; - lighting_command_tracking_value_notify( + lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -424,18 +499,17 @@ lighting_command_step_off_handler(struct bacnet_lighting_command_data *data) * @note The WARN, WARN_RELINQUISH, and WARN_OFF lighting * commands, as well as writing one of the special values to the * Present_Value property, cause a blink-warn notification - * to occur at the specified priority. A blink-warn - * notification is used to warn the occupants that the lights - * are about to turn off, giving the occupants the opportunity - * to exit the space or to override the lights for a period of time. + * to occur. A blink-warn notification is used to warn the + * occupants that the lights are about to turn off, giving + * the occupants the opportunity to exit the space or to + * override the lights for a period of time. * * The actual blink-warn notification mechanism shall be a local matter. * The physical lights may blink once, multiple times, or * repeatedly. They may also go bright, go dim, or signal a notification * through some other means. In some circumstances, no blink-warn * notification will occur at all. The blink-warn notification - * shall not be reflected in the priority array or the tracking - * value. + * shall not be reflected in the tracking value. * * @param data [in] dimmer data * @param milliseconds - number of milliseconds elapsed @@ -443,11 +517,9 @@ lighting_command_step_off_handler(struct bacnet_lighting_command_data *data) static void lighting_command_blink_handler( struct bacnet_lighting_command_data *data, uint16_t milliseconds) { - float old_value, target_value, min_value, max_value; + float old_value, target_value; old_value = data->Tracking_Value; - min_value = data->Min_Actual_Value; - max_value = data->Max_Actual_Value; /* detect 'end' operation */ if (data->Blink.Duration > milliseconds) { data->Blink.Duration -= milliseconds; @@ -494,14 +566,30 @@ static void lighting_command_blink_handler( } } } - target_value = - lighting_command_normalize_value(target_value, min_value, max_value); + target_value = lighting_command_normalized_range_clamp(data, target_value); /* note: The blink-warn notifications shall not be reflected in the tracking value. */ if (data->In_Progress == BACNET_LIGHTING_IDLE) { data->Tracking_Value = target_value; } - lighting_command_tracking_value_notify(data, old_value, target_value); + lighting_command_tracking_value_event(data, old_value, target_value); +} + +/** + * @brief Overrides the current lighting command if overridden is true + * @param data [in] dimmer data + */ +void lighting_command_override( + struct bacnet_lighting_command_data *data, float value) +{ + float old_value; + + if (!data) { + return; + } + old_value = data->Tracking_Value; + data->Tracking_Value = value; + lighting_command_tracking_value_event(data, old_value, value); } /** @@ -516,6 +604,9 @@ void lighting_command_timer( if (!data) { return; } + if (data->Overridden) { + data->Lighting_Operation = BACNET_LIGHTS_NONE; + } switch (data->Lighting_Operation) { case BACNET_LIGHTS_NONE: data->In_Progress = BACNET_LIGHTING_IDLE; @@ -546,13 +637,20 @@ void lighting_command_timer( case BACNET_LIGHTS_STOP: data->In_Progress = BACNET_LIGHTING_IDLE; break; + case BACNET_LIGHTS_RESTORE_ON: + case BACNET_LIGHTS_DEFAULT_ON: + case BACNET_LIGHTS_TOGGLE_RESTORE: + case BACNET_LIGHTS_TOGGLE_DEFAULT: + lighting_command_fade_handler(data, milliseconds); + break; default: break; } + lighting_command_timer_notify(data, milliseconds); } /** - * @brief Set the lighting command if the priority is active + * @brief Set the lighting command to perform a fade to value operation * @param data [in] dimmer data * @param value [in] BACnet lighting value * @param fade_time [in] BACnet lighting fade time @@ -586,7 +684,7 @@ void lighting_command_ramp_to( } /** - * @brief Set the lighting command if the priority is active + * @brief Set the lighting command to perform a step increment operation * @param data [in] dimmer object instance * @param operation [in] BACnet lighting operation * @param step_increment [in] BACnet lighting step increment value @@ -606,7 +704,7 @@ void lighting_command_step( } /** - * @brief Set the lighting command to blink mode + * @brief Set the lighting command to perform a blink operation * @param data [in] dimmer object instance * @param operation [in] BACnet lighting operation for blink warn * @param blink [in] BACnet blink data @@ -634,7 +732,7 @@ void lighting_command_blink_warn( } /** - * @brief Set the lighting command if the priority is active + * @brief Set the lighting command to perform a stop operation * @param object [in] BACnet object instance * @param priority [in] BACnet priority array value 1..16 */ @@ -647,7 +745,7 @@ void lighting_command_stop(struct bacnet_lighting_command_data *data) } /** - * @brief Set the lighting command if the priority is active + * @brief Set the lighting command to perform no operations * @param object [in] BACnet object instance * @param priority [in] BACnet priority array value 1..16 */ @@ -659,6 +757,82 @@ void lighting_command_none(struct bacnet_lighting_command_data *data) data->Lighting_Operation = BACNET_LIGHTS_NONE; } +/** + * @brief Set the lighting command to perform a restore-on operation + * @param data [in] dimmer data + * @param fade_time [in] BACnet lighting fade time + */ +void lighting_command_restore_on( + struct bacnet_lighting_command_data *data, uint32_t fade_time) +{ + if (!data) { + return; + } + data->Fade_Time = fade_time; + data->Lighting_Operation = BACNET_LIGHTS_RESTORE_ON; + data->Target_Level = data->Last_On_Value; +} + +/** + * @brief Set the lighting command to perform a default-on operation + * @param data [in] dimmer data + * @param fade_time [in] BACnet lighting fade time + */ +void lighting_command_default_on( + struct bacnet_lighting_command_data *data, uint32_t fade_time) +{ + if (!data) { + return; + } + data->Fade_Time = fade_time; + data->Lighting_Operation = BACNET_LIGHTS_DEFAULT_ON; + data->Target_Level = data->Default_On_Value; +} + +/** + * @brief Set the lighting command to perform a toggle restore operation + * @param data [in] dimmer data + * @param fade_time [in] BACnet lighting fade time + */ +void lighting_command_toggle_restore( + struct bacnet_lighting_command_data *data, uint32_t fade_time) +{ + if (!data) { + return; + } + data->Fade_Time = fade_time; + data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_RESTORE; + if (isless(data->Tracking_Value, 1.0f)) { + /* OFF: write the Last_On_Value */ + data->Target_Level = data->Last_On_Value; + } else { + /* not OFF, write 0.0% */ + data->Target_Level = 0.0f; + } +} + +/** + * @brief Set the lighting command to perform a toggle default operation + * @param data [in] dimmer data + * @param fade_time [in] BACnet lighting fade time + */ +void lighting_command_toggle_default( + struct bacnet_lighting_command_data *data, uint32_t fade_time) +{ + if (!data) { + return; + } + data->Fade_Time = fade_time; + data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_DEFAULT; + if (isless(data->Tracking_Value, 1.0f)) { + /* OFF: write the Default_On_Value */ + data->Target_Level = data->Default_On_Value; + } else { + /* not OFF, write 0.0% */ + data->Target_Level = 0.0f; + } +} + void lighting_command_init(struct bacnet_lighting_command_data *data) { if (!data) { @@ -666,12 +840,15 @@ void lighting_command_init(struct bacnet_lighting_command_data *data) } data->Tracking_Value = 0.0f; data->Lighting_Operation = BACNET_LIGHTS_NONE; - data->In_Progress = BACNET_LIGHTING_IDLE; + data->In_Progress = BACNET_LIGHTING_NOT_CONTROLLED; data->Out_Of_Service = false; data->Min_Actual_Value = 1.0f; data->Max_Actual_Value = 100.0f; data->Low_Trim_Value = 1.0f; data->High_Trim_Value = 100.0f; + data->Last_On_Value = 100.0f; + data->Default_On_Value = 100.0f; + data->Overridden = false; data->Blink.On_Value = 100.0f; data->Blink.Off_Value = 0.0f; data->Blink.End_Value = 0.0f; diff --git a/src/bacnet/basic/sys/lighting_command.h b/src/bacnet/basic/sys/lighting_command.h index 946ce061..5246f459 100644 --- a/src/bacnet/basic/sys/lighting_command.h +++ b/src/bacnet/basic/sys/lighting_command.h @@ -14,6 +14,7 @@ /** * @brief Callback for tracking value updates + * @param key - key used to link to specific light * @param old_value - value prior to write * @param value - value of the write */ @@ -25,6 +26,21 @@ struct lighting_command_notification { lighting_command_tracking_value_callback callback; }; +/* forward prototype of the structure defined later */ +struct bacnet_lighting_command_data; + +/** + * @brief Callback for non-standard Lighting_Operation timer values + * @param data - Lighting Command data structure + * @param milliseconds - elapsed time in milliseconds + */ +typedef void (*lighting_command_timer_callback)( + struct bacnet_lighting_command_data *data, uint16_t milliseconds); +struct lighting_command_timer_notification { + struct lighting_command_timer_notification *next; + lighting_command_timer_callback callback; +}; + typedef struct bacnet_lighting_command_warn_data { /* warn */ float On_Value; @@ -51,12 +67,17 @@ typedef struct bacnet_lighting_command_data { float Max_Actual_Value; float High_Trim_Value; float Low_Trim_Value; + float Default_On_Value; + float Last_On_Value; BACNET_LIGHTING_COMMAND_WARN_DATA Blink; /* bits - in common area of structure */ bool Out_Of_Service : 1; + bool Overridden : 1; + bool Overridden_Momentary : 1; /* key used with callback */ uint32_t Key; struct lighting_command_notification Notification_Head; + struct lighting_command_timer_notification Timer_Notification_Head; } BACNET_LIGHTING_COMMAND_DATA; #ifdef __cplusplus @@ -86,12 +107,45 @@ BACNET_STACK_EXPORT void lighting_command_stop(struct bacnet_lighting_command_data *data); BACNET_STACK_EXPORT void lighting_command_none(struct bacnet_lighting_command_data *data); +BACNET_STACK_EXPORT +void lighting_command_restore_on( + struct bacnet_lighting_command_data *data, uint32_t fade_time); +BACNET_STACK_EXPORT +void lighting_command_default_on( + struct bacnet_lighting_command_data *data, uint32_t fade_time); +BACNET_STACK_EXPORT +void lighting_command_toggle_restore( + struct bacnet_lighting_command_data *data, uint32_t fade_time); +BACNET_STACK_EXPORT +void lighting_command_toggle_default( + struct bacnet_lighting_command_data *data, uint32_t fade_time); + +BACNET_STACK_EXPORT +void lighting_command_override( + struct bacnet_lighting_command_data *data, float value); + +BACNET_STACK_EXPORT +float lighting_command_step_increment_clamp(float step_increment); +BACNET_STACK_EXPORT +float lighting_command_operating_range_clamp( + struct bacnet_lighting_command_data *data, float value); +BACNET_STACK_EXPORT +float lighting_command_normalized_range_clamp( + struct bacnet_lighting_command_data *data, float value); +BACNET_STACK_EXPORT +float lighting_command_normalized_on_range_clamp( + struct bacnet_lighting_command_data *data, float value); + BACNET_STACK_EXPORT void lighting_command_init(struct bacnet_lighting_command_data *data); BACNET_STACK_EXPORT void lighting_command_notification_add( struct bacnet_lighting_command_data *data, struct lighting_command_notification *notification); +BACNET_STACK_EXPORT +void lighting_command_timer_notfication_add( + struct bacnet_lighting_command_data *data, + struct lighting_command_timer_notification *notification); #ifdef __cplusplus } diff --git a/src/bacnet/property.c b/src/bacnet/property.c index d5571d06..d35274af 100644 --- a/src/bacnet/property.c +++ b/src/bacnet/property.c @@ -1648,6 +1648,8 @@ static const int Lighting_Output_Properties_Required[] = { PROP_RELINQUISH_DEFAULT, PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY, PROP_CURRENT_COMMAND_PRIORITY, + PROP_LAST_ON_VALUE, + PROP_DEFAULT_ON_VALUE, -1 }; @@ -1670,6 +1672,12 @@ static const int Lighting_Output_Properties_Optional[] = { PROP_AUDIT_LEVEL, PROP_AUDITABLE_OPERATIONS, PROP_AUDIT_PRIORITY_FILTER, + PROP_COLOR_REFERENCE, + PROP_COLOR_OVERRIDE, + PROP_OVERRIDE_COLOR_REFERENCE, + PROP_HIGH_END_TRIM, + PROP_LOW_END_TRIM, + PROP_TRIM_FADE_TIME, PROP_TAGS, PROP_PROFILE_LOCATION, PROP_PROFILE_NAME, diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt index 2d65a162..80fefc4c 100644 --- a/test/bacnet/basic/object/lo/CMakeLists.txt +++ b/test/bacnet/basic/object/lo/CMakeLists.txt @@ -23,6 +23,7 @@ set(ZTST_DIR "${TST_DIR}/ztest/src") add_compile_definitions( BIG_ENDIAN=0 CONFIG_ZTEST=1 + BACNET_PROTOCOL_REVISION=28 ) include_directories( diff --git a/test/bacnet/basic/object/lo/src/main.c b/test/bacnet/basic/object/lo/src/main.c index b2925c30..5ee8a64a 100644 --- a/test/bacnet/basic/object/lo/src/main.c +++ b/test/bacnet/basic/object/lo/src/main.c @@ -1,15 +1,12 @@ -/* - * Copyright (c) 2020 Legrand North America, LLC. - * +/* @file + * @brief test BACnet Lighting Output object APIs + * @date 2013 + * @author Steve Karg * @copyright SPDX-License-Identifier: MIT */ - -/* @file - * @brief test BACnet integer encode/decode APIs - */ - #include #include +#include #include /** @@ -17,6 +14,33 @@ * @{ */ +/** + * @brief compare two floating point values to 3 decimal places + * + * @param x1 - first comparison value + * @param x2 - second comparison value + * @return true if the value is the same to 3 decimal points + */ +static bool is_float_equal(float x1, float x2) +{ + return fabs(x1 - x2) < 0.001; +} + +static float Test_Tracking_Value; +/** + * @brief Callback for tracking value updates + * @param key - key used to link to specific light + * @param old_value - value prior to write + * @param value - value of the write + */ +static void lighting_command_tracking_value_observer( + uint32_t key, float old_value, float value) +{ + (void)key; + (void)old_value; + Test_Tracking_Value = value; +} + /** * @brief Test */ @@ -36,17 +60,30 @@ static void testLightingOutput(void) const uint32_t instance = 123; BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; bool status = false; - unsigned index; + unsigned index, count; uint16_t milliseconds = 10; + uint32_t test_instance; const char *test_name = NULL; char *sample_name = "sample"; + BACNET_LIGHTING_COMMAND lighting_command = { 0 }; + BACNET_LIGHTING_IN_PROGRESS in_progress; + float real_value, test_real; + uint32_t unsigned_value, test_unsigned; + unsigned priority, test_priority; + BACNET_OBJECT_ID object_id, test_object_id; Lighting_Output_Init(); Lighting_Output_Create(instance); status = Lighting_Output_Valid_Instance(instance); zassert_true(status, NULL); + status = Lighting_Output_Valid_Instance(BACNET_MAX_INSTANCE); + zassert_false(status, NULL); index = Lighting_Output_Instance_To_Index(instance); zassert_equal(index, 0, NULL); + count = Lighting_Output_Count(); + zassert_equal(count, 1, NULL); + test_instance = Lighting_Output_Index_To_Instance(0); + zassert_equal(test_instance, instance, NULL); rpdata.application_data = &apdu[0]; rpdata.application_data_len = sizeof(apdu); @@ -88,6 +125,27 @@ static void testLightingOutput(void) "property '%s': WriteProperty Unknown!\n", bactext_property_name(rpdata.object_property)); } + if (property_list_commandable_member( + wpdata.object_type, wpdata.object_property)) { + wpdata.priority = 16; + status = Lighting_Output_Write_Property(&wpdata); + zassert_true(status, NULL); + wpdata.application_data_len = + encode_application_null(wpdata.application_data); + wpdata.priority = 16; + status = Lighting_Output_Write_Property(&wpdata); + zassert_true(status, NULL); + wpdata.priority = 6; + status = Lighting_Output_Write_Property(&wpdata); + zassert_false(status, NULL); + zassert_equal( + wpdata.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL); + wpdata.priority = 0; + status = Lighting_Output_Write_Property(&wpdata); + zassert_false(status, NULL); + zassert_equal( + wpdata.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL); + } } pRequired++; } @@ -129,6 +187,7 @@ static void testLightingOutput(void) rpdata.object_property = PROP_ALL; len = Lighting_Output_Read_Property(&rpdata); zassert_equal(len, BACNET_STATUS_ERROR, NULL); + wpdata.object_property = PROP_ALL; status = Lighting_Output_Write_Property(&wpdata); zassert_false(status, NULL); /* check the dimming/ramping/stepping engine*/ @@ -142,9 +201,268 @@ static void testLightingOutput(void) zassert_true(status, NULL); test_name = Lighting_Output_Name_ASCII(instance); zassert_equal(test_name, NULL, NULL); + /* test the ASCII description get/set */ + status = Lighting_Output_Description_Set(instance, sample_name); + zassert_true(status, NULL); + test_name = Lighting_Output_Description(instance); + zassert_equal(test_name, sample_name, NULL); + /* test local control API */ + lighting_command.operation = BACNET_LIGHTS_NONE; + do { + status = + Lighting_Output_Lighting_Command_Set(instance, &lighting_command); + if (status) { + zassert_true(status, NULL); + } else { + printf( + "lighting-command operation[%d] %s not supported.\n", + lighting_command.operation, + bactext_lighting_operation_name(lighting_command.operation)); + } + if (lighting_command.operation == BACNET_LIGHTS_PROPRIETARY_MIN) { + /* skip to make testing faster */ + lighting_command.operation = BACNET_LIGHTS_PROPRIETARY_MAX; + } else if (lighting_command.operation == BACNET_LIGHTS_RESERVED_MIN) { + /* skip to make testing faster */ + lighting_command.operation = BACNET_LIGHTS_RESERVED_MAX; + } else { + lighting_command.operation++; + } + } while (lighting_command.operation <= BACNET_LIGHTS_PROPRIETARY_MAX); + /* test the present-value APIs */ + Lighting_Output_Present_Value_Relinquish_All(instance); + real_value = 1.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + test_real = Lighting_Output_Present_Value(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + test_priority = Lighting_Output_Present_Value_Priority(instance); + zassert_equal( + priority, test_priority, "priority=%u test_priority=%u", priority, + test_priority); + Lighting_Output_Present_Value_Relinquish(instance, priority); + real_value = Lighting_Output_Relinquish_Default(instance); + test_real = Lighting_Output_Present_Value(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + /* test the present-value special values */ + status = Lighting_Output_Default_Fade_Time_Set(instance, 100); + zassert_true(status, NULL); + status = Lighting_Output_Default_Ramp_Rate_Set(instance, 100.0f); + zassert_true(status, NULL); + status = Lighting_Output_Egress_Time_Set(instance, 0); + zassert_true(status, NULL); + status = Lighting_Output_Default_Step_Increment_Set(instance, 1.0f); + zassert_true(status, NULL); + status = Lighting_Output_Transition_Set( + instance, BACNET_LIGHTING_TRANSITION_NONE); + zassert_true(status, NULL); + /* WARN - start with lights on */ + real_value = -1.0; + priority = 8; + status = Lighting_Output_Present_Value_Set(instance, 100.0f, priority); + zassert_true(status, NULL); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + status = Lighting_Output_Present_Value_Set(instance, real_value, priority); + zassert_true(status, NULL); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_OTHER, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + /* WARN RELINQUISH */ + real_value = -2.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + real_value = -3.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + real_value = -4.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + real_value = -5.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + real_value = -6.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + real_value = -7.0; + priority = 8; + Lighting_Output_Present_Value_Set(instance, real_value, priority); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + status = Lighting_Output_In_Progress_Set( + instance, BACNET_LIGHTING_NOT_CONTROLLED); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_NOT_CONTROLLED, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + /* tracking-value */ + real_value = 100.0f; + status = Lighting_Output_Tracking_Value_Set(instance, real_value); + zassert_true(status, NULL); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + /* blink-warn features */ + status = Lighting_Output_Blink_Warn_Feature_Set(instance, 0.0f, 0, 65535); + zassert_true(status, NULL); + status = Lighting_Output_Blink_Warn_Feature_Set(instance, -1.0f, 0, 65535); + zassert_true(status, NULL); + status = Lighting_Output_Blink_Warn_Feature_Set(instance, 101.0f, 0, 65535); + zassert_true(status, NULL); + /* egress-time */ + unsigned_value = 5 * 60; + status = Lighting_Output_Egress_Time_Set(instance, unsigned_value); + zassert_true(status, NULL); + test_unsigned = Lighting_Output_Egress_Time(instance); + zassert_equal(unsigned_value, test_unsigned, NULL); + /* default-fade-time */ + unsigned_value = 5 * 60 * 1000; + status = Lighting_Output_Default_Fade_Time_Set(instance, unsigned_value); + zassert_true(status, NULL); + test_unsigned = Lighting_Output_Default_Fade_Time(instance); + zassert_equal(unsigned_value, test_unsigned, NULL); + /* default-ramp-rate */ + real_value = 1.0f; + status = Lighting_Output_Default_Ramp_Rate_Set(instance, real_value); + zassert_true(status, NULL); + test_real = Lighting_Output_Default_Ramp_Rate(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + /* default-step-increment */ + real_value = 2.0f; + status = Lighting_Output_Default_Step_Increment_Set(instance, real_value); + zassert_true(status, NULL); + test_real = Lighting_Output_Default_Step_Increment(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + /* relinquish-default */ + real_value = 0.0f; + status = Lighting_Output_Relinquish_Default_Set(instance, real_value); + zassert_true(status, NULL); + test_real = Lighting_Output_Relinquish_Default(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + /* overridden */ + real_value = 88.0f; + status = Lighting_Output_Overridden_Set(instance, real_value); + zassert_true(status, NULL); + status = Lighting_Output_Overridden_Status(instance); + zassert_true(status, NULL); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true( + is_float_equal(test_real, real_value), "value=%f test_value=%f", + real_value, test_real); + status = Lighting_Output_Present_Value_Set(instance, 99.0f, priority); + zassert_true(status, NULL); + Lighting_Output_Timer(instance, 10); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true( + is_float_equal(test_real, real_value), "value=%f test_value=%f", + real_value, test_real); + real_value = Lighting_Output_Present_Value(instance); + status = Lighting_Output_Overridden_Clear(instance); + zassert_true(status, NULL); + Lighting_Output_Timer(instance, 10); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + /* overridden-momentary */ + real_value = 77.0f; + status = Lighting_Output_Overridden_Momentary(instance, real_value); + zassert_true(status, NULL); + status = Lighting_Output_Overridden_Status(instance); + zassert_true(status, NULL); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true( + is_float_equal(test_real, real_value), "value=%f test_value=%f", + real_value, test_real); + status = Lighting_Output_Present_Value_Set(instance, 98.0f, priority); + Lighting_Output_Timer(instance, 10); + real_value = Lighting_Output_Present_Value(instance); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true( + is_float_equal(test_real, real_value), "value=%f test_value=%f", + real_value, test_real); + /* color-override */ + status = Lighting_Output_Color_Override_Set(instance, true); + zassert_true(status, NULL); + status = Lighting_Output_Color_Override(instance); + zassert_true(status, NULL); + status = Lighting_Output_Color_Override_Set(instance, false); + zassert_true(status, NULL); + status = Lighting_Output_Color_Override(instance); + zassert_false(status, NULL); + /* color-reference */ + object_id.instance = 1; + object_id.type = OBJECT_COLOR; + status = Lighting_Output_Color_Reference_Set(instance, &object_id); + zassert_true(status, NULL); + status = Lighting_Output_Color_Reference(instance, &test_object_id); + zassert_true(status, NULL); + zassert_equal(object_id.instance, test_object_id.instance, NULL); + zassert_equal(object_id.type, test_object_id.type, NULL); + /* override-color-reference */ + object_id.instance = 2; + object_id.type = OBJECT_COLOR; + status = Lighting_Output_Override_Color_Reference_Set(instance, &object_id); + zassert_true(status, NULL); + status = + Lighting_Output_Override_Color_Reference(instance, &test_object_id); + zassert_true(status, NULL); + zassert_equal(object_id.instance, test_object_id.instance, NULL); + zassert_equal(object_id.type, test_object_id.type, NULL); + /* tracking-value observer */ + real_value = 95.0f; + Lighting_Output_Write_Present_Value_Callback_Set( + lighting_command_tracking_value_observer); + status = Lighting_Output_Present_Value_Set(instance, real_value, priority); + zassert_true(status, NULL); + Lighting_Output_Timer(instance, 10); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, real_value), NULL); + zassert_true(is_float_equal(Test_Tracking_Value, real_value), NULL); + + /* out-of-bounds */ + test_instance = Lighting_Output_Create(BACNET_MAX_INSTANCE + 1); + zassert_equal(test_instance, BACNET_MAX_INSTANCE, NULL); /* check the delete function */ status = Lighting_Output_Delete(instance); zassert_true(status, NULL); + test_instance = Lighting_Output_Create(BACNET_MAX_INSTANCE); + zassert_equal(test_instance, 1, NULL); + Lighting_Output_Cleanup(); return; } diff --git a/test/bacnet/basic/sys/lighting_command/src/main.c b/test/bacnet/basic/sys/lighting_command/src/main.c index 9a587292..2f76513b 100644 --- a/test/bacnet/basic/sys/lighting_command/src/main.c +++ b/test/bacnet/basic/sys/lighting_command/src/main.c @@ -1,11 +1,7 @@ /* @file - * @brief test BACnet integer encode/decode APIs + * @brief test BACnet Lighting Command encode/decode APIs * @date June 2022 - * @brief tests sRGB to and from from CIE xy and brightness API - * - * @section LICENSE - * Copyright (c) 2022 Steve Karg - * + * @author Steve Karg * @copyright SPDX-License-Identifier: MIT */ #include @@ -20,6 +16,7 @@ */ static float Tracking_Value; +static uint16_t Tracking_Elapsed_Milliseconds; /** * @brief Callback for tracking value updates @@ -33,6 +30,25 @@ static void dimmer_tracking_value(uint32_t key, float old_value, float value) Tracking_Value = value; } +/** + * @brief Callback for non-standard Lighting_Operation timer values + * @param data - Lighting Command data structure + * @param milliseconds - elapsed time in milliseconds + */ +static void dimmer_timer_task( + struct bacnet_lighting_command_data *data, uint16_t milliseconds) +{ + Tracking_Elapsed_Milliseconds = milliseconds; + switch (data->Lighting_Operation) { + case BACNET_LIGHTS_PROPRIETARY_MIN: + break; + case BACNET_LIGHTS_PROPRIETARY_MAX: + break; + default: + break; + } +} + /** * @brief compare two floating point values to 3 decimal places * @@ -127,20 +143,56 @@ static void test_lighting_command_command_unit(void) #endif { BACNET_LIGHTING_COMMAND_DATA data = { 0 }; + struct lighting_command_notification observer1 = { 0 }; + struct lighting_command_notification observer2 = { 0 }; + struct lighting_command_timer_notification timer_observer1 = { 0 }; + struct lighting_command_timer_notification timer_observer2 = { 0 }; uint16_t milliseconds = 10; uint32_t fade_time = 1000; float target_level = 100.0f; float target_step = 1.0f; + float ramp_rate = 1000.0f; + float override_level; lighting_command_init(&data); - data.Notification_Head.callback = dimmer_tracking_value; + /* lighting command subscribe */ + observer1.callback = dimmer_tracking_value; + lighting_command_notification_add(&data, &observer1); + /* add again to verify skipping */ + lighting_command_notification_add(&data, &observer1); + /* add second tracker */ + lighting_command_notification_add(&data, &observer2); + + /* lighting command timer subscribe */ + timer_observer1.callback = dimmer_timer_task; + lighting_command_timer_notfication_add(&data, &timer_observer1); + /* add again to verify skipping */ + lighting_command_timer_notfication_add(&data, &timer_observer1); + /* add second tracker */ + lighting_command_timer_notfication_add(&data, &timer_observer2); + + /* basic STOP and NONE states */ lighting_command_stop(&data); lighting_command_timer(&data, milliseconds); zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); lighting_command_none(&data); lighting_command_timer(&data, milliseconds); zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + + /* normalized range clamp testing */ + data.Max_Actual_Value = 95.0f; + data.Min_Actual_Value = 5.0f; + target_level = lighting_command_normalized_range_clamp(&data, 0.1f); + zassert_true(is_float_equal(target_level, 0.0f), NULL); + target_level = lighting_command_normalized_range_clamp(&data, 100.0f); + zassert_true(is_float_equal(target_level, data.Max_Actual_Value), NULL); + target_level = lighting_command_normalized_range_clamp(&data, 1.0f); + zassert_true(is_float_equal(target_level, data.Min_Actual_Value), NULL); + data.Max_Actual_Value = 100.0f; + data.Min_Actual_Value = 1.0f; + /* fade up */ + target_level = 100.0f; lighting_command_fade_to(&data, 100.0f, fade_time); milliseconds = fade_time / 2; lighting_command_timer(&data, milliseconds); @@ -169,7 +221,7 @@ static void test_lighting_command_command_unit(void) milliseconds = 10; lighting_command_fade_to(&data, target_level, 0); lighting_command_timer(&data, milliseconds); - zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); zassert_true(is_float_equal(Tracking_Value, data.Low_Trim_Value), NULL); target_level = 0.0f; milliseconds = 10; @@ -184,9 +236,52 @@ static void test_lighting_command_command_unit(void) milliseconds = 10; lighting_command_fade_to(&data, target_level, 0); lighting_command_timer(&data, milliseconds); - zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); zassert_true(is_float_equal(Tracking_Value, data.High_Trim_Value), NULL); data.High_Trim_Value = data.Max_Actual_Value; + /* override */ + override_level = 42.0f; + target_level = 100.0f; + milliseconds = 10; + data.Overridden = true; + data.Overridden_Momentary = false; + lighting_command_override(&data, override_level); + lighting_command_timer(&data, milliseconds); + zassert_true(is_float_equal(Tracking_Value, override_level), NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(is_float_equal(Tracking_Value, override_level), NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + data.Overridden = false; + lighting_command_override(&data, target_level); + lighting_command_timer(&data, milliseconds); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + /* momentary override - self clearing flags */ + override_level = 42.0f; + target_level = 100.0f; + milliseconds = 10; + data.Overridden = true; + data.Overridden_Momentary = true; + lighting_command_override(&data, override_level); + lighting_command_timer(&data, milliseconds); + zassert_true(is_float_equal(Tracking_Value, override_level), NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_false(data.Overridden, NULL); + zassert_true(data.Overridden_Momentary, NULL); + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_false(data.Overridden, NULL); + zassert_false(data.Overridden_Momentary, NULL); + + /* step clamping */ + target_step = lighting_command_step_increment_clamp(0.0f); + zassert_true(is_float_equal(target_step, 0.1f), NULL); + target_step = lighting_command_step_increment_clamp(100.1f); + zassert_true(is_float_equal(target_step, 100.0f), NULL); /* step UP - inhibit ON */ target_step = 1.0f; @@ -303,7 +398,8 @@ static void test_lighting_command_command_unit(void) zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); zassert_true(is_float_equal(Tracking_Value, target_level), NULL); target_level = 100.0f; - lighting_command_ramp_to(&data, target_level, 100.0f); + ramp_rate = 100.0f; + lighting_command_ramp_to(&data, target_level, ramp_rate); lighting_command_timer(&data, milliseconds); zassert_true( data.In_Progress == BACNET_LIGHTING_RAMP_ACTIVE, "In_Progress=%d", @@ -316,8 +412,9 @@ static void test_lighting_command_command_unit(void) /* slower ramp up */ target_level = 100.0f; milliseconds = 100; + ramp_rate = 1.0f; do { - lighting_command_ramp_to(&data, target_level, 1.0f); + lighting_command_ramp_to(&data, target_level, ramp_rate); lighting_command_timer(&data, milliseconds); if (data.Lighting_Operation == BACNET_LIGHTS_RAMP_TO) { zassert_true( @@ -335,8 +432,28 @@ static void test_lighting_command_command_unit(void) /* slower ramp down */ target_level = data.Min_Actual_Value; milliseconds = 33; + ramp_rate = 0.1f; do { - lighting_command_ramp_to(&data, target_level, 0.1f); + lighting_command_ramp_to(&data, target_level, ramp_rate); + lighting_command_timer(&data, milliseconds); + if (data.Lighting_Operation == BACNET_LIGHTS_RAMP_TO) { + zassert_true( + data.In_Progress == BACNET_LIGHTING_RAMP_ACTIVE, + "In_Progress=%d", data.In_Progress); + zassert_true( + isgreater(data.Tracking_Value, data.Min_Actual_Value), + "Tracking_Value=%f", Tracking_Value); + zassert_true( + isless(data.Tracking_Value, data.Max_Actual_Value), + "Tracking_Value=%f", Tracking_Value); + } + } while (data.Lighting_Operation != BACNET_LIGHTS_STOP); + /* large elapsed timer - ramp up */ + target_level = data.Max_Actual_Value; + milliseconds = 2000; + ramp_rate = 0.1f; + do { + lighting_command_ramp_to(&data, target_level, ramp_rate); lighting_command_timer(&data, milliseconds); if (data.Lighting_Operation == BACNET_LIGHTS_RAMP_TO) { zassert_true( @@ -351,13 +468,88 @@ static void test_lighting_command_command_unit(void) } } while (data.Lighting_Operation != BACNET_LIGHTS_STOP); + /* out-of-service */ + target_level = 100.0f; + milliseconds = 10; + data.Out_Of_Service = false; + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + data.Out_Of_Service = true; + lighting_command_fade_to(&data, 0.0f, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + /* previous target level - unchanged */ + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + target_level = 0.0f; + data.Out_Of_Service = false; + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + + /* restore-on, toggle-restore */ + target_level = 0.0f; + milliseconds = 10; + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + lighting_command_restore_on(&data, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Last_On_Value), NULL); + lighting_command_toggle_restore(&data, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL); + lighting_command_toggle_restore(&data, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Last_On_Value), NULL); + + /* default-on, toggle-default */ + target_level = 0.0f; + milliseconds = 10; + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + lighting_command_default_on(&data, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Default_On_Value), NULL); + lighting_command_toggle_default(&data, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL); + lighting_command_toggle_default(&data, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Default_On_Value), NULL); + + /* non-standard lighting operation */ + milliseconds = 10; + data.Lighting_Operation = BACNET_LIGHTS_PROPRIETARY_MIN; + lighting_command_timer(&data, milliseconds); + zassert_equal(data.Lighting_Operation, BACNET_LIGHTS_PROPRIETARY_MIN, NULL); + data.Lighting_Operation = BACNET_LIGHTS_PROPRIETARY_MAX; + lighting_command_timer(&data, milliseconds); + zassert_equal(data.Lighting_Operation, BACNET_LIGHTS_PROPRIETARY_MAX, NULL); + /* null check code coverage */ + lighting_command_override(NULL, override_level); lighting_command_fade_to(NULL, 0.0f, 0); lighting_command_ramp_to(NULL, 0.0f, 0.0f); lighting_command_step(NULL, BACNET_LIGHTS_STEP_OFF, 0.0f); lighting_command_blink_warn(NULL, BACNET_LIGHTS_WARN, NULL); lighting_command_stop(NULL); lighting_command_none(NULL); + lighting_command_restore_on(NULL, 0); + lighting_command_default_on(NULL, 0); + lighting_command_toggle_restore(NULL, 0); + lighting_command_toggle_default(NULL, 0); lighting_command_timer(NULL, 0); lighting_command_init(NULL); }