Added end-of-blink callback for warn-off and warn-relinquish lighting commands (#1214)

* Added Blink.Callback and Blink.Priority to support end-of-blink actions for WARN_OFF/WARN_RELINQUISH. The Blink.Callback is invoked when a blink sequence ends or when a new command interrupts a WARN_OFF/WARN_RELINQUISH in progress.

* Added Lighting Output (LO) WARN_OFF/WARN_RELINQUISH post-egress priority array handling using the Blink.Callback
This commit is contained in:
Steve Karg
2026-01-29 16:03:55 -06:00
committed by GitHub
parent 503472fdb5
commit 59218819c1
5 changed files with 216 additions and 64 deletions
+3
View File
@@ -120,6 +120,9 @@ The git repositories are hosted at the following sites:
### Fixed ### Fixed
* Fixed lighting output object lighting-commands for warn-off and
warn-relinquish when an update at the specified priority slot
shall occur after an egress time delay. (#1214)
* Fixed lighting output object lighting-commands for warn-off and * Fixed lighting output object lighting-commands for warn-off and
warn-relinquish when blink-warn notification shall not occur. (#1212) warn-relinquish when blink-warn notification shall not occur. (#1212)
* Fixed timer object task to initiate a write-request at expiration. (#1212) * Fixed timer object task to initiate a write-request at expiration. (#1212)
+52 -13
View File
@@ -610,6 +610,42 @@ Lighting_Command_Warn(struct object_data *pObject, unsigned priority)
} }
} }
/**
* @brief Callback that manipulates the value at the specified priority slot
after a delay of Egress_Time seconds.
* @param object_instance object-instance number of the object
* @param operation BACnet lighting operation
* @param priority BACnet priority array value 1..16
*/
static void Lighting_Command_Blink_Stop(
uint32_t object_instance,
BACNET_LIGHTING_OPERATION operation,
uint8_t priority)
{
struct object_data *pObject;
switch (operation) {
case BACNET_LIGHTS_WARN_OFF:
/* writes the value 0.0% to the specified priority slot
after a delay of Egress_Time seconds. */
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
(void)Present_Value_Set(pObject, 0.0, priority);
}
break;
case BACNET_LIGHTS_WARN_RELINQUISH:
/* relinquishes the value at the specified priority slot
after a delay of Egress_Time seconds. */
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
(void)Present_Value_Relinquish(pObject, priority);
}
break;
default:
break;
}
}
/** /**
* @brief Set the lighting command if the priority is active * @brief Set the lighting command if the priority is active
* @param object [in] BACnet object instance * @param object [in] BACnet object instance
@@ -619,6 +655,7 @@ static void
Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority) Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority)
{ {
unsigned current_priority; unsigned current_priority;
BACNET_LIGHTING_COMMAND_WARN_DATA blink;
if (!pObject) { if (!pObject) {
return; return;
@@ -637,14 +674,14 @@ Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority)
active priority, or active priority, or
(b) The Present_Value is 0.0%, or (b) The Present_Value is 0.0%, or
(c) Blink_Warn_Enable is FALSE. */ (c) Blink_Warn_Enable is FALSE. */
pObject->Lighting_Command.Blink.Duration = memmove(
pObject->Egress_Time_Seconds * 1000UL; &blink, &pObject->Lighting_Command.Blink,
sizeof(BACNET_LIGHTING_COMMAND_WARN_DATA));
blink.Duration = pObject->Egress_Time_Seconds * 1000UL;
blink.Priority = priority;
blink.Callback = Lighting_Command_Blink_Stop;
lighting_command_blink_warn( lighting_command_blink_warn(
&pObject->Lighting_Command, BACNET_LIGHTS_WARN_OFF, &pObject->Lighting_Command, BACNET_LIGHTS_WARN_OFF, &blink);
&pObject->Lighting_Command.Blink);
/* FIXME: writes the value 0.0% to the specified slot
in the priority array after a delay of Egress_Time seconds. */
Present_Value_Set(pObject, 0.0, priority);
} else { } else {
/* the value 0.0% written at the specified priority immediately */ /* the value 0.0% written at the specified priority immediately */
Present_Value_Set(pObject, 0.0, priority); Present_Value_Set(pObject, 0.0, priority);
@@ -688,6 +725,7 @@ static void
Lighting_Command_Warn_Relinquish(struct object_data *pObject, unsigned priority) Lighting_Command_Warn_Relinquish(struct object_data *pObject, unsigned priority)
{ {
uint8_t current_priority; uint8_t current_priority;
BACNET_LIGHTING_COMMAND_WARN_DATA blink;
if (!pObject) { if (!pObject) {
return; return;
@@ -708,14 +746,15 @@ Lighting_Command_Warn_Relinquish(struct object_data *pObject, unsigned priority)
(b) The Present_Value is 0.0%, or (b) The Present_Value is 0.0%, or
(c) The Present_Value would not evaluate to 0.0% after (c) The Present_Value would not evaluate to 0.0% after
the priority slot is relinquished. */ the priority slot is relinquished. */
pObject->Lighting_Command.Blink.Duration = memmove(
pObject->Egress_Time_Seconds * 1000UL; &blink, &pObject->Lighting_Command.Blink,
sizeof(BACNET_LIGHTING_COMMAND_WARN_DATA));
blink.Duration = pObject->Egress_Time_Seconds * 1000UL;
blink.Priority = priority;
blink.Callback = Lighting_Command_Blink_Stop;
lighting_command_blink_warn( lighting_command_blink_warn(
&pObject->Lighting_Command, BACNET_LIGHTS_WARN_RELINQUISH, &pObject->Lighting_Command, BACNET_LIGHTS_WARN_RELINQUISH,
&pObject->Lighting_Command.Blink); &blink);
/* FIXME: relinquishes the value at the specified priority slot
after a delay of Egress_Time seconds.*/
Present_Value_Relinquish(pObject, priority);
} else { } else {
/* the value at the specified priority shall be /* the value at the specified priority shall be
relinquished immediately */ relinquished immediately */
+62 -2
View File
@@ -63,8 +63,7 @@ void lighting_command_notification_add(
/** /**
* @brief call the lighting command tracking value callbacks * @brief call the lighting command tracking value callbacks
* @param data - dimmer data structure * @param data - dimmer data structure
* @param old_value - value prior to write * @param milliseconds - number of elapsed milliseconds since the last call
* @param value - value of the write
*/ */
static void lighting_command_timer_notify( static void lighting_command_timer_notify(
struct bacnet_lighting_command_data *data, uint16_t milliseconds) struct bacnet_lighting_command_data *data, uint16_t milliseconds)
@@ -105,6 +104,31 @@ void lighting_command_timer_notfication_add(
} while (head); } while (head);
} }
/**
* @brief call the lighting command blink stop callback
* for WARN_OFF/WARN_RELINQUISH operations
* @param data - dimmer data structure
*/
static void
lighting_command_blink_stop_notify(struct bacnet_lighting_command_data *data)
{
if (data->Blink.Callback) {
/* do some checking to avoid extra callbacks */
switch (data->Lighting_Operation) {
case BACNET_LIGHTS_WARN_OFF:
case BACNET_LIGHTS_WARN_RELINQUISH:
if (data->Blink.Priority != 0) {
data->Blink.Callback(
data->Key, data->Lighting_Operation,
data->Blink.Priority);
}
break;
default:
break;
}
}
}
/** /**
* @brief Clamps the ramp rate value * @brief Clamps the ramp rate value
* @param ramp_rate [in] ramp rate value * @param ramp_rate [in] ramp rate value
@@ -570,6 +594,7 @@ static void lighting_command_blink_handler(
} }
if (data->Blink.Duration == 0) { if (data->Blink.Duration == 0) {
/* 'end' operation */ /* 'end' operation */
lighting_command_blink_stop_notify(data);
data->In_Progress = BACNET_LIGHTING_IDLE; data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP; data->Lighting_Operation = BACNET_LIGHTS_STOP;
target_value = data->Blink.End_Value; target_value = data->Blink.End_Value;
@@ -601,6 +626,7 @@ static void lighting_command_blink_handler(
} }
if (data->Blink.Count == 0) { if (data->Blink.Count == 0) {
/* 'end' operation */ /* 'end' operation */
lighting_command_blink_stop_notify(data);
data->In_Progress = BACNET_LIGHTING_IDLE; data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP; data->Lighting_Operation = BACNET_LIGHTS_STOP;
target_value = data->Blink.End_Value; target_value = data->Blink.End_Value;
@@ -718,6 +744,9 @@ void lighting_command_fade_to(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Fade_Time = fade_time; data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_FADE_TO; data->Lighting_Operation = BACNET_LIGHTS_FADE_TO;
data->Target_Level = value; data->Target_Level = value;
@@ -739,6 +768,9 @@ void lighting_command_ramp_to(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Ramp_Rate = lighting_command_ramp_rate_clamp(ramp_rate); data->Ramp_Rate = lighting_command_ramp_rate_clamp(ramp_rate);
data->Lighting_Operation = BACNET_LIGHTS_RAMP_TO; data->Lighting_Operation = BACNET_LIGHTS_RAMP_TO;
data->Target_Level = value; data->Target_Level = value;
@@ -764,6 +796,9 @@ void lighting_command_step(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
if (((operation == BACNET_LIGHTS_STEP_UP) || if (((operation == BACNET_LIGHTS_STEP_UP) ||
(operation == BACNET_LIGHTS_STEP_DOWN)) && (operation == BACNET_LIGHTS_STEP_DOWN)) &&
(!islessgreater(data->Tracking_Value, 0.0))) { (!islessgreater(data->Tracking_Value, 0.0))) {
@@ -818,9 +853,14 @@ void lighting_command_blink_warn(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the new warning */
data->Lighting_Operation = operation; data->Lighting_Operation = operation;
data->Blink.Target_Interval = blink->Interval; data->Blink.Target_Interval = blink->Interval;
data->Blink.Duration = blink->Duration; data->Blink.Duration = blink->Duration;
data->Blink.Priority = blink->Priority;
data->Blink.Callback = blink->Callback;
data->Blink.Count = blink->Count; data->Blink.Count = blink->Count;
data->Blink.On_Value = blink->On_Value; data->Blink.On_Value = blink->On_Value;
data->Blink.Off_Value = blink->Off_Value; data->Blink.Off_Value = blink->Off_Value;
@@ -842,6 +882,9 @@ void lighting_command_stop(struct bacnet_lighting_command_data *data)
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Lighting_Operation = BACNET_LIGHTS_STOP; data->Lighting_Operation = BACNET_LIGHTS_STOP;
if (isgreaterequal(data->Tracking_Value, 1.0)) { if (isgreaterequal(data->Tracking_Value, 1.0)) {
/* the last value that was greater than or equal to 1.0%.*/ /* the last value that was greater than or equal to 1.0%.*/
@@ -859,6 +902,9 @@ void lighting_command_none(struct bacnet_lighting_command_data *data)
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Lighting_Operation = BACNET_LIGHTS_NONE; data->Lighting_Operation = BACNET_LIGHTS_NONE;
} }
@@ -873,6 +919,9 @@ void lighting_command_restore_on(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Fade_Time = fade_time; data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_RESTORE_ON; data->Lighting_Operation = BACNET_LIGHTS_RESTORE_ON;
data->Target_Level = data->Last_On_Value; data->Target_Level = data->Last_On_Value;
@@ -889,6 +938,9 @@ void lighting_command_default_on(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Fade_Time = fade_time; data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_DEFAULT_ON; data->Lighting_Operation = BACNET_LIGHTS_DEFAULT_ON;
data->Target_Level = data->Default_On_Value; data->Target_Level = data->Default_On_Value;
@@ -905,6 +957,9 @@ void lighting_command_toggle_restore(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Fade_Time = fade_time; data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_RESTORE; data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_RESTORE;
if (isless(data->Tracking_Value, 1.0f)) { if (isless(data->Tracking_Value, 1.0f)) {
@@ -927,6 +982,9 @@ void lighting_command_toggle_default(
if (!data) { if (!data) {
return; return;
} }
/* possibly interrupting a blink warn, so notify */
lighting_command_blink_stop_notify(data);
/* configure the lighting operation */
data->Fade_Time = fade_time; data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_DEFAULT; data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_DEFAULT;
if (isless(data->Tracking_Value, 1.0f)) { if (isless(data->Tracking_Value, 1.0f)) {
@@ -962,6 +1020,8 @@ void lighting_command_init(struct bacnet_lighting_command_data *data)
data->Blink.Interval = 0; data->Blink.Interval = 0;
data->Blink.Duration = 0; data->Blink.Duration = 0;
data->Blink.State = false; data->Blink.State = false;
data->Blink.Callback = NULL;
data->Blink.State = false;
data->Notification_Head.next = NULL; data->Notification_Head.next = NULL;
data->Notification_Head.callback = NULL; data->Notification_Head.callback = NULL;
} }
+12
View File
@@ -41,11 +41,23 @@ struct lighting_command_timer_notification {
lighting_command_timer_callback callback; lighting_command_timer_callback callback;
}; };
/**
* @brief Callback that manipulates the value at the specified priority slot
after a delay of Egress_Time seconds.
* @param object_instance object-instance number of the object
* @param operation BACnet lighting operation
* @param priority BACnet priority array value 1..16
*/
typedef void (*lighting_command_blink_callback)(
uint32_t key, BACNET_LIGHTING_OPERATION operation, uint8_t priority);
typedef struct bacnet_lighting_command_warn_data { typedef struct bacnet_lighting_command_warn_data {
/* warn */ /* warn */
float On_Value; float On_Value;
float Off_Value; float Off_Value;
float End_Value; float End_Value;
uint8_t Priority;
lighting_command_blink_callback Callback;
uint16_t Target_Interval; uint16_t Target_Interval;
/* internal tracking */ /* internal tracking */
uint16_t Interval; uint16_t Interval;
@@ -61,6 +61,25 @@ static bool is_float_equal(float x1, float x2)
return fabs(x1 - x2) < 0.001; return fabs(x1 - x2) < 0.001;
} }
static uint32_t Test_Blink_End_Key;
static BACNET_LIGHTING_OPERATION Test_Blink_End_Operation;
static uint8_t Test_Blink_End_Priority;
/**
* @brief Callback that manipulates the value at the specified priority slot
after a delay of Egress_Time seconds.
* @param object_instance object-instance number of the object
* @param operation BACnet lighting operation
* @param priority BACnet priority array value 1..16
*/
static void test_blink_end(
uint32_t key, BACNET_LIGHTING_OPERATION operation, uint8_t priority)
{
Test_Blink_End_Key = key;
Test_Blink_End_Operation = operation;
Test_Blink_End_Priority = priority;
}
/** /**
* @brief test dimmer blink handler * @brief test dimmer blink handler
* @param data - dimmer data * @param data - dimmer data
@@ -72,64 +91,83 @@ static void test_lighting_command_blink_unit(BACNET_LIGHTING_COMMAND_DATA *data)
uint16_t milliseconds = 10; uint16_t milliseconds = 10;
uint32_t duration = 0; uint32_t duration = 0;
BACNET_LIGHTING_COMMAND_WARN_DATA *blink = &data->Blink; BACNET_LIGHTING_COMMAND_WARN_DATA *blink = &data->Blink;
BACNET_LIGHTING_OPERATION operation[] = { BACNET_LIGHTS_WARN,
BACNET_LIGHTS_WARN_OFF,
BACNET_LIGHTS_WARN_RELINQUISH };
unsigned i;
lighting_command_fade_to(data, data->Max_Actual_Value, 0); lighting_command_fade_to(data, data->Max_Actual_Value, 0);
lighting_command_timer(data, milliseconds); lighting_command_timer(data, milliseconds);
zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL); zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL);
zassert_true(is_float_equal(Tracking_Value, data->Max_Actual_Value), NULL); zassert_true(is_float_equal(Tracking_Value, data->Max_Actual_Value), NULL);
if (blink->Duration == 0) { for (i = 0; i < ARRAY_SIZE(operation); i++) {
/* immediate */ /* blink warn - common */
lighting_command_blink_warn(data, BACNET_LIGHTS_WARN, blink); data->Blink.Callback = test_blink_end;
lighting_command_timer(data, milliseconds); data->Blink.Priority = 8;
zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL); Test_Blink_End_Priority = 0;
zassert_true(is_float_equal(Tracking_Value, blink->End_Value), NULL); /* special cases */
} else if (blink->Interval == 0) { if (blink->Duration == 0) {
/* no blink, just egress timing */ /* immediate */
lighting_command_blink_warn(data, BACNET_LIGHTS_WARN, blink); lighting_command_blink_warn(data, operation[i], 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); lighting_command_timer(data, milliseconds);
if (blink->Duration) { zassert_true(data->In_Progress == BACNET_LIGHTING_IDLE, NULL);
zassert_true( zassert_true(
data->In_Progress == BACNET_LIGHTING_OTHER, is_float_equal(Tracking_Value, blink->End_Value), NULL);
"In_Progress=%d", data->In_Progress); } else if (blink->Interval == 0) {
if (data->Blink.State) { /* no blink, just egress timing */
zassert_true( lighting_command_blink_warn(data, operation[i], blink);
is_float_equal(Tracking_Value, blink->Off_Value), lighting_command_timer(data, milliseconds);
"Tracking_Value=%f", Tracking_Value); zassert_true(data->In_Progress == BACNET_LIGHTING_OTHER, NULL);
} else { zassert_true(is_float_equal(Tracking_Value, blink->On_Value), NULL);
zassert_true( milliseconds = blink->Duration;
is_float_equal(Tracking_Value, blink->On_Value), lighting_command_blink_warn(data, operation[i], blink);
"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, 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 { } else {
zassert_true( duration = blink->Duration;
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); milliseconds = blink->Interval;
do {
lighting_command_blink_warn(data, operation[i], 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);
}
if (operation[i] != BACNET_LIGHTS_WARN) {
/* callback was called */
zassert_equal(Test_Blink_End_Priority, blink->Priority, NULL);
zassert_equal(Test_Blink_End_Operation, operation[i], NULL);
zassert_equal(Test_Blink_End_Key, data->Key, NULL);
}
} }
} }