diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c index c9bd542e..3eb7ba88 100644 --- a/src/bacnet/basic/object/lo.c +++ b/src/bacnet/basic/object/lo.c @@ -36,6 +36,8 @@ struct object_data { uint32_t Egress_Time_Seconds; uint32_t Default_Fade_Time; uint32_t Trim_Fade_Time; + float High_End_Trim; + float Low_End_Trim; float Default_Ramp_Rate; float Default_Step_Increment; BACNET_LIGHTING_TRANSITION Transition; @@ -498,6 +500,57 @@ unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) return priority; } +/** + * @brief Determine if fade, ramp, or warn command is currently executing + * @param pObject [in] object to apply the trim values to + * @param priority [in] priority of the command + */ +static bool Lighting_Command_In_Progress(struct object_data *pObject) +{ + bool in_progress = false; + + if (!pObject) { + return in_progress; + } + if ((pObject->Lighting_Command.In_Progress == + BACNET_LIGHTING_FADE_ACTIVE) || + (pObject->Lighting_Command.In_Progress == + BACNET_LIGHTING_RAMP_ACTIVE) || + (pObject->Lighting_Command.Blink.Duration > 0)) { + in_progress = true; + } + + return in_progress; +} + +/** + * @brief Configure the lighting command to apply low or high trim + * to the tracking value based on the priority of the command + * @param pObject [in] object to apply the trim values to + * @param priority [in] priority of the command + */ +static void +Lighting_Command_Trim_Apply(struct object_data *pObject, unsigned priority) +{ + if (!pObject) { + return; + } + /* If Present_Value is commanded at priority 1 or 2, + High_End_Trim and Low_End_Trim shall not be applied and + the Tracking_Value shall not be clamped. */ + if ((priority == 1) || (priority == 2)) { + /* remove any high or low trim */ + pObject->Lighting_Command.High_Trim_Value = 100.0f; + pObject->Lighting_Command.Low_Trim_Value = 1.0f; + pObject->Lighting_Command.Trim_Fade_Time = 0; + } else { + /* apply high and low trim */ + pObject->Lighting_Command.High_Trim_Value = pObject->High_End_Trim; + pObject->Lighting_Command.Low_Trim_Value = pObject->Low_End_Trim; + pObject->Lighting_Command.Trim_Fade_Time = pObject->Trim_Fade_Time; + } +} + /** * @brief Set the lighting command if the priority is active * @param object [in] BACnet object instance @@ -519,7 +572,9 @@ static void Lighting_Command_Fade_To( Present_Value_Set(pObject, value, priority); current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ + /* we have priority */ + Lighting_Command_Trim_Apply(pObject, priority); + /* configure the Lighting Command */ lighting_command_fade_to(&pObject->Lighting_Command, value, fade_time); } } @@ -545,7 +600,9 @@ static void Lighting_Command_Ramp_To( Present_Value_Set(pObject, value, priority); current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { - /* we have priority - configure the Lighting Command */ + /* we have priority */ + Lighting_Command_Trim_Apply(pObject, priority); + /* configure the Lighting Command */ lighting_command_ramp_to(&pObject->Lighting_Command, value, ramp_rate); } } @@ -598,6 +655,7 @@ Lighting_Command_Warn(struct object_data *pObject, unsigned priority) (Priority_Array_Active(pObject, priority - 1)) && (!is_float_equal(Priority_Array_Value(pObject, priority - 1), 0.0)) && pObject->Blink_Warn_Enable) { + Lighting_Command_Trim_Apply(pObject, priority); /* The blink-warn notification shall not occur if any of the following conditions occur: (a) The specified priority is not the highest @@ -662,6 +720,7 @@ Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority) } current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { + Lighting_Command_Trim_Apply(pObject, priority); if ((Priority_Array_Active(pObject, priority - 1)) && (!is_float_equal( Priority_Array_Value(pObject, priority - 1), 0.0)) && @@ -732,6 +791,7 @@ Lighting_Command_Warn_Relinquish(struct object_data *pObject, unsigned priority) } current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { + Lighting_Command_Trim_Apply(pObject, priority); if ((Priority_Array_Active(pObject, priority - 1)) && (!is_float_equal( Priority_Array_Value(pObject, priority - 1), 0.0)) && @@ -813,6 +873,7 @@ static void Lighting_Command_Step_Up_On( current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { /* we have priority - configure the Lighting Command */ + Lighting_Command_Trim_Apply(pObject, priority); lighting_command_step( &pObject->Lighting_Command, operation, step_increment); } @@ -870,6 +931,7 @@ static void Lighting_Command_Step_Down_Off( current_priority = Present_Value_Priority(pObject); if (priority <= current_priority) { /* we have priority - configure the Lighting Command */ + Lighting_Command_Trim_Apply(pObject, priority); lighting_command_step( &pObject->Lighting_Command, operation, step_increment); } @@ -1398,11 +1460,7 @@ Lighting_Command_Stop(struct object_data *pObject, unsigned priority) } current_priority = Present_Value_Priority(pObject); if (priority == current_priority) { - if ((pObject->Lighting_Command.In_Progress == - BACNET_LIGHTING_FADE_ACTIVE) || - (pObject->Lighting_Command.In_Progress == - BACNET_LIGHTING_RAMP_ACTIVE) || - (pObject->Lighting_Command.Blink.Duration > 0)) { + if (Lighting_Command_In_Progress(pObject)) { /* fade, ramp, or warn command is currently executing at the specified priority */ value = pObject->Lighting_Command.Tracking_Value; @@ -2552,7 +2610,7 @@ float Lighting_Output_High_End_Trim(uint32_t object_instance) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - value = pObject->Lighting_Command.High_Trim_Value; + value = pObject->High_End_Trim; } return value; @@ -2572,8 +2630,15 @@ bool Lighting_Output_High_End_Trim_Set(uint32_t object_instance, float value) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { + /* High_End_Trim shall always be a positive number + in the normalized range 1.0% to 100.0%. */ if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) { - pObject->Lighting_Command.High_Trim_Value = value; + pObject->High_End_Trim = value; + Lighting_Command_Trim_Apply( + pObject, Present_Value_Priority(pObject)); + if (!Lighting_Command_In_Progress(pObject)) { + lighting_command_refresh(&pObject->Lighting_Command); + } status = true; } } @@ -2622,7 +2687,7 @@ float Lighting_Output_Low_End_Trim(uint32_t object_instance) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - value = pObject->Lighting_Command.Low_Trim_Value; + value = pObject->Low_End_Trim; } return value; @@ -2642,8 +2707,15 @@ bool Lighting_Output_Low_End_Trim_Set(uint32_t object_instance, float value) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { + /* Low_End_Trim shall always be a positive number + in the normalized range 1.0% to 100.0%. */ if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) { - pObject->Lighting_Command.Low_Trim_Value = value; + pObject->Low_End_Trim = value; + Lighting_Command_Trim_Apply( + pObject, Present_Value_Priority(pObject)); + if (!Lighting_Command_In_Progress(pObject)) { + lighting_command_refresh(&pObject->Lighting_Command); + } status = true; } } @@ -2714,7 +2786,14 @@ bool Lighting_Output_Trim_Fade_Time_Set( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { if (value <= 86400000UL) { + /* The range of allowable fade-time values + is 0 ms to 86400000 ms (1 day) inclusive. */ pObject->Trim_Fade_Time = value; + Lighting_Command_Trim_Apply( + pObject, Present_Value_Priority(pObject)); + if (!Lighting_Command_In_Progress(pObject)) { + lighting_command_refresh(&pObject->Lighting_Command); + } status = true; } } @@ -2766,9 +2845,7 @@ bool Lighting_Output_Overridden_Set(uint32_t object_instance, float value) 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); + lighting_command_override_set(&pObject->Lighting_Command, value); status = true; } @@ -2791,10 +2868,8 @@ bool Lighting_Output_Overridden_Clear(uint32_t object_instance) 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); + lighting_command_override_clear(&pObject->Lighting_Command, value); status = true; } @@ -2818,9 +2893,7 @@ bool Lighting_Output_Overridden_Momentary(uint32_t object_instance, float value) 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); + lighting_command_override_momentary(&pObject->Lighting_Command, value); status = true; } @@ -3787,6 +3860,11 @@ uint32_t Lighting_Output_Create(uint32_t object_instance) pObject->Last_Lighting_Command.use_fade_time = false; pObject->Last_Lighting_Command.use_priority = false; pObject->Blink_Warn_Enable = false; + pObject->High_End_Trim = 100.0f; + pObject->Lighting_Command.High_Trim_Value = pObject->High_End_Trim; + pObject->Low_End_Trim = 1.0f; + pObject->Lighting_Command.Low_Trim_Value = pObject->Low_End_Trim; + pObject->Trim_Fade_Time = 0; pObject->Egress_Time_Seconds = 0; pObject->Default_Fade_Time = 100; pObject->Default_Ramp_Rate = 100.0; diff --git a/src/bacnet/basic/sys/lighting_command.c b/src/bacnet/basic/sys/lighting_command.c index a160897b..050154bd 100644 --- a/src/bacnet/basic/sys/lighting_command.c +++ b/src/bacnet/basic/sys/lighting_command.c @@ -163,6 +163,32 @@ float lighting_command_step_increment_clamp(float step_increment) return step_increment; } +/** + * @brief Clamp the value within the physical 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. + * @param value [in] value to clamp within the physical min/max range + * @return value clamped within the physical min/max range of 0.0% to 100.0% + */ +float lighting_command_physical_range_clamp(float value) +{ + float physical_value; + + /* clamp value within physical values, if non-zero */ + if (isless(value, 1.0f)) { + /* jump target to OFF */ + physical_value = 0.0f; + } else if (isgreater(value, 100.0f)) { + physical_value = 100.0f; + } else { + physical_value = value; + } + + return physical_value; +} + /** * @brief Calculate the target value for a step down command * @param tracking_value [in] current tracking value @@ -201,6 +227,52 @@ float lighting_command_step_up_target_value( return target_value; } +/** + * @brief Calculate the target value for a low or high trim fade operation + * @param data - dimmer data structure + * @param value - target value for the trim fade operation + * @param trim_value - target trim value for the fade operation + * @param milliseconds - number of milliseconds elapsed since the last call + * @return calculated target value for the trim fade operation + */ +static float lighting_command_trim_fade( + struct bacnet_lighting_command_data *data, + float value, + float trim_value, + uint32_t milliseconds) +{ + float new_value; + float x1, x2, x3, y1, y3; + + if (data) { + if (milliseconds > 0) { + /* fading */ + if (milliseconds >= data->Trim_Fade_Time) { + /* end of fading */ + data->Trim_Fade_Time = 0; + new_value = trim_value; + } else { + x1 = 0.0f; + x2 = (float)milliseconds; + x3 = (float)data->Trim_Fade_Time; + y1 = value; + y3 = trim_value; + new_value = linear_interpolate(x1, x2, x3, y1, y3); + data->Trim_Fade_Time -= milliseconds; + } + } else { + /* no fading */ + data->Trim_Fade_Time = 0; + new_value = trim_value; + } + } else { + /* no fading */ + new_value = value; + } + + return new_value; +} + /** * @brief Clamp the value within the operating range between low and high * end trim values. @@ -212,23 +284,68 @@ float lighting_command_step_up_target_value( * 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 + * @param milliseconds - number of milliseconds elapsed since the last call + * @return value clamped within the operating range defined by the High_End_Trim + * and Low_End_Trim property values + */ +float lighting_command_operating_range_clamp_fade( + struct bacnet_lighting_command_data *data, + float value, + uint16_t milliseconds) +{ + float high_trim, low_trim, swap_value; + + if (data) { + /* clamp range within physical limits */ + high_trim = + lighting_command_physical_range_clamp(data->High_Trim_Value); + low_trim = lighting_command_physical_range_clamp(data->Low_Trim_Value); + /* valid range check for high and low trim values */ + if (isgreater(low_trim, high_trim)) { + /* swap the trims if they are inverse */ + swap_value = low_trim; + low_trim = high_trim; + high_trim = swap_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, high_trim)) { + value = lighting_command_trim_fade( + data, value, high_trim, milliseconds); + data->In_Progress = BACNET_LIGHTING_TRIM_ACTIVE; + } else if (isless(value, low_trim)) { + value = + lighting_command_trim_fade(data, value, low_trim, milliseconds); + data->In_Progress = BACNET_LIGHTING_TRIM_ACTIVE; + } + } else { + /* no data, so just clamp value within physical limits */ + value = lighting_command_physical_range_clamp(value); + } + + return value; +} + +/** + * @brief Clamp the value within the operating range between low and high + * end trim values immediately. + * @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 + * @return value clamped within the operating range defined by the High_End_Trim + * and Low_End_Trim property values */ 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; + return lighting_command_operating_range_clamp_fade(data, value, 0); } /** @@ -250,11 +367,23 @@ float lighting_command_operating_range_clamp( float lighting_command_normalized_on_range_clamp( struct bacnet_lighting_command_data *data, float value) { + float min_value, max_value, swap_value; + + /* clamp range within physical limits */ + max_value = lighting_command_physical_range_clamp(data->Max_Actual_Value); + min_value = lighting_command_physical_range_clamp(data->Min_Actual_Value); + /* valid range check for high and low trim values */ + if (isgreater(min_value, max_value)) { + /* swap the trims if they are inverse */ + swap_value = min_value; + min_value = max_value; + max_value = swap_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; + if (isgreater(value, max_value)) { + value = max_value; + } else if (isless(value, min_value)) { + value = min_value; } return value; @@ -280,15 +409,26 @@ float lighting_command_normalized_range_clamp( struct bacnet_lighting_command_data *data, float value) { float normalized_value; + float min_value, max_value, swap_value; + /* clamp range within physical limits */ + max_value = lighting_command_physical_range_clamp(data->Max_Actual_Value); + min_value = lighting_command_physical_range_clamp(data->Min_Actual_Value); + /* valid range check for high and low trim values */ + if (isgreater(min_value, max_value)) { + /* swap the trims if they are inverse */ + swap_value = min_value; + min_value = max_value; + max_value = swap_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 if (isgreater(value, max_value)) { + normalized_value = max_value; + } else if (isless(value, min_value)) { + normalized_value = min_value; } else { normalized_value = value; } @@ -301,19 +441,18 @@ float lighting_command_normalized_range_clamp( * @param data - dimmer data structure * @param old_value - value prior to write * @param value - value of the write + * @param milliseconds - number of milliseconds elapsed */ static void lighting_command_tracking_value_event( struct bacnet_lighting_command_data *data, float old_value, float value) { if (data->Overridden) { - value = lighting_command_operating_range_clamp(data, 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); lighting_command_tracking_value_notify(data, old_value, value); } else { debug_printf( @@ -336,6 +475,7 @@ static void lighting_command_fade_handler( float target_value; old_value = data->Tracking_Value; + /* clamp Tracking value within the Normalized ON Range */ target_value = lighting_command_normalized_on_range_clamp(data, data->Target_Level); if ((milliseconds >= data->Fade_Time) || @@ -365,6 +505,10 @@ static void lighting_command_fade_handler( data->Fade_Time -= milliseconds; data->In_Progress = BACNET_LIGHTING_FADE_ACTIVE; } + /* clamp Tracking Value inclusively within the Operating Range */ + data->Tracking_Value = lighting_command_operating_range_clamp_fade( + data, data->Tracking_Value, milliseconds); + /* notify */ lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -387,9 +531,11 @@ static void lighting_command_fade_handler( static void lighting_command_ramp_handler( struct bacnet_lighting_command_data *data, uint16_t milliseconds) { - float old_value, target_value, step_value, steps, ramp_rate; + float old_value, target_value, step_value, steps, ramp_rate, + operating_value; old_value = data->Tracking_Value; + /* clamp Tracking value within the Normalized ON Range */ target_value = lighting_command_normalized_on_range_clamp(data, data->Target_Level); if (!islessgreater(data->Tracking_Value, target_value)) { @@ -449,6 +595,11 @@ static void lighting_command_ramp_handler( data->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE; } } + /* clamp Tracking_Value inclusively within the Operating Range */ + operating_value = lighting_command_operating_range_clamp_fade( + data, data->Tracking_Value, milliseconds); + data->Tracking_Value = operating_value; + /* notify */ lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -465,17 +616,23 @@ static void lighting_command_ramp_handler( static void lighting_command_step_up_handler(struct bacnet_lighting_command_data *data) { - float old_value, target_value; + float old_value, target_value, operating_value; old_value = data->Tracking_Value; if (isgreaterequal(old_value, data->Min_Actual_Value)) { /* inhibit ON if the value is already OFF */ target_value = lighting_command_step_up_target_value( data->Tracking_Value, data->Step_Increment); + /* clamp Tracking value within the Normalized ON Range */ data->Tracking_Value = lighting_command_normalized_on_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; + /* clamp Tracking value inclusively within the Operating Range */ + operating_value = + lighting_command_operating_range_clamp(data, data->Tracking_Value); + data->Tracking_Value = operating_value; + /* notify */ lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -493,15 +650,21 @@ lighting_command_step_up_handler(struct bacnet_lighting_command_data *data) static void lighting_command_step_down_handler(struct bacnet_lighting_command_data *data) { - float old_value, target_value; + float old_value, target_value, operating_value; old_value = data->Tracking_Value; target_value = lighting_command_step_down_target_value( data->Tracking_Value, data->Step_Increment); + /* clamp Tracking value within the Normalized ON Range */ data->Tracking_Value = lighting_command_normalized_on_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; + /* clamp Tracking value inclusively within the Operating Range */ + operating_value = + lighting_command_operating_range_clamp(data, data->Tracking_Value); + data->Tracking_Value = operating_value; + /* notify */ lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -518,7 +681,7 @@ 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; + float old_value, target_value, operating_value; old_value = data->Tracking_Value; target_value = lighting_command_step_up_target_value( @@ -527,6 +690,11 @@ lighting_command_step_on_handler(struct bacnet_lighting_command_data *data) lighting_command_normalized_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; + /* clamp Tracking value inclusively within the Operating Range */ + operating_value = + lighting_command_operating_range_clamp(data, data->Tracking_Value); + data->Tracking_Value = operating_value; + /* notify */ lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -543,7 +711,7 @@ 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; + float old_value, target_value, operating_value; old_value = data->Tracking_Value; target_value = lighting_command_step_down_target_value( @@ -552,6 +720,11 @@ lighting_command_step_off_handler(struct bacnet_lighting_command_data *data) lighting_command_normalized_range_clamp(data, target_value); data->In_Progress = BACNET_LIGHTING_IDLE; data->Lighting_Operation = BACNET_LIGHTS_STOP; + /* clamp Tracking value inclusively within the Operating Range */ + operating_value = + lighting_command_operating_range_clamp(data, data->Tracking_Value); + data->Tracking_Value = operating_value; + /* notify */ lighting_command_tracking_value_event( data, old_value, data->Tracking_Value); } @@ -583,7 +756,7 @@ 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; + float old_value, target_value, operating_value; old_value = data->Tracking_Value; /* detect 'end' operation */ @@ -635,16 +808,19 @@ static void lighting_command_blink_handler( } } target_value = lighting_command_normalized_range_clamp(data, target_value); + /* clamp Tracking value inclusively within the Operating Range */ + operating_value = + lighting_command_operating_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; + data->Tracking_Value = operating_value; } - lighting_command_tracking_value_event(data, old_value, target_value); + lighting_command_tracking_value_event(data, old_value, operating_value); } /** - * @brief Overrides the current lighting command if overridden is true + * @brief Overrides the current lighting command with the provided value * @param data [in] dimmer data */ void lighting_command_override( @@ -656,8 +832,63 @@ void lighting_command_override( return; } old_value = data->Tracking_Value; - data->Tracking_Value = value; - lighting_command_tracking_value_event(data, old_value, value); + data->Tracking_Value = lighting_command_physical_range_clamp(value); + lighting_command_tracking_value_event( + data, old_value, data->Tracking_Value); +} + +/** + * @brief Overrides the current lighting command with the provided value + * @param data [in] dimmer data + */ +void lighting_command_override_set( + struct bacnet_lighting_command_data *data, float value) +{ + if (!data) { + return; + } + data->Overridden = true; + data->Overridden_Momentary = false; + lighting_command_override(data, value); +} + +/** + * @brief Clears the override of the current lighting command + * @param data [in] dimmer data + */ +void lighting_command_override_clear( + struct bacnet_lighting_command_data *data, float value) +{ + float old_value, normalized_value, operating_value; + + if (!data) { + return; + } + data->Overridden = false; + data->Overridden_Momentary = false; + old_value = data->Tracking_Value; + /* clamp Tracking value within the Normalized Range */ + normalized_value = lighting_command_normalized_range_clamp(data, value); + /* clamp Tracking value inclusively within the Operating Range */ + operating_value = + lighting_command_operating_range_clamp(data, normalized_value); + data->Tracking_Value = operating_value; + lighting_command_tracking_value_event(data, old_value, operating_value); +} + +/** + * @brief Overrides the current lighting command with the provided value + * @param data [in] dimmer data + */ +void lighting_command_override_momentary( + struct bacnet_lighting_command_data *data, float value) +{ + if (!data) { + return; + } + data->Overridden = true; + data->Overridden_Momentary = true; + lighting_command_override(data, value); } /** @@ -1009,6 +1240,7 @@ void lighting_command_init(struct bacnet_lighting_command_data *data) data->Max_Actual_Value = 100.0f; data->Low_Trim_Value = 1.0f; data->High_Trim_Value = 100.0f; + data->Trim_Fade_Time = 0; data->Last_On_Value = 100.0f; data->Default_On_Value = 100.0f; data->Overridden = false; diff --git a/src/bacnet/basic/sys/lighting_command.h b/src/bacnet/basic/sys/lighting_command.h index d61b508c..d8babfd3 100644 --- a/src/bacnet/basic/sys/lighting_command.h +++ b/src/bacnet/basic/sys/lighting_command.h @@ -79,6 +79,7 @@ typedef struct bacnet_lighting_command_data { float Max_Actual_Value; float High_Trim_Value; float Low_Trim_Value; + uint32_t Trim_Fade_Time; float Default_On_Value; float Last_On_Value; BACNET_LIGHTING_COMMAND_WARN_DATA Blink; @@ -135,6 +136,15 @@ void lighting_command_toggle_default( BACNET_STACK_EXPORT void lighting_command_override( struct bacnet_lighting_command_data *data, float value); +BACNET_STACK_EXPORT +void lighting_command_override_set( + struct bacnet_lighting_command_data *data, float value); +BACNET_STACK_EXPORT +void lighting_command_override_clear( + struct bacnet_lighting_command_data *data, float value); +BACNET_STACK_EXPORT +void lighting_command_override_momentary( + struct bacnet_lighting_command_data *data, float value); BACNET_STACK_EXPORT float lighting_command_ramp_rate_clamp(float ramp_rate); @@ -144,11 +154,18 @@ BACNET_STACK_EXPORT float lighting_command_operating_range_clamp( struct bacnet_lighting_command_data *data, float value); BACNET_STACK_EXPORT +float lighting_command_operating_range_clamp_fade( + struct bacnet_lighting_command_data *data, + float value, + uint16_t milliseconds); +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 +float lighting_command_physical_range_clamp(float value); BACNET_STACK_EXPORT void lighting_command_refresh(struct bacnet_lighting_command_data *data); diff --git a/test/bacnet/basic/object/lo/src/main.c b/test/bacnet/basic/object/lo/src/main.c index e9041fdb..3847c3a5 100644 --- a/test/bacnet/basic/object/lo/src/main.c +++ b/test/bacnet/basic/object/lo/src/main.c @@ -402,6 +402,112 @@ static void testLightingOutput(void) 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); + /* high-end-trim, low-end-trim, and trim-fade-time */ + Lighting_Output_Present_Value_Relinquish_All(instance); + status = Lighting_Output_Transition_Set( + instance, BACNET_LIGHTING_TRANSITION_NONE); + zassert_true(status, NULL); + status = Lighting_Output_Trim_Fade_Time_Set(instance, 0); + zassert_true(status, NULL); + status = Lighting_Output_High_End_Trim_Set(instance, 90.0f); + zassert_true(status, NULL); + test_real = Lighting_Output_High_End_Trim(instance); + zassert_true(is_float_equal(test_real, 90.0f), NULL); + status = Lighting_Output_Low_End_Trim_Set(instance, 10.0f); + zassert_true(status, NULL); + test_real = Lighting_Output_Low_End_Trim(instance); + zassert_true(is_float_equal(test_real, 10.0f), NULL); + 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_TRIM_ACTIVE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 90.0f), 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, 1.0f, priority); + zassert_true(status, NULL); + Lighting_Output_Timer(instance, 10); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_TRIM_ACTIVE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 10.0f), "tracking=%f", test_real); + 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)); + Lighting_Output_Present_Value_Relinquish_All(instance); + status = Lighting_Output_Present_Value_Set(instance, 100.0f, 1); + 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)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 100.0f), "tracking=%f", test_real); + Lighting_Output_Present_Value_Relinquish_All(instance); + status = Lighting_Output_Present_Value_Set(instance, 80.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, 80.0f), NULL); + unsigned_value = 1000; + status = Lighting_Output_Trim_Fade_Time_Set(instance, unsigned_value); + zassert_true(status, NULL); + test_unsigned = Lighting_Output_Trim_Fade_Time(instance); + zassert_equal(test_unsigned, unsigned_value, NULL); + status = Lighting_Output_Default_Fade_Time_Set(instance, 2000); + zassert_true(status, NULL); + status = Lighting_Output_Transition_Set( + instance, BACNET_LIGHTING_TRANSITION_FADE); + zassert_true(status, NULL); + status = Lighting_Output_Present_Value_Set(instance, 100.0f, priority); + zassert_true(status, NULL); + milliseconds = 500; + Lighting_Output_Timer(instance, milliseconds); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_FADE_ACTIVE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 85.0f), NULL); + Lighting_Output_Timer(instance, milliseconds); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_FADE_ACTIVE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 90.0f), NULL); + Lighting_Output_Timer(instance, milliseconds); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_TRIM_ACTIVE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 92.5f), NULL); + Lighting_Output_Timer(instance, milliseconds); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_TRIM_ACTIVE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); + test_real = Lighting_Output_Tracking_Value(instance); + zassert_true(is_float_equal(test_real, 90.0f), NULL); + Lighting_Output_Timer(instance, milliseconds); + in_progress = Lighting_Output_In_Progress(instance); + zassert_equal( + in_progress, BACNET_LIGHTING_IDLE, "in_progress=%s", + bactext_lighting_in_progress(in_progress)); /* feedback value */ status = Lighting_Output_Feedback_Value_Set(instance, 55.5f); zassert_true(status, NULL); diff --git a/test/bacnet/basic/sys/lighting_command/src/main.c b/test/bacnet/basic/sys/lighting_command/src/main.c index e5e584f9..f964b476 100644 --- a/test/bacnet/basic/sys/lighting_command/src/main.c +++ b/test/bacnet/basic/sys/lighting_command/src/main.c @@ -267,7 +267,9 @@ static void test_lighting_command_unit(void) zassert_true(is_float_equal(data.Last_On_Value, 1.0f), NULL); lighting_command_timer(&data, milliseconds); zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); - zassert_true(is_float_equal(Tracking_Value, data.Low_Trim_Value), NULL); + zassert_true( + is_float_equal(Tracking_Value, data.Low_Trim_Value), + "Tracking_Value=%f Low_Trim=%f", Tracking_Value, data.Low_Trim_Value); target_level = 0.0f; milliseconds = 10; lighting_command_fade_to(&data, target_level, 0); @@ -287,13 +289,60 @@ static void test_lighting_command_unit(void) zassert_true(is_float_equal(Tracking_Value, data.High_Trim_Value), NULL); data.High_Trim_Value = data.Max_Actual_Value; zassert_true(is_float_equal(data.Last_On_Value, target_level), NULL); + /* trim fade time */ + target_level = 80.0f; + milliseconds = 10; + lighting_command_fade_to(&data, target_level, 0); + lighting_command_timer(&data, milliseconds); + zassert_true(is_float_equal(Tracking_Value, target_level), NULL); + data.High_Trim_Value = 90.0f; + data.Trim_Fade_Time = fade_time; + lighting_command_fade_to(&data, 100.0f, fade_time * 2); + milliseconds = fade_time / 4; + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_FADE_ACTIVE, NULL); + zassert_true(is_float_equal(Tracking_Value, 82.5f), NULL); + zassert_equal(data.Trim_Fade_Time, fade_time, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_FADE_ACTIVE, NULL); + zassert_true(is_float_equal(Tracking_Value, 85.0f), NULL); + zassert_equal(data.Trim_Fade_Time, fade_time, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_FADE_ACTIVE, NULL); + zassert_true(is_float_equal(Tracking_Value, 87.5f), NULL); + zassert_equal(data.Trim_Fade_Time, fade_time, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_FADE_ACTIVE, NULL); + zassert_true(is_float_equal(Tracking_Value, 90.0f), NULL); + zassert_equal(data.Trim_Fade_Time, fade_time, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); + zassert_true(is_float_equal(Tracking_Value, 91.875f), NULL); + zassert_equal(data.Trim_Fade_Time, 750, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); + zassert_true( + is_float_equal(Tracking_Value, 93.056f), "Tracking_Value=%f", + Tracking_Value); + zassert_equal(data.Trim_Fade_Time, 500, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); + zassert_true( + is_float_equal(Tracking_Value, 93.264f), "Tracking_Value=%f", + Tracking_Value); + zassert_equal(data.Trim_Fade_Time, 250, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL); + zassert_true(is_float_equal(Tracking_Value, data.High_Trim_Value), NULL); + zassert_equal(data.Trim_Fade_Time, 0, NULL); + lighting_command_timer(&data, milliseconds); + zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, 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_override_set(&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); @@ -301,8 +350,7 @@ static void test_lighting_command_unit(void) 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_override_clear(&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); @@ -310,9 +358,7 @@ static void test_lighting_command_unit(void) 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_override_momentary(&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); @@ -331,6 +377,24 @@ static void test_lighting_command_unit(void) target_step = lighting_command_step_increment_clamp(100.1f); zassert_true(is_float_equal(target_step, 100.0f), NULL); + /* physical range clamping */ + target_level = lighting_command_physical_range_clamp(0.0f); + zassert_true(is_float_equal(target_level, 0.0f), NULL); + target_level = lighting_command_physical_range_clamp(0.5f); + zassert_true(is_float_equal(target_level, 0.0f), NULL); + target_level = lighting_command_physical_range_clamp(0.9f); + zassert_true(is_float_equal(target_level, 0.0f), NULL); + target_level = lighting_command_physical_range_clamp(1.0f); + zassert_true(is_float_equal(target_level, 1.0f), NULL); + target_level = lighting_command_physical_range_clamp(50.0f); + zassert_true(is_float_equal(target_level, 50.0f), NULL); + target_level = lighting_command_physical_range_clamp(100.0f); + zassert_true(is_float_equal(target_level, 100.0f), NULL); + target_level = lighting_command_physical_range_clamp(100.1f); + zassert_true(is_float_equal(target_level, 100.0f), NULL); + target_level = lighting_command_physical_range_clamp(150.0f); + zassert_true(is_float_equal(target_level, 100.0f), NULL); + /* step UP - inhibit ON */ target_step = 1.0f; target_level = 0.0f; @@ -602,7 +666,9 @@ static void test_lighting_command_unit(void) zassert_equal(data.Lighting_Operation, BACNET_LIGHTS_PROPRIETARY_MAX, NULL); /* null check code coverage */ - lighting_command_override(NULL, override_level); + lighting_command_override_set(NULL, override_level); + lighting_command_override_clear(NULL, override_level); + lighting_command_override_momentary(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);