From 51c915a5da3bc31fea8abe964e479b9e77f1ba19 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Wed, 13 Nov 2024 15:24:25 -0600 Subject: [PATCH] Bugfix/lighting output dimming refactor (#855) * Refactored lighting command operations from the lighting output object, and added unit testing. Integrated the result back into lighting-output object. --- CMakeLists.txt | 2 + apps/blinkt/main.c | 7 + .../bacnet-stack/bacnet-stack.vcxproj | 1 + .../bacnet-stack/bacnet-stack.vcxproj.filters | 3 + src/bacnet/basic/object/lo.c | 621 ++++------------- src/bacnet/basic/object/lo.h | 12 +- src/bacnet/basic/sys/lighting_command.c | 644 ++++++++++++++++++ src/bacnet/basic/sys/lighting_command.h | 90 +++ test/CMakeLists.txt | 1 + test/bacnet/basic/object/blo/CMakeLists.txt | 2 + .../basic/object/color_object/CMakeLists.txt | 4 +- .../object/color_temperature/CMakeLists.txt | 4 +- .../bacnet/basic/object/device/CMakeLists.txt | 3 +- test/bacnet/basic/object/lo/CMakeLists.txt | 1 + .../basic/sys/lighting_command/CMakeLists.txt | 46 ++ .../basic/sys/lighting_command/src/main.c | 380 +++++++++++ 16 files changed, 1330 insertions(+), 491 deletions(-) create mode 100644 src/bacnet/basic/sys/lighting_command.c create mode 100644 src/bacnet/basic/sys/lighting_command.h create mode 100644 test/bacnet/basic/sys/lighting_command/CMakeLists.txt create mode 100644 test/bacnet/basic/sys/lighting_command/src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bce3b2a..8cb65fab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -536,6 +536,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/sys/keylist.h src/bacnet/basic/sys/linear.c src/bacnet/basic/sys/linear.h + src/bacnet/basic/sys/lighting_command.c + src/bacnet/basic/sys/lighting_command.h src/bacnet/basic/sys/mstimer.c src/bacnet/basic/sys/mstimer.h src/bacnet/basic/sys/ringbuf.c diff --git a/apps/blinkt/main.c b/apps/blinkt/main.c index 50a206e9..3c1d3ecd 100644 --- a/apps/blinkt/main.c +++ b/apps/blinkt/main.c @@ -225,10 +225,17 @@ static void bacnet_output_init(void) Channel_Create(light_channel_instance); Channel_Name_Set(light_channel_instance, "Lights"); + Channel_Number_Set(light_channel_instance, 1); + Channel_Control_Groups_Element_Set(light_channel_instance, 1, 1); Channel_Create(color_channel_instance); Channel_Name_Set(color_channel_instance, "Colors"); + Channel_Number_Set(color_channel_instance, 2); + Channel_Control_Groups_Element_Set(color_channel_instance, 1, 2); Channel_Create(temp_channel_instance); Channel_Name_Set(temp_channel_instance, "Color-Temperatures"); + Channel_Number_Set(temp_channel_instance, 3); + Channel_Control_Groups_Element_Set(temp_channel_instance, 1, 3); + /* configure outputs and bindings */ led_max = blinkt_led_count(); for (i = 0; i < led_max; i++) { /* color */ diff --git a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj index c11e60df..fce4b9b3 100644 --- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj +++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj @@ -148,6 +148,7 @@ + diff --git a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters index 4a05f105..cd9021bb 100644 --- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters +++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters @@ -588,6 +588,9 @@ Source Files\src\bacnet\basic\sys + + Source Files\src\bacnet\basic\sys + Source Files\ports\win32 diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c index 9a64469b..45da3cc0 100644 --- a/src/bacnet/basic/object/lo.c +++ b/src/bacnet/basic/object/lo.c @@ -23,6 +23,7 @@ #include "bacnet/basic/sys/keylist.h" #include "bacnet/basic/sys/linear.h" #include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/lighting_command.h" #include "bacnet/bactext.h" #include "bacnet/proplist.h" /* me! */ @@ -30,10 +31,9 @@ struct object_data { float Present_Value; - float Tracking_Value; + BACNET_LIGHTING_COMMAND_DATA Lighting_Command; + BACNET_LIGHTING_COMMAND Last_Lighting_Command; float Physical_Value; - BACNET_LIGHTING_COMMAND Lighting_Command; - BACNET_LIGHTING_IN_PROGRESS In_Progress; uint32_t Egress_Time; uint32_t Default_Fade_Time; float Default_Ramp_Rate; @@ -45,15 +45,12 @@ struct object_data { float Relinquish_Default; float Power; float Instantaneous_Power; - float Min_Actual_Value; - float Max_Actual_Value; uint8_t Lighting_Command_Default_Priority; BACNET_OBJECT_ID Color_Reference; BACNET_OBJECT_ID Override_Color_Reference; const char *Object_Name; const char *Description; /* bits */ - bool Out_Of_Service : 1; bool Blink_Warn_Enable : 1; bool Egress_Active : 1; bool Color_Override : 1; @@ -61,8 +58,8 @@ struct object_data { /* Key List for storing the object data sorted by instance number */ static OS_Keylist Object_List; /* callback for present value writes */ -static lighting_output_write_present_value_callback - Lighting_Output_Write_Present_Value_Callback; +static lighting_command_tracking_value_callback + Lighting_Command_Tracking_Value_Callback; /* These arrays are used by the ReadPropertyMultiple handler and property-list property (as of protocol-revision 14) */ @@ -455,7 +452,9 @@ Lighting_Command_Warn(struct object_data *pObject, unsigned priority) active priority, or (b) The value at the specified priority is 0.0%, or (c) Blink_Warn_Enable is FALSE. */ - pObject->Lighting_Command.operation = BACNET_LIGHTS_WARN; + lighting_command_blink_warn( + &pObject->Lighting_Command, BACNET_LIGHTS_WARN, + &pObject->Lighting_Command.Blink); } } @@ -485,7 +484,9 @@ Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority) active priority, or (b) The Present_Value is 0.0%, or (c) Blink_Warn_Enable is FALSE. */ - pObject->Lighting_Command.operation = BACNET_LIGHTS_WARN_OFF; + lighting_command_blink_warn( + &pObject->Lighting_Command, BACNET_LIGHTS_WARN_OFF, + &pObject->Lighting_Command.Blink); } else { Present_Value_Set(pObject, 0.0, priority); } @@ -521,7 +522,9 @@ Lighting_Command_Warn_Relinquish(struct object_data *pObject, unsigned priority) priority, including Relinquish_Default, is greater than 0.0%, or (d) Blink_Warn_Enable is FALSE. */ - pObject->Lighting_Command.operation = BACNET_LIGHTS_WARN_RELINQUISH; + lighting_command_blink_warn( + &pObject->Lighting_Command, BACNET_LIGHTS_WARN_RELINQUISH, + &pObject->Lighting_Command.Blink); } else { Present_Value_Relinquish(pObject, priority); } @@ -549,9 +552,7 @@ static void Lighting_Command_Fade_To( current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { /* we have priority - configure the Lighting Command */ - pObject->Lighting_Command.fade_time = fade_time; - pObject->Lighting_Command.operation = BACNET_LIGHTS_FADE_TO; - pObject->Lighting_Command.target_level = value; + lighting_command_fade_to(&pObject->Lighting_Command, value, fade_time); } } @@ -577,9 +578,7 @@ static void Lighting_Command_Ramp_To( current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { /* we have priority - configure the Lighting Command */ - pObject->Lighting_Command.ramp_rate = ramp_rate; - pObject->Lighting_Command.operation = BACNET_LIGHTS_RAMP_TO; - pObject->Lighting_Command.target_level = value; + lighting_command_ramp_to(&pObject->Lighting_Command, value, ramp_rate); } } @@ -604,10 +603,8 @@ static void Lighting_Command_Step( current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { /* we have priority - configure the Lighting Command */ - pObject->Lighting_Command.operation = operation; - pObject->Lighting_Command.fade_time = 0; - pObject->Lighting_Command.ramp_rate = 0.0f; - pObject->Lighting_Command.step_increment = step_increment; + lighting_command_step( + &pObject->Lighting_Command, operation, step_increment); } } @@ -669,23 +666,18 @@ static bool Lighting_Output_Present_Value_Write( /* we have priority - configure the Lighting Command */ if (pObject->Transition == BACNET_LIGHTING_TRANSITION_FADE) { - pObject->Lighting_Command.fade_time = - pObject->Default_Fade_Time; - pObject->Lighting_Command.operation = - BACNET_LIGHTS_FADE_TO; + Lighting_Command_Fade_To( + pObject, priority, value, + pObject->Default_Fade_Time); } else if ( pObject->Transition == BACNET_LIGHTING_TRANSITION_RAMP) { - pObject->Lighting_Command.ramp_rate = - pObject->Default_Ramp_Rate; - pObject->Lighting_Command.operation = - BACNET_LIGHTS_RAMP_TO; + Lighting_Command_Ramp_To( + pObject, priority, value, + pObject->Default_Ramp_Rate); } else { - pObject->Lighting_Command.fade_time = 0; - pObject->Lighting_Command.operation = - BACNET_LIGHTS_FADE_TO; + Lighting_Command_Fade_To(pObject, priority, value, 0); } - pObject->Lighting_Command.target_level = value; } status = true; } else { @@ -773,19 +765,17 @@ static bool Lighting_Output_Present_Value_Relinquish_Write( } /* we have priority - configure the Lighting Command */ if (pObject->Transition == BACNET_LIGHTING_TRANSITION_FADE) { - pObject->Lighting_Command.fade_time = - pObject->Default_Fade_Time; - pObject->Lighting_Command.operation = BACNET_LIGHTS_FADE_TO; + Lighting_Command_Fade_To( + pObject, new_priority, value, + pObject->Default_Fade_Time); } else if ( pObject->Transition == BACNET_LIGHTING_TRANSITION_RAMP) { - pObject->Lighting_Command.ramp_rate = - pObject->Default_Ramp_Rate; - pObject->Lighting_Command.operation = BACNET_LIGHTS_RAMP_TO; + Lighting_Command_Ramp_To( + pObject, new_priority, value, + pObject->Default_Ramp_Rate); } else { - pObject->Lighting_Command.fade_time = 0; - pObject->Lighting_Command.operation = BACNET_LIGHTS_FADE_TO; + Lighting_Command_Fade_To(pObject, new_priority, value, 0); } - pObject->Lighting_Command.target_level = value; } status = true; } else { @@ -920,28 +910,6 @@ bool Lighting_Output_Description_Set( 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) { - status = lighting_command_copy(&pObject->Lighting_Command, value); - } - - return status; -} - /** * @brief Set the lighting command if the priority is active * @param object [in] BACnet object instance @@ -958,7 +926,7 @@ Lighting_Command_Stop(struct object_data *pObject, unsigned priority) current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { /* we have priority - configure the Lighting Command */ - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + lighting_command_stop(&pObject->Lighting_Command); } } @@ -1085,6 +1053,9 @@ static bool Lighting_Output_Lighting_Command_Write( /** * 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 * @@ -1098,7 +1069,81 @@ bool Lighting_Output_Lighting_Command( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - status = lighting_command_copy(value, &pObject->Lighting_Command); + 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; + } + if (status) { + lighting_command_copy(&pObject->Last_Lighting_Command, value); + } } return status; @@ -1119,7 +1164,7 @@ Lighting_Output_In_Progress(uint32_t object_instance) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - value = pObject->In_Progress; + value = pObject->Lighting_Command.In_Progress; } return value; @@ -1142,7 +1187,7 @@ bool Lighting_Output_In_Progress_Set( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - pObject->In_Progress = in_progress; + pObject->Lighting_Command.In_Progress = in_progress; } return status; @@ -1162,7 +1207,7 @@ float Lighting_Output_Tracking_Value(uint32_t object_instance) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - value = pObject->Tracking_Value; + value = pObject->Lighting_Command.Tracking_Value; } return value; @@ -1184,7 +1229,7 @@ bool Lighting_Output_Tracking_Value_Set(uint32_t object_instance, float value) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - pObject->Tracking_Value = value; + pObject->Lighting_Command.Tracking_Value = value; status = true; } @@ -1627,7 +1672,7 @@ bool Lighting_Output_Out_Of_Service(uint32_t object_instance) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - value = pObject->Out_Of_Service; + value = pObject->Lighting_Command.Out_Of_Service; } return value; @@ -1647,7 +1692,7 @@ void Lighting_Output_Out_Of_Service_Set(uint32_t object_instance, bool value) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - pObject->Out_Of_Service = value; + pObject->Lighting_Command.Out_Of_Service = value; } } @@ -2276,366 +2321,6 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) return status; } -/** - * Handles the timing for a single Lighting Output object Fade - * - * @param object_instance - Lighting Output object - * @param milliseconds - number of milliseconds elapsed since previously - * called. Works best when called about every 10 milliseconds. - */ -static void -Lighting_Output_Fade_Handler(uint32_t object_instance, uint16_t milliseconds) -{ - struct object_data *pObject; - float old_value; - float x1, x2, x3, y1, y3; - - pObject = Keylist_Data(Object_List, object_instance); - if (!pObject) { - return; - } - old_value = pObject->Tracking_Value; - if (milliseconds >= pObject->Lighting_Command.fade_time) { - /* stop fading */ - pObject->Tracking_Value = pObject->Lighting_Command.target_level; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - pObject->Lighting_Command.fade_time = 0; - } else { - if (!islessgreater( - pObject->Tracking_Value, - pObject->Lighting_Command.target_level)) { - /* stop fading */ - pObject->Tracking_Value = pObject->Lighting_Command.target_level; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - pObject->Lighting_Command.fade_time = 0; - } else { - /* fading */ - x1 = 0.0f; - x2 = (float)milliseconds; - x3 = (float)pObject->Lighting_Command.fade_time; - y1 = old_value; - y3 = pObject->Lighting_Command.target_level; - pObject->Tracking_Value = linear_interpolate(x1, x2, x3, y1, y3); - pObject->Lighting_Command.fade_time -= milliseconds; - pObject->In_Progress = BACNET_LIGHTING_FADE_ACTIVE; - } - } - if (Lighting_Output_Write_Present_Value_Callback) { - Lighting_Output_Write_Present_Value_Callback( - object_instance, old_value, pObject->Tracking_Value); - } else { - debug_printf( - "LO[%u] Fade Handler Operation=%s Value=%f\n", object_instance, - bactext_lighting_operation_name( - pObject->Lighting_Command.operation), - (double)pObject->Tracking_Value); - } -} - -/** - * Updates the object tracking value while ramping - * - * Commands Present_Value to ramp from the current Tracking_Value to the - * target-level specified in the command. The ramp operation - * changes the output from its current value to target-level, - * at a particular percent per second defined by ramp-rate. - * While the ramp operation is executing, In_Progress shall be set - * to RAMP_ACTIVE, and Tracking_Value shall be updated to reflect the current - * progress of the ramp. shall be clamped to - * Min_Actual_Value and Max_Actual_Value. - * - * @param object_instance - object-instance number of the object - * @param milliseconds - number of milliseconds elapsed - */ -static void -Lighting_Output_Ramp_Handler(uint32_t object_instance, uint16_t milliseconds) -{ - float old_value, target_value, min_value, max_value, step_value, steps; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (!pObject) { - return; - } - old_value = pObject->Tracking_Value; - min_value = pObject->Min_Actual_Value; - max_value = pObject->Max_Actual_Value; - target_value = pObject->Lighting_Command.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; - } - /* determine the number of steps */ - if (milliseconds <= 1000) { - /* percent per second */ - steps = linear_interpolate( - 0.0f, (float)milliseconds, 1000.0f, 0.0f, - pObject->Lighting_Command.ramp_rate); - } else { - steps = ((float)milliseconds * pObject->Lighting_Command.ramp_rate) / - 1000.0f; - } - if (!islessgreater(pObject->Tracking_Value, target_value)) { - /* stop ramping */ - pObject->Tracking_Value = target_value; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - } else { - if (isless(old_value, target_value)) { - step_value = old_value + steps; - if (isgreater(step_value, target_value)) { - /* stop ramping */ - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - } - } else if (isgreater(old_value, target_value)) { - debug_printf( - "LO[%u] Ramp Handler Down steps=%f tracking=%f\n", - object_instance, (double)steps, (double)old_value); - if (isgreater(old_value, steps)) { - step_value = old_value - steps; - } else { - step_value = target_value; - } - if (isless(step_value, target_value)) { - /* stop ramping */ - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - } - } else { - debug_printf( - "LO[%u] Ramp Handler at target=%f tracking=%f\n", - object_instance, (double)target_value, (double)old_value); - /* stop ramping */ - step_value = target_value; - pObject->Lighting_Command.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; - } - pObject->Tracking_Value = step_value; - if (pObject->Lighting_Command.operation == BACNET_LIGHTS_STOP) { - pObject->In_Progress = BACNET_LIGHTING_IDLE; - } else { - pObject->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE; - } - } - if (Lighting_Output_Write_Present_Value_Callback) { - Lighting_Output_Write_Present_Value_Callback( - object_instance, old_value, pObject->Tracking_Value); - } else { - debug_printf( - "LO[%u] Ramp Handler Operation=%s Value=%f\n", object_instance, - bactext_lighting_operation_name( - pObject->Lighting_Command.operation), - (double)pObject->Tracking_Value); - } -} - -/** - * Updates the object tracking value while stepping - * - * Commands Present_Value to a value equal to the Tracking_Value - * plus the step-increment. The resulting sum shall be clamped to - * Min_Actual_Value and Max_Actual_Value - * - * @param object_instance - object-instance number of the object - */ -static void Lighting_Output_Step_Up_Handler(uint32_t object_instance) -{ - float old_value, target_value, min_value, max_value, step_value; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (!pObject) { - return; - } - old_value = pObject->Tracking_Value; - min_value = pObject->Min_Actual_Value; - max_value = pObject->Max_Actual_Value; - step_value = pObject->Lighting_Command.step_increment; - /* inhibit ON if the value is already OFF */ - if (isgreaterequal(old_value, min_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; - } - pObject->Present_Value = target_value; - pObject->Tracking_Value = target_value; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - if (Lighting_Output_Write_Present_Value_Callback) { - Lighting_Output_Write_Present_Value_Callback( - object_instance, old_value, pObject->Tracking_Value); - } else { - debug_printf( - "LO[%u] Step Up Handler Operation=%s Value=%f\n", - object_instance, - bactext_lighting_operation_name( - pObject->Lighting_Command.operation), - (double)pObject->Tracking_Value); - } - } -} - -/** - * Updates the object tracking value while stepping - * - * Commands Present_Value to a value equal to the Tracking_Value - * plus the step-increment. The resulting sum shall be clamped to - * Min_Actual_Value and Max_Actual_Value - * - * @param object_instance - object-instance number of the object - */ -static void Lighting_Output_Step_Down_Handler(uint32_t object_instance) -{ - float old_value, target_value, min_value, max_value, step_value; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (!pObject) { - return; - } - old_value = target_value = pObject->Tracking_Value; - min_value = pObject->Min_Actual_Value; - max_value = pObject->Max_Actual_Value; - step_value = pObject->Lighting_Command.step_increment; - if (isgreaterequal(target_value, step_value)) { - target_value -= step_value; - } else { - target_value = 0.0; - } - /* 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; - } - pObject->Present_Value = target_value; - pObject->Tracking_Value = target_value; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - if (Lighting_Output_Write_Present_Value_Callback) { - Lighting_Output_Write_Present_Value_Callback( - object_instance, old_value, pObject->Tracking_Value); - } else { - debug_printf( - "LO[%u] Step Down Handler Operation=%s Value=%f\n", object_instance, - bactext_lighting_operation_name( - pObject->Lighting_Command.operation), - (double)pObject->Tracking_Value); - } -} - -/** - * Updates the object tracking value while stepping - * - * Commands Present_Value to a value equal to the Tracking_Value - * plus the step-increment. The resulting sum shall be clamped to - * Min_Actual_Value and Max_Actual_Value - * - * @param object_instance - object-instance number of the object - */ -static void Lighting_Output_Step_On_Handler(uint32_t object_instance) -{ - float old_value, target_value, min_value, max_value, step_value; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (!pObject) { - return; - } - old_value = target_value = pObject->Tracking_Value; - min_value = pObject->Min_Actual_Value; - max_value = pObject->Max_Actual_Value; - step_value = pObject->Lighting_Command.step_increment; - target_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; - } - pObject->Present_Value = target_value; - pObject->Tracking_Value = target_value; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - if (Lighting_Output_Write_Present_Value_Callback) { - Lighting_Output_Write_Present_Value_Callback( - object_instance, old_value, pObject->Tracking_Value); - } else { - debug_printf( - "LO[%u] Step On Handler Operation=%s Value=%f\n", object_instance, - bactext_lighting_operation_name( - pObject->Lighting_Command.operation), - (double)pObject->Tracking_Value); - } -} - -/** - * Updates the object tracking value while stepping - * - * Commands Present_Value to a value equal to the Tracking_Value - * plus the step-increment. The resulting sum shall be clamped to - * Min_Actual_Value and Max_Actual_Value - * - * @param object_instance - object-instance number of the object - */ -static void Lighting_Output_Step_Off_Handler(uint32_t object_instance) -{ - float old_value, target_value, min_value, max_value, step_value; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (!pObject) { - return; - } - old_value = target_value = pObject->Tracking_Value; - min_value = pObject->Min_Actual_Value; - max_value = pObject->Max_Actual_Value; - step_value = pObject->Lighting_Command.step_increment; - if (isgreaterequal(target_value, step_value)) { - target_value -= step_value; - } else { - target_value = 0.0f; - } - /* clamp target within max */ - if (isgreater(target_value, max_value)) { - target_value = max_value; - } - /* jump target to OFF if below min */ - if (isless(target_value, min_value)) { - target_value = 0.0f; - } - pObject->Present_Value = target_value; - pObject->Tracking_Value = target_value; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; - if (Lighting_Output_Write_Present_Value_Callback) { - Lighting_Output_Write_Present_Value_Callback( - object_instance, old_value, pObject->Tracking_Value); - } else { - debug_printf( - "LO[%u] Step Off Handler Operation=%s Value=%f\n", object_instance, - bactext_lighting_operation_name( - pObject->Lighting_Command.operation), - (double)pObject->Tracking_Value); - } -} - /** * @brief Updates the lighting object tracking value per ramp or fade or step * @param object_instance - object-instance number of the object @@ -2648,39 +2333,20 @@ void Lighting_Output_Timer(uint32_t object_instance, uint16_t milliseconds) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - switch (pObject->Lighting_Command.operation) { - case BACNET_LIGHTS_NONE: - pObject->In_Progress = BACNET_LIGHTING_IDLE; - break; - case BACNET_LIGHTS_FADE_TO: - Lighting_Output_Fade_Handler(object_instance, milliseconds); - break; - case BACNET_LIGHTS_RAMP_TO: - Lighting_Output_Ramp_Handler(object_instance, milliseconds); - break; - case BACNET_LIGHTS_STEP_UP: - Lighting_Output_Step_Up_Handler(object_instance); - break; - case BACNET_LIGHTS_STEP_DOWN: - Lighting_Output_Step_Down_Handler(object_instance); - break; - case BACNET_LIGHTS_STEP_ON: - Lighting_Output_Step_On_Handler(object_instance); - break; - case BACNET_LIGHTS_STEP_OFF: - Lighting_Output_Step_Off_Handler(object_instance); - break; - case BACNET_LIGHTS_WARN: - break; - case BACNET_LIGHTS_WARN_OFF: - break; - case BACNET_LIGHTS_WARN_RELINQUISH: - break; - case BACNET_LIGHTS_STOP: - pObject->In_Progress = BACNET_LIGHTING_IDLE; - break; - default: - break; + lighting_command_timer(&pObject->Lighting_Command, milliseconds); + } +} + +static void Lighting_Output_Tracking_Value_Callback( + uint32_t object_instance, float old_value, float value) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (Lighting_Command_Tracking_Value_Callback) { + Lighting_Command_Tracking_Value_Callback( + object_instance, old_value, value); } } } @@ -2690,9 +2356,9 @@ void Lighting_Output_Timer(uint32_t object_instance, uint16_t milliseconds) * @param cb - callback used to provide indications */ void Lighting_Output_Write_Present_Value_Callback_Set( - lighting_output_write_present_value_callback cb) + lighting_command_tracking_value_callback cb) { - Lighting_Output_Write_Present_Value_Callback = cb; + Lighting_Command_Tracking_Value_Callback = cb; } /** @@ -2725,16 +2391,17 @@ uint32_t Lighting_Output_Create(uint32_t object_instance) pObject->Object_Name = NULL; pObject->Description = NULL; pObject->Present_Value = 0.0f; - pObject->Tracking_Value = 0.0f; pObject->Physical_Value = 0.0f; - pObject->Lighting_Command.operation = BACNET_LIGHTS_NONE; - pObject->Lighting_Command.use_target_level = false; - pObject->Lighting_Command.use_ramp_rate = false; - pObject->Lighting_Command.use_step_increment = false; - pObject->Lighting_Command.use_fade_time = false; - pObject->Lighting_Command.use_priority = false; - pObject->In_Progress = BACNET_LIGHTING_IDLE; - pObject->Out_Of_Service = false; + lighting_command_init(&pObject->Lighting_Command); + pObject->Lighting_Command.Key = object_instance; + pObject->Lighting_Command.Tracking_Value_Callback = + Lighting_Output_Tracking_Value_Callback; + pObject->Last_Lighting_Command.operation = BACNET_LIGHTS_NONE; + pObject->Last_Lighting_Command.use_target_level = false; + pObject->Last_Lighting_Command.use_ramp_rate = false; + pObject->Last_Lighting_Command.use_step_increment = false; + pObject->Last_Lighting_Command.use_fade_time = false; + pObject->Last_Lighting_Command.use_priority = false; pObject->Blink_Warn_Enable = false; pObject->Egress_Active = false; pObject->Egress_Time = 0; @@ -2750,8 +2417,6 @@ uint32_t Lighting_Output_Create(uint32_t object_instance) pObject->Relinquish_Default = 0.0f; pObject->Power = 0.0f; pObject->Instantaneous_Power = 0.0f; - pObject->Min_Actual_Value = 0.0f; - pObject->Max_Actual_Value = 100.0f; pObject->Lighting_Command_Default_Priority = 16; pObject->Color_Override = false; pObject->Color_Reference.type = OBJECT_COLOR; diff --git a/src/bacnet/basic/object/lo.h b/src/bacnet/basic/object/lo.h index 793b4fac..3192f345 100644 --- a/src/bacnet/basic/object/lo.h +++ b/src/bacnet/basic/object/lo.h @@ -15,15 +15,7 @@ #include "bacnet/bacerror.h" #include "bacnet/rp.h" #include "bacnet/wp.h" - -/** - * @brief Callback for write present value request - * @param object_instance - object-instance number of the object - * @param old_value - value prior to write - * @param value - value of the write - */ -typedef void (*lighting_output_write_present_value_callback)( - uint32_t object_instance, float old_value, float value); +#include "bacnet/basic/sys/lighting_command.h" #ifdef __cplusplus extern "C" { @@ -177,7 +169,7 @@ void Lighting_Output_Timer(uint32_t object_instance, uint16_t milliseconds); BACNET_STACK_EXPORT void Lighting_Output_Write_Present_Value_Callback_Set( - lighting_output_write_present_value_callback cb); + lighting_command_tracking_value_callback cb); BACNET_STACK_EXPORT uint32_t Lighting_Output_Create(uint32_t object_instance); diff --git a/src/bacnet/basic/sys/lighting_command.c b/src/bacnet/basic/sys/lighting_command.c new file mode 100644 index 00000000..92869410 --- /dev/null +++ b/src/bacnet/basic/sys/lighting_command.c @@ -0,0 +1,644 @@ +/** + * @file + * @brief dimming brightness engine based on lighting commands + * @author Steve Karg + * @date 2022 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include +#include +#include +#include "linear.h" +#include "debug.h" +/* me! */ +#include "lighting_command.h" + +/** + * @brief Callback for tracking value updates + * @param data - dimmer data + * @param old_value - value prior to write + * @param value - value of the write + */ +static void lighting_command_tracking_value_notify( + struct bacnet_lighting_command_data *data, float old_value, float value) +{ + if (data->Tracking_Value_Callback) { + 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; + } + data->Tracking_Value_Callback(data->Key, old_value, value); + } else { + debug_printf("Lighting-Command[%lu]-Out-of-Service\n", data->Key); + } + } else { + debug_printf( + "Lighting-Command[%lu]-Tracking-Value=%f\n", data->Key, value); + } +} + +/** + * Handles the timing for a single Lighting Output object Fade + * + * @param data [in] dimmer data + * @param milliseconds - number of milliseconds elapsed since previously + * called. Works best when called about every 10 milliseconds. + */ +static void lighting_command_fade_handler( + struct bacnet_lighting_command_data *data, uint16_t milliseconds) +{ + float old_value; + float x1, x2, x3, y1, y3; + float target_value, min_value, max_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; + } + if ((milliseconds >= data->Fade_Time) || + (!islessgreater(data->Tracking_Value, target_value))) { + /* stop fading */ + if (isless(data->Target_Level, 1.0f)) { + /* jump target to OFF if below normalized min */ + data->Tracking_Value = 0.0f; + } else { + data->Tracking_Value = target_value; + } + data->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + data->Fade_Time = 0; + } else { + /* fading */ + x1 = 0.0f; + x2 = (float)milliseconds; + x3 = (float)data->Fade_Time; + if (isless(old_value, min_value)) { + y1 = min_value; + } else { + y1 = old_value; + } + y3 = target_value; + data->Tracking_Value = linear_interpolate(x1, x2, x3, y1, y3); + data->Fade_Time -= milliseconds; + data->In_Progress = BACNET_LIGHTING_FADE_ACTIVE; + } + lighting_command_tracking_value_notify( + data, old_value, data->Tracking_Value); +} + +/** + * Updates the object tracking value while ramping + * + * Commands the dimmer to ramp from the current Tracking_Value to the + * target-level specified in the command. The ramp operation + * changes the output from its current value to target-level, + * at a particular percent per second defined by ramp-rate. + * While the ramp operation is executing, In_Progress shall be set + * to RAMP_ACTIVE, and Tracking_Value shall be updated to reflect the current + * progress of the ramp. shall be clamped to + * Min_Actual_Value and Max_Actual_Value. + * + * @param data [in] dimmer data + * @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; + + 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; + } + /* determine the number of steps */ + if (milliseconds <= 1000) { + /* percent per second */ + steps = linear_interpolate( + 0.0f, (float)milliseconds, 1000.0f, 0.0f, data->Ramp_Rate); + } else { + steps = ((float)milliseconds * data->Ramp_Rate) / 1000.0f; + } + if (!islessgreater(data->Tracking_Value, target_value)) { + /* stop ramping */ + if (isless(data->Target_Level, 1.0f)) { + /* jump target to OFF if below normalized min */ + data->Tracking_Value = 0.0f; + } else { + data->Tracking_Value = target_value; + } + data->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + } else { + if (isless(old_value, target_value)) { + step_value = old_value + steps; + if (isgreater(step_value, target_value)) { + /* stop ramping */ + data->Lighting_Operation = BACNET_LIGHTS_STOP; + } + } else if (isgreater(old_value, target_value)) { + if (isgreater(old_value, steps)) { + step_value = old_value - steps; + } else { + step_value = target_value; + } + if (isless(step_value, target_value)) { + /* stop ramping */ + data->Lighting_Operation = BACNET_LIGHTS_STOP; + } + } else { + /* stop ramping */ + step_value = target_value; + 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; + } + if (data->Lighting_Operation == BACNET_LIGHTS_STOP) { + if (isless(data->Target_Level, 1.0f)) { + /* jump target to OFF if below normalized min */ + data->Tracking_Value = 0.0f; + } else { + data->Tracking_Value = step_value; + } + data->In_Progress = BACNET_LIGHTING_IDLE; + } else { + data->Tracking_Value = step_value; + data->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE; + } + } + lighting_command_tracking_value_notify( + 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 + * + * Commands the dimmer to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param data [in] dimmer data + */ +static void +lighting_command_step_up_handler(struct bacnet_lighting_command_data *data) +{ + float old_value, target_value, min_value, max_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)) { + 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->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + lighting_command_tracking_value_notify( + 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 + * + * Commands the dimmer to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param data [in] dimmer data + */ +static void +lighting_command_step_down_handler(struct bacnet_lighting_command_data *data) +{ + float old_value, target_value, min_value, max_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; + } + /* 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->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + lighting_command_tracking_value_notify( + data, old_value, data->Tracking_Value); +} + +/** + * Updates the object tracking value while stepping + * + * Commands the dimmer to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param data [in] dimmer 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; + + 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); + data->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + lighting_command_tracking_value_notify( + data, old_value, data->Tracking_Value); +} + +/** + * Updates the object tracking value while stepping + * + * Commands the dimmer to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param data [in] dimmer 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; + + 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; + } + data->Tracking_Value = + lighting_command_normalize_value(target_value, min_value, max_value); + data->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + lighting_command_tracking_value_notify( + data, old_value, data->Tracking_Value); +} + +/** + * Updates the object tracking value while blinking + * + * @note When the value of In_Progress is NOT_CONTROLLED or OTHER, + * the value of Tracking_Value shall be a local matter. + * + * @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. + * + * 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. + * + * @param data [in] dimmer data + * @param milliseconds - number of milliseconds elapsed + */ +static void lighting_command_blink_handler( + struct bacnet_lighting_command_data *data, uint16_t milliseconds) +{ + float old_value, target_value, min_value, max_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; + } else { + data->Blink.Duration = 0; + } + if (data->Blink.Duration == 0) { + /* 'end' operation */ + data->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + target_value = data->Blink.End_Value; + } else if (data->Blink.Target_Interval == 0) { + /* only 'on' level */ + target_value = data->Blink.On_Value; + } else { + /* 'blink' operation */ + if (data->Blink.State) { + target_value = data->Blink.On_Value; + } else { + target_value = data->Blink.Off_Value; + } + /* detect next interval */ + if (data->Blink.Interval > milliseconds) { + data->Blink.Interval -= milliseconds; + } else { + data->Blink.Interval = 0; + } + if (data->Blink.Interval == 0) { + /* next blink */ + data->Blink.Interval = data->Blink.Target_Interval; + data->Blink.State = !data->Blink.State; + if (data->Blink.State) { + /* end of 'off' operation when counting */ + if ((data->Blink.Count > 0) && + (data->Blink.Count != UINT16_MAX)) { + data->Blink.Count--; + } + if (data->Blink.Count == 0) { + /* 'end' operation */ + data->In_Progress = BACNET_LIGHTING_IDLE; + data->Lighting_Operation = BACNET_LIGHTS_STOP; + target_value = data->Blink.End_Value; + } + } + } + } + target_value = + lighting_command_normalize_value(target_value, min_value, max_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); +} + +/** + * @brief Updates the dimmer tracking value per ramp or fade or step + * @param data [in] dimmer data + * @param milliseconds - number of milliseconds elapsed since previously + * called. Suggest that this is called every 10 milliseconds. + */ +void lighting_command_timer( + struct bacnet_lighting_command_data *data, uint16_t milliseconds) +{ + if (!data) { + return; + } + switch (data->Lighting_Operation) { + case BACNET_LIGHTS_NONE: + data->In_Progress = BACNET_LIGHTING_IDLE; + break; + case BACNET_LIGHTS_FADE_TO: + lighting_command_fade_handler(data, milliseconds); + break; + case BACNET_LIGHTS_RAMP_TO: + lighting_command_ramp_handler(data, milliseconds); + break; + case BACNET_LIGHTS_STEP_UP: + lighting_command_step_up_handler(data); + break; + case BACNET_LIGHTS_STEP_DOWN: + lighting_command_step_down_handler(data); + break; + case BACNET_LIGHTS_STEP_ON: + lighting_command_step_on_handler(data); + break; + case BACNET_LIGHTS_STEP_OFF: + lighting_command_step_off_handler(data); + break; + case BACNET_LIGHTS_WARN: + case BACNET_LIGHTS_WARN_OFF: + case BACNET_LIGHTS_WARN_RELINQUISH: + lighting_command_blink_handler(data, milliseconds); + break; + case BACNET_LIGHTS_STOP: + data->In_Progress = BACNET_LIGHTING_IDLE; + break; + default: + break; + } +} + +/** + * @brief Set the lighting command if the priority is active + * @param data [in] dimmer data + * @param value [in] BACnet lighting value + * @param fade_time [in] BACnet lighting fade time + */ +void lighting_command_fade_to( + struct bacnet_lighting_command_data *data, float value, uint32_t fade_time) +{ + if (!data) { + return; + } + data->Fade_Time = fade_time; + data->Lighting_Operation = BACNET_LIGHTS_FADE_TO; + data->Target_Level = value; +} + +/** + * @brief Set the dimmer command to perform a ramp to value operation + * @param data [in] dimmer object instance + * @param value [in] target lighting value + * @param ramp_rate [in] target ramp rate in percent per second 0.1 to 100.0 + */ +void lighting_command_ramp_to( + struct bacnet_lighting_command_data *data, float value, float ramp_rate) +{ + if (!data) { + return; + } + data->Ramp_Rate = ramp_rate; + data->Lighting_Operation = BACNET_LIGHTS_RAMP_TO; + data->Target_Level = value; +} + +/** + * @brief Set the lighting command if the priority is active + * @param data [in] dimmer object instance + * @param operation [in] BACnet lighting operation + * @param step_increment [in] BACnet lighting step increment value + */ +void lighting_command_step( + struct bacnet_lighting_command_data *data, + BACNET_LIGHTING_OPERATION operation, + float step_increment) +{ + if (!data) { + return; + } + data->Lighting_Operation = operation; + data->Fade_Time = 0; + data->Ramp_Rate = 0.0f; + data->Step_Increment = step_increment; +} + +/** + * @brief Set the lighting command to blink mode + * @param data [in] dimmer object instance + * @param operation [in] BACnet lighting operation for blink warn + * @param blink [in] BACnet blink data + */ +void lighting_command_blink_warn( + struct bacnet_lighting_command_data *data, + BACNET_LIGHTING_OPERATION operation, + struct bacnet_lighting_command_warn_data *blink) +{ + if (!data) { + return; + } + data->Lighting_Operation = operation; + data->Blink.Target_Interval = blink->Interval; + data->Blink.Duration = blink->Duration; + data->Blink.Count = blink->Count; + data->Blink.On_Value = blink->On_Value; + data->Blink.Off_Value = blink->Off_Value; + data->Blink.End_Value = blink->End_Value; + /* start blinking */ + data->In_Progress = BACNET_LIGHTING_OTHER; + /* configure next interval */ + data->Blink.State = false; + data->Blink.Interval = blink->Interval; +} + +/** + * @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 + */ +void lighting_command_stop(struct bacnet_lighting_command_data *data) +{ + if (!data) { + return; + } + data->Lighting_Operation = BACNET_LIGHTS_STOP; +} + +/** + * @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 + */ +void lighting_command_none(struct bacnet_lighting_command_data *data) +{ + if (!data) { + return; + } + data->Lighting_Operation = BACNET_LIGHTS_NONE; +} + +void lighting_command_init(struct bacnet_lighting_command_data *data) +{ + if (!data) { + return; + } + data->Tracking_Value = 0.0f; + data->Lighting_Operation = BACNET_LIGHTS_NONE; + data->In_Progress = BACNET_LIGHTING_IDLE; + 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->Blink.On_Value = 100.0f; + data->Blink.Off_Value = 0.0f; + data->Blink.End_Value = 0.0f; + data->Blink.Target_Interval = 0; + data->Blink.Count = 0; + data->Blink.Interval = 0; + data->Blink.Duration = 0; + data->Blink.State = false; + data->Tracking_Value_Callback = NULL; +} diff --git a/src/bacnet/basic/sys/lighting_command.h b/src/bacnet/basic/sys/lighting_command.h new file mode 100644 index 00000000..bafae623 --- /dev/null +++ b/src/bacnet/basic/sys/lighting_command.h @@ -0,0 +1,90 @@ +/** + * @file + * @brief API for lighting command brightness control engine + * @author Steve Karg + * @date 2022 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_BASIC_SYS_LIGHTING_COMMAND_H +#define BACNET_BASIC_SYS_LIGHTING_COMMAND_H +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" + +/** + * @brief Callback for tracking value updates + * @param old_value - value prior to write + * @param value - value of the write + */ +typedef void (*lighting_command_tracking_value_callback)( + uint32_t key, float old_value, float value); + +typedef struct bacnet_lighting_command_warn_data { + /* warn */ + float On_Value; + float Off_Value; + float End_Value; + uint16_t Target_Interval; + /* internal tracking */ + uint16_t Interval; + uint32_t Duration; + uint16_t Count; + /* bits */ + bool State : 1; +} BACNET_LIGHTING_COMMAND_WARN_DATA; + +typedef struct bacnet_lighting_command_data { + float Tracking_Value; + BACNET_LIGHTING_OPERATION Lighting_Operation; + float Target_Level; + float Ramp_Rate; + float Step_Increment; + uint32_t Fade_Time; + BACNET_LIGHTING_IN_PROGRESS In_Progress; + float Min_Actual_Value; + float Max_Actual_Value; + float High_Trim_Value; + float Low_Trim_Value; + BACNET_LIGHTING_COMMAND_WARN_DATA Blink; + /* bits - in common area of structure */ + bool Out_Of_Service : 1; + /* key used with callback */ + uint32_t Key; + lighting_command_tracking_value_callback Tracking_Value_Callback; +} BACNET_LIGHTING_COMMAND_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void lighting_command_timer( + struct bacnet_lighting_command_data *data, uint16_t milliseconds); +BACNET_STACK_EXPORT +void lighting_command_fade_to( + struct bacnet_lighting_command_data *data, float value, uint32_t fade_time); +BACNET_STACK_EXPORT +void lighting_command_ramp_to( + struct bacnet_lighting_command_data *data, float value, float ramp_rate); +BACNET_STACK_EXPORT +void lighting_command_step( + struct bacnet_lighting_command_data *data, + BACNET_LIGHTING_OPERATION operation, + float step_increment); +BACNET_STACK_EXPORT +void lighting_command_blink_warn( + struct bacnet_lighting_command_data *data, + BACNET_LIGHTING_OPERATION operation, + struct bacnet_lighting_command_warn_data *blink); +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_init(struct bacnet_lighting_command_data *data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fd7a6b5a..7f95f8a1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -171,6 +171,7 @@ list(APPEND testdirs # basic/sys bacnet/basic/sys/color_rgb bacnet/basic/sys/days + bacnet/basic/sys/lighting_command bacnet/basic/sys/fifo bacnet/basic/sys/filename bacnet/basic/sys/keylist diff --git a/test/bacnet/basic/object/blo/CMakeLists.txt b/test/bacnet/basic/object/blo/CMakeLists.txt index 4f17c9b8..70eb6aea 100644 --- a/test/bacnet/basic/object/blo/CMakeLists.txt +++ b/test/bacnet/basic/object/blo/CMakeLists.txt @@ -48,7 +48,9 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/lighting_command.c ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/indtext.c diff --git a/test/bacnet/basic/object/color_object/CMakeLists.txt b/test/bacnet/basic/object/color_object/CMakeLists.txt index beda6112..c6c048bd 100644 --- a/test/bacnet/basic/object/color_object/CMakeLists.txt +++ b/test/bacnet/basic/object/color_object/CMakeLists.txt @@ -46,11 +46,13 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/lighting_command.c ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/cov.c ${SRC_DIR}/bacnet/datetime.c - ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c diff --git a/test/bacnet/basic/object/color_temperature/CMakeLists.txt b/test/bacnet/basic/object/color_temperature/CMakeLists.txt index 8055be16..634fd982 100644 --- a/test/bacnet/basic/object/color_temperature/CMakeLists.txt +++ b/test/bacnet/basic/object/color_temperature/CMakeLists.txt @@ -46,11 +46,13 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/lighting_command.c ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/cov.c ${SRC_DIR}/bacnet/datetime.c - ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index a576b21d..7a725d27 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -83,14 +83,15 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/service/h_cov.c ${SRC_DIR}/bacnet/basic/service/h_wp.c ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/lighting_command.c ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/basic/tsm/tsm.c ${SRC_DIR}/bacnet/datalink/bvlc.c ${SRC_DIR}/bacnet/cov.c ${SRC_DIR}/bacnet/datetime.c - ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/dcc.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt index 5f45b3fe..36344ebb 100644 --- a/test/bacnet/basic/object/lo/CMakeLists.txt +++ b/test/bacnet/basic/object/lo/CMakeLists.txt @@ -51,6 +51,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/sys/color_rgb.c ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/lighting_command.c ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/indtext.c diff --git a/test/bacnet/basic/sys/lighting_command/CMakeLists.txt b/test/bacnet/basic/sys/lighting_command/CMakeLists.txt new file mode 100644 index 00000000..b75d84d0 --- /dev/null +++ b/test/bacnet/basic/sys/lighting_command/CMakeLists.txt @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/sys/lighting_command.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/basic/sys/debug.c + ${SRC_DIR}/bacnet/basic/sys/linear.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) + +target_link_libraries(${PROJECT_NAME} PRIVATE + m) diff --git a/test/bacnet/basic/sys/lighting_command/src/main.c b/test/bacnet/basic/sys/lighting_command/src/main.c new file mode 100644 index 00000000..aba82c15 --- /dev/null +++ b/test/bacnet/basic/sys/lighting_command/src/main.c @@ -0,0 +1,380 @@ +/* @file + * @brief test BACnet integer 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 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +static float Tracking_Value; + +/** + * @brief Callback for tracking value updates + * @param old_value - value prior to write + * @param value - value of the write + */ +static void dimmer_tracking_value(uint32_t key, float old_value, float value) +{ + (void)key; + (void)old_value; + Tracking_Value = value; +} + +/** + * @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; +} + +/** + * @brief test dimmer blink handler + * @param data - dimmer data + * @param blink - blink data + * @param milliseconds - number of milliseconds per timer call + */ +static void test_lighting_command_blink_unit(BACNET_LIGHTING_COMMAND_DATA *data) +{ + uint16_t milliseconds = 10; + uint32_t duration = 0; + BACNET_LIGHTING_COMMAND_WARN_DATA *blink = &data->Blink; + + lighting_command_fade_to(data, data->Max_Actual_Value, 0); + lighting_command_timer(data, milliseconds); + zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data->Max_Actual_Value), NULL); + if (blink->Duration == 0) { + /* immediate */ + lighting_command_blink_warn(data, BACNET_LIGHTS_WARN, blink); + lighting_command_timer(data, milliseconds); + zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, blink->End_Value), NULL); + } else if (blink->Interval == 0) { + /* no blink, just egress timing */ + lighting_command_blink_warn(data, BACNET_LIGHTS_WARN, blink); + lighting_command_timer(data, milliseconds); + zassert_true(data->In_Progress == BACNET_LIGHTING_OTHER, NULL); + zassert_true(is_float_equal(Tracking_Value, blink->On_Value), NULL); + milliseconds = blink->Duration; + lighting_command_blink_warn(data, BACNET_LIGHTS_WARN, blink); + lighting_command_timer(data, milliseconds); + zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, blink->End_Value), NULL); + } else { + /* blinking and egress timing */ + if ((blink->Count > 0) && (blink->Count < UINT16_MAX)) { + duration = blink->Count * blink->Interval * 2UL; + if (duration > blink->Duration) { + duration = blink->Duration; + } + } else { + duration = blink->Duration; + } + milliseconds = blink->Interval; + do { + lighting_command_blink_warn(data, BACNET_LIGHTS_WARN, blink); + lighting_command_timer(data, milliseconds); + if (blink->Duration) { + zassert_true( + data->In_Progress == BACNET_LIGHTING_OTHER, + "In_Progress=%d", data->In_Progress); + if (data->Blink.State) { + zassert_true( + is_float_equal(Tracking_Value, blink->Off_Value), + "Tracking_Value=%f", Tracking_Value); + } else { + zassert_true( + is_float_equal(Tracking_Value, blink->On_Value), + "Tracking_Value=%f", Tracking_Value); + } + } else { + zassert_true( + data->In_Progress == BACNET_LIGHTING_IDLE, "In_Progress=%d", + data->In_Progress); + zassert_true( + is_float_equal(Tracking_Value, blink->End_Value), + "Tracking_Value=%f", Tracking_Value); + } + } while (blink->Duration); + } +} + +/** + * Tests for Dimmer Command + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(lighting_command_tests, test_lighting_command_command_unit) +#else +static void test_lighting_command_command_unit(void) +#endif +{ + BACNET_LIGHTING_COMMAND_DATA data = { 0 }; + uint16_t milliseconds = 10; + uint32_t fade_time = 1000; + float target_level = 100.0f; + float target_step = 1.0f; + + lighting_command_init(&data); + data.Tracking_Value_Callback = dimmer_tracking_value; + 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); + /* fade up */ + lighting_command_fade_to(&data, 100.0f, fade_time); + milliseconds = fade_time / 2; + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_FADE_ACTIVE, NULL); + zassert_true( + is_float_equal(Tracking_Value, 50.5f), "Tracking_Value=%f", + Tracking_Value); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + /* fade down */ + target_level = 0.0f; + lighting_command_fade_to(&data, target_level, fade_time); + milliseconds = fade_time / 2; + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_FADE_ACTIVE, NULL); + zassert_true( + is_float_equal(Tracking_Value, 50.5f), "Tracking_Value=%f", + Tracking_Value); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL); + /* low trim */ + data.Low_Trim_Value = 10.0f; + target_level = 1.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, data.Low_Trim_Value), NULL); + 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); + data.Low_Trim_Value = data.Min_Actual_Value; + /* high trim */ + data.High_Trim_Value = 90.0f; + target_level = 100.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, data.High_Trim_Value), NULL); + data.High_Trim_Value = data.Max_Actual_Value; + + /* step UP - inhibit ON */ + target_step = 1.0f; + 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_step(&data, BACNET_LIGHTS_STEP_UP, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL); + /* step UP while ON */ + target_step = 1.0f; + target_level = 1.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_step(&data, BACNET_LIGHTS_STEP_UP, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true( + is_float_equal(Tracking_Value, target_step + target_level), NULL); + /* clamp to max */ + target_step = 100.0f; + lighting_command_step(&data, BACNET_LIGHTS_STEP_UP, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Max_Actual_Value), NULL); + /* turn ON, then step UP */ + target_step = 1.0f; + 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_step(&data, BACNET_LIGHTS_STEP_ON, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, 1.0f), NULL); + /* clamp to max */ + target_step = 100.0f; + lighting_command_step(&data, BACNET_LIGHTS_STEP_ON, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Max_Actual_Value), NULL); + /* step DOWN, not off */ + target_step = 1.0f; + target_level = data.Min_Actual_Value + target_step; + 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_step(&data, BACNET_LIGHTS_STEP_DOWN, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Min_Actual_Value), NULL); + /* clamp to min */ + target_step = 100.0f; + lighting_command_step(&data, BACNET_LIGHTS_STEP_DOWN, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.Min_Actual_Value), NULL); + /* step DOWN and off */ + target_step = 100.0f; + target_level = data.Min_Actual_Value; + 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_step(&data, BACNET_LIGHTS_STEP_OFF, target_step); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL); + zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL); + /* blink warn - immediate off */ + data.Blink.Interval = 0; + data.Blink.Duration = 0; + data.Blink.State = false; + data.Blink.On_Value = 100.0f; + data.Blink.Off_Value = 0.0f; + data.Blink.End_Value = 0.0f; + data.Blink.Count = UINT16_MAX; + test_lighting_command_blink_unit(&data); + /* blink warn - off after duration */ + data.Blink.Interval = 0; + data.Blink.Duration = 1000; + data.Blink.State = false; + data.Blink.On_Value = 100.0f; + data.Blink.Off_Value = 0.0f; + data.Blink.End_Value = 0.0f; + data.Blink.Count = UINT16_MAX; + test_lighting_command_blink_unit(&data); + /* blink warn - on/off for duration */ + data.Blink.Interval = 500; + data.Blink.Duration = 2000; + data.Blink.State = false; + data.Blink.On_Value = 100.0f; + data.Blink.Off_Value = 0.0f; + data.Blink.End_Value = 0.0f; + data.Blink.Count = UINT16_MAX; + test_lighting_command_blink_unit(&data); + + /* quick ramp */ + target_level = 0.0f; + milliseconds = 1000; + 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); + target_level = 100.0f; + lighting_command_ramp_to(&data, target_level, 100.0f); + lighting_command_timer(&data, milliseconds); + zassert_true( + data.In_Progress == BACNET_LIGHTING_RAMP_ACTIVE, "In_Progress=%d", + data.In_Progress); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + lighting_command_timer(&data, milliseconds); + zassert_true( + data.In_Progress == BACNET_LIGHTING_IDLE, "In_Progress=%d", + data.In_Progress); + /* slower ramp up */ + target_level = 100.0f; + milliseconds = 100; + do { + lighting_command_ramp_to(&data, target_level, 1.0f); + 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); + + /* slower ramp down */ + target_level = data.Min_Actual_Value; + milliseconds = 33; + do { + lighting_command_ramp_to(&data, target_level, 0.1f); + 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); + + /* null check code coverage */ + 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_timer(NULL, 0); + lighting_command_init(NULL); +} + +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(lighting_command_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite( + lighting_command_tests, + ztest_unit_test(test_lighting_command_command_unit)); + + ztest_run_test_suite(lighting_command_tests); +} +#endif