Files
bacnet_stack/src/bacnet/basic/sys/lighting_command.c
T

686 lines
22 KiB
C

/**
* @file
* @brief dimming brightness engine based on lighting commands
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include "linear.h"
#include "debug.h"
/* me! */
#include "lighting_command.h"
/**
* @brief call the lighting command tracking value callbacks
* @param data - dimmer data
* @param old_value - value prior to write
* @param value - value of the write
*/
static void lighting_command_tracking_value_handler(
struct bacnet_lighting_command_data *data, float old_value, float value)
{
struct lighting_command_notification *head;
head = &data->Notification_Head;
do {
if (head->callback) {
head->callback(data->Key, old_value, value);
}
head = head->next;
} while (head);
}
/**
* @brief Add a Lighting Command notification callback
* @param cb - notification callback to be added
*/
void lighting_command_notification_add(
struct bacnet_lighting_command_data *data,
struct lighting_command_notification *notification)
{
struct lighting_command_notification *head;
head = &data->Notification_Head;
do {
if (head->next == notification) {
/* already here! */
break;
} else if (!head->next) {
/* first available free node */
head->next = notification;
break;
}
head = head->next;
} while (head);
}
/**
* @brief 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->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;
}
lighting_command_tracking_value_handler(data, old_value, value);
} else {
debug_printf(
"Lighting-Command[%lu]-Out-of-Service\n", (unsigned long)data->Key);
}
}
/**
* 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. <target-level> 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->Notification_Head.next = NULL;
data->Notification_Head.callback = NULL;
}