diff --git a/CHANGELOG.md b/CHANGELOG.md
index 365a4c5d..942fd6fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,8 @@ The git repositories are hosted at the following sites:
### Added
+* Added a basic creatable Loop object with PID control. Integrated into
+ the basic device object and server examples. (#1141)
* Added defines for lighting output object present-value special values. (#1137)
* Added get copy API to timer object for state-change-value (#1134)
* Added Audit Log and Time Value objects to basic device and builds. (#1128)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93b7339c..09b70141 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -387,6 +387,8 @@ add_library(${PROJECT_NAME}
src/bacnet/basic/object/lc.h
src/bacnet/basic/object/lo.c
src/bacnet/basic/object/lo.h
+ src/bacnet/basic/object/loop.c
+ src/bacnet/basic/object/loop.h
src/bacnet/basic/object/lsp.c
src/bacnet/basic/object/lsp.h
src/bacnet/basic/object/lsz.c
diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile
index 8f5dc063..5c23dc9c 100644
--- a/apps/gateway/Makefile
+++ b/apps/gateway/Makefile
@@ -29,6 +29,7 @@ BACNET_OBJECT_SRC := \
$(BACNET_OBJECT_DIR)/iv.c \
$(BACNET_OBJECT_DIR)/lc.c \
$(BACNET_OBJECT_DIR)/lo.c \
+ $(BACNET_OBJECT_DIR)/loop.c \
$(BACNET_OBJECT_DIR)/lsp.c \
$(BACNET_OBJECT_DIR)/lsz.c \
$(BACNET_OBJECT_DIR)/ms-input.c \
diff --git a/apps/server-basic/Makefile b/apps/server-basic/Makefile
index 6956bd64..584dab2b 100644
--- a/apps/server-basic/Makefile
+++ b/apps/server-basic/Makefile
@@ -31,6 +31,7 @@ SRC = main.c \
$(BACNET_OBJECT_DIR)/iv.c \
$(BACNET_OBJECT_DIR)/lc.c \
$(BACNET_OBJECT_DIR)/lo.c \
+ $(BACNET_OBJECT_DIR)/loop.c \
$(BACNET_OBJECT_DIR)/lsp.c \
$(BACNET_OBJECT_DIR)/lsz.c \
$(BACNET_OBJECT_DIR)/ms-input.c \
diff --git a/apps/server-mini/Makefile b/apps/server-mini/Makefile
index e8423b71..ca3bea72 100644
--- a/apps/server-mini/Makefile
+++ b/apps/server-mini/Makefile
@@ -26,6 +26,7 @@ SRC = main.c \
$(BACNET_OBJECT_DIR)/iv.c \
$(BACNET_OBJECT_DIR)/lc.c \
$(BACNET_OBJECT_DIR)/lo.c \
+ $(BACNET_OBJECT_DIR)/loop.c \
$(BACNET_OBJECT_DIR)/lsp.c \
$(BACNET_OBJECT_DIR)/lsz.c \
$(BACNET_OBJECT_DIR)/ms-input.c \
diff --git a/apps/server/Makefile b/apps/server/Makefile
index f6e1ce91..3646f7d2 100644
--- a/apps/server/Makefile
+++ b/apps/server/Makefile
@@ -33,6 +33,7 @@ SRC = main.c \
$(BACNET_OBJECT_DIR)/iv.c \
$(BACNET_OBJECT_DIR)/lc.c \
$(BACNET_OBJECT_DIR)/lo.c \
+ $(BACNET_OBJECT_DIR)/loop.c \
$(BACNET_OBJECT_DIR)/lsp.c \
$(BACNET_OBJECT_DIR)/lsz.c \
$(BACNET_OBJECT_DIR)/ms-input.c \
diff --git a/doc/htdocs/index.html b/doc/htdocs/index.html
index 49f84601..c4156ec9 100644
--- a/doc/htdocs/index.html
+++ b/doc/htdocs/index.html
@@ -786,9 +786,11 @@
"top">Yes
|
+
diff --git a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters
index 1a04927e..35e31c30 100644
--- a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters
+++ b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters
@@ -57,6 +57,9 @@
Source Files
+
+ Source Files
+
Source Files
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 4f338c35..63ccd5bf 100644
--- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj
+++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj
@@ -74,6 +74,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 d7d6c8dd..b4ef6fe8 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
@@ -363,6 +363,9 @@
Source Files\src\bacnet\basic\object
+
+ Source Files\src\bacnet\basic\object
+
Source Files\src\bacnet\basic\object
diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c
index 5c0316ed..6a92729d 100644
--- a/src/bacnet/bacapp.c
+++ b/src/bacnet/bacapp.c
@@ -1232,6 +1232,7 @@ int bacapp_known_property_tag(
case PROP_MANIPULATED_VARIABLE_REFERENCE:
case PROP_CONTROLLED_VARIABLE_REFERENCE:
+ case PROP_SETPOINT_REFERENCE:
case PROP_INPUT_REFERENCE:
case PROP_EVENT_ALGORITHM_INHIBIT_REF:
/* Properties using BACnetObjectPropertyReference */
@@ -1321,8 +1322,11 @@ int bacapp_known_property_tag(
/* BACnetLogRecord */
return BACNET_APPLICATION_TAG_LOG_RECORD;
case PROP_ACTION:
- /* BACnetActionCommand */
- return BACNET_APPLICATION_TAG_ACTION_COMMAND;
+ if (object_type == OBJECT_COMMAND) {
+ /* BACnetActionCommand */
+ return BACNET_APPLICATION_TAG_ACTION_COMMAND;
+ }
+ return -1;
case PROP_SCALE:
/* BACnetScale */
return BACNET_APPLICATION_TAG_SCALE;
diff --git a/src/bacnet/bacdef.h b/src/bacnet/bacdef.h
index de5c5471..aa35e05b 100644
--- a/src/bacnet/bacdef.h
+++ b/src/bacnet/bacdef.h
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
/* config is always first to allow developers to override */
#include "bacnet/config.h"
/* BACnet Stack core enumerations */
diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h
index a8595770..7dfef32c 100644
--- a/src/bacnet/bacenum.h
+++ b/src/bacnet/bacenum.h
@@ -599,7 +599,8 @@ typedef enum {
typedef enum BACnetAction {
ACTION_DIRECT = 0,
- ACTION_REVERSE = 1
+ ACTION_REVERSE = 1,
+ BACNET_ACTION_MAX = 2
} BACNET_ACTION;
typedef enum BACnetBinaryPV {
diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c
index 5628ae04..6d4e42d2 100644
--- a/src/bacnet/basic/object/device.c
+++ b/src/bacnet/basic/object/device.c
@@ -57,6 +57,7 @@
#include "bacnet/basic/object/iv.h"
#include "bacnet/basic/object/osv.h"
#include "bacnet/basic/object/piv.h"
+#include "bacnet/basic/object/loop.h"
#include "bacnet/basic/object/time_value.h"
#include "bacnet/basic/object/timer.h"
#include "bacnet/basic/object/channel.h"
@@ -386,6 +387,14 @@ static object_functions_t My_Object_Table[] = {
NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */,
NULL /* Add_List_Element */, NULL /* Remove_List_Element */,
NULL /* Create */, NULL /* Delete */, NULL /* Timer */ },
+ { OBJECT_LOOP, Loop_Init, Loop_Count,
+ Loop_Index_To_Instance, Loop_Valid_Instance,
+ Loop_Object_Name, Loop_Read_Property,
+ Loop_Write_Property, Loop_Property_Lists,
+ NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */,
+ NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */,
+ NULL /* Add_List_Element */, NULL /* Remove_List_Element */,
+ Loop_Create, Loop_Delete, Loop_Timer },
{ OBJECT_PROGRAM, Program_Init, Program_Count,
Program_Index_To_Instance, Program_Valid_Instance,
Program_Object_Name, Program_Read_Property,
diff --git a/src/bacnet/basic/object/loop.c b/src/bacnet/basic/object/loop.c
new file mode 100644
index 00000000..2e766d37
--- /dev/null
+++ b/src/bacnet/basic/object/loop.c
@@ -0,0 +1,2269 @@
+/**
+ * @file
+ * @brief The Loop object type defines a standardized object whose
+ * properties represent the externally visible characteristics of
+ * any form of feedback control loop. Flexibility is achieved by
+ * providing three independent gain constants with no assumed
+ * values for units. The appropriate gain units are determined
+ * by the details of the control algorithm, which is a local matter.
+ * @author Steve Karg
+ * @date November 2025
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#include
+#include
+#include
+#include
+#include
+
+/* BACnet Stack defines - first */
+#include "bacnet/bacdef.h"
+/* BACnet Stack API */
+#include "bacnet/bacdcode.h"
+#include "bacnet/bacapp.h"
+#include "bacnet/bactext.h"
+#include "bacnet/datetime.h"
+#include "bacnet/proplist.h"
+#include "bacnet/timer_value.h"
+/* basic objects and services */
+#include "bacnet/basic/object/device.h"
+#include "bacnet/basic/services.h"
+#include "bacnet/basic/sys/debug.h"
+#include "bacnet/basic/sys/keylist.h"
+/* me! */
+#include "bacnet/basic/object/loop.h"
+
+/* Key List for storing the object data sorted by instance number */
+static OS_Keylist Object_List = NULL;
+/* common object type */
+static const BACNET_OBJECT_TYPE Object_Type = OBJECT_LOOP;
+/* handling for manipulated and reference properties */
+static write_property_function Write_Property_Internal_Callback;
+static read_property_function Read_Property_Internal_Callback;
+
+struct object_data {
+ /* internal variables for PID calculations */
+ uint32_t Update_Timer;
+ float Integral_Sum;
+ float Error;
+ /* variables for object properties */
+ uint32_t Update_Interval;
+ float Present_Value;
+ uint16_t Output_Units;
+ BACNET_OBJECT_PROPERTY_REFERENCE Manipulated_Property_Reference;
+ uint16_t Controlled_Variable_Units;
+ float Controlled_Variable_Value;
+ BACNET_OBJECT_PROPERTY_REFERENCE Controlled_Variable_Reference;
+ float Setpoint;
+ BACNET_OBJECT_PROPERTY_REFERENCE Setpoint_Reference;
+ BACNET_ACTION Action;
+ float Proportional_Constant;
+ uint16_t Proportional_Constant_Units;
+ float Integral_Constant;
+ uint16_t Integral_Constant_Units;
+ float Derivative_Constant;
+ uint16_t Derivative_Constant_Units;
+ float Bias;
+ float Maximum_Output;
+ float Minimum_Output;
+ float COV_Increment;
+ uint8_t Priority_For_Writing;
+ const char *Description;
+ const char *Object_Name;
+ BACNET_RELIABILITY Reliability;
+ bool Out_Of_Service : 1;
+ bool Changed : 1;
+ void *Context;
+};
+
+/* These three arrays are used by the ReadPropertyMultiple handler */
+static const int Properties_Required[] = {
+ /* unordered list of required properties */
+ PROP_OBJECT_IDENTIFIER,
+ PROP_OBJECT_NAME,
+ PROP_OBJECT_TYPE,
+ PROP_PRESENT_VALUE,
+ PROP_STATUS_FLAGS,
+ PROP_EVENT_STATE,
+ PROP_OUT_OF_SERVICE,
+ PROP_OUTPUT_UNITS,
+ PROP_MANIPULATED_VARIABLE_REFERENCE,
+ PROP_CONTROLLED_VARIABLE_REFERENCE,
+ PROP_CONTROLLED_VARIABLE_VALUE,
+ PROP_CONTROLLED_VARIABLE_UNITS,
+ PROP_SETPOINT_REFERENCE,
+ PROP_SETPOINT,
+ PROP_ACTION,
+ PROP_PRIORITY_FOR_WRITING,
+ -1
+};
+
+static const int Properties_Optional[] = {
+ /* unordered list of optional properties */
+ PROP_DESCRIPTION,
+ PROP_RELIABILITY,
+ PROP_PROPORTIONAL_CONSTANT,
+ PROP_PROPORTIONAL_CONSTANT_UNITS,
+ PROP_INTEGRAL_CONSTANT,
+ PROP_INTEGRAL_CONSTANT_UNITS,
+ PROP_DERIVATIVE_CONSTANT,
+ PROP_DERIVATIVE_CONSTANT_UNITS,
+ PROP_BIAS,
+ PROP_MAXIMUM_OUTPUT,
+ PROP_MINIMUM_OUTPUT,
+ PROP_COV_INCREMENT,
+ PROP_UPDATE_INTERVAL,
+ -1
+};
+
+/* handling for proprietary properties */
+static const int Properties_Proprietary[] = { -1 };
+static const int *Properties_Proprietary_Extended;
+static write_property_function Write_Property_Proprietary_Callback;
+static read_property_function Read_Property_Proprietary_Callback;
+
+/**
+ * Returns the list of required, optional, and proprietary properties.
+ * Used by ReadPropertyMultiple service.
+ *
+ * @param pRequired - pointer to list of int terminated by -1, of
+ * BACnet required properties for this object.
+ * @param pOptional - pointer to list of int terminated by -1, of
+ * BACnet optional properties for this object.
+ * @param pProprietary - pointer to list of int terminated by -1, of
+ * BACnet proprietary properties for this object.
+ */
+void Loop_Property_Lists(
+ const int **pRequired, const int **pOptional, const int **pProprietary)
+{
+ if (pRequired) {
+ *pRequired = Properties_Required;
+ }
+ if (pOptional) {
+ *pOptional = Properties_Optional;
+ }
+ if (pProprietary) {
+ if (Properties_Proprietary_Extended) {
+ *pProprietary = Properties_Proprietary_Extended;
+ } else {
+ *pProprietary = Properties_Proprietary;
+ }
+ }
+
+ return;
+}
+
+/**
+ * @brief Determine if the property is a member of this object
+ * @param object_property - object-property to be checked
+ * @return true if the property is a member of any of these lists
+ */
+static bool Loop_Property_Lists_Member(int object_property)
+{
+ const int *pRequired;
+ const int *pOptional;
+ const int *pProprietary;
+
+ Loop_Property_Lists(&pRequired, &pOptional, &pProprietary);
+ return property_lists_member(
+ pRequired, pOptional, pProprietary, object_property);
+}
+
+/**
+ * @brief Set a list of proprietary properties.
+ * Used by ReadProperty/WriteProperty and Multiple services.
+ * @param pProprietary - pointer to list of int terminated by -1, of
+ * BACnet proprietary properties for this object.
+ */
+void Loop_Proprietary_Property_List_Set(const int *pProprietary)
+{
+ Properties_Proprietary_Extended = pProprietary;
+}
+
+/**
+ * @brief Sets a callback used when the object supports proprietary properties.
+ * @param cb - callback used to provide proprietary properties service handling.
+ */
+void Loop_Read_Property_Proprietary_Callback_Set(read_property_function cb)
+{
+ Read_Property_Proprietary_Callback = cb;
+}
+
+/**
+ * @brief Sets a callback used when the object supports proprietary properties.
+ * @param cb - callback used to provide proprietary properties service handling.
+ */
+void Loop_Write_Property_Proprietary_Callback_Set(write_property_function cb)
+{
+ Write_Property_Proprietary_Callback = cb;
+}
+
+/**
+ * @brief Gets an object from the list using an instance number as the key
+ * @param object_instance - object-instance number of the object
+ * @return object found in the list, or NULL if not found
+ */
+static struct object_data *Object_Data(uint32_t object_instance)
+{
+ return Keylist_Data(Object_List, object_instance);
+}
+
+/**
+ * Determines if a given Loop instance is valid
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return true if the instance is valid, and false if not
+ */
+bool Loop_Valid_Instance(uint32_t object_instance)
+{
+ struct object_data *pObject = Object_Data(object_instance);
+
+ return (pObject != NULL);
+}
+
+/**
+ * Determines the number of Loop objects
+ *
+ * @return Number of Loop objects
+ */
+unsigned Loop_Count(void)
+{
+ return Keylist_Count(Object_List);
+}
+
+/**
+ * Determines the object instance-number for a given 0..N index
+ * of Loop objects where N is Loop_Count().
+ *
+ * @param index - 0..MAX_PROGRAMS value
+ *
+ * @return object instance-number for the given index
+ */
+uint32_t Loop_Index_To_Instance(unsigned index)
+{
+ KEY key = UINT32_MAX;
+
+ Keylist_Index_Key(Object_List, index, &key);
+
+ return key;
+}
+
+/**
+ * For a given object instance-number, determines a 0..N index
+ * of Loop objects where N is Loop_Count().
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return index for the given instance-number, or MAX_PROGRAMS
+ * if not valid.
+ */
+unsigned Loop_Instance_To_Index(uint32_t object_instance)
+{
+ return Keylist_Index(Object_List, object_instance);
+}
+
+/**
+ * For a given object instance-number, loads the object-name into
+ * a characterstring. Note that the object name must be unique
+ * within this device.
+ *
+ * @param object_instance - object-instance number of the object
+ * @param object_name - holds the object-name retrieved
+ *
+ * @return true if object-name was retrieved
+ */
+bool Loop_Object_Name(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
+{
+ char text[32] = "";
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Object_Name) {
+ status =
+ characterstring_init_ansi(object_name, pObject->Object_Name);
+ } else {
+ snprintf(
+ text, sizeof(text), "LOOP-%lu", (unsigned long)object_instance);
+ status = characterstring_init_ansi(object_name, text);
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, sets the object-name
+ * @param object_instance - object-instance number of the object
+ * @param new_name - holds the object-name to be set
+ * @return true if object-name was set
+ */
+bool Loop_Name_Set(uint32_t object_instance, const char *new_name)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ status = true;
+ pObject->Object_Name = new_name;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Return the object name C string
+ * @param object_instance [in] BACnet object instance number
+ * @return object name or NULL if not found
+ */
+const char *Loop_Name_ASCII(uint32_t object_instance)
+{
+ const char *name = NULL;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ name = pObject->Object_Name;
+ }
+
+ return name;
+}
+
+/**
+ * For a given object instance-number, return the description.
+ * @param object_instance - object-instance number of the object
+ * @param description - description pointer
+ * @return true/false
+ */
+bool Loop_Description(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *description)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Description) {
+ status =
+ characterstring_init_ansi(description, pObject->Description);
+ } else {
+ status = characterstring_init_ansi(description, "");
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, sets the description
+ * @param object_instance - object-instance number of the object
+ * @param new_name - holds the description to be set
+ * @return true if string was set
+ */
+bool Loop_Description_Set(uint32_t object_instance, const char *new_name)
+{
+ bool status = false; /* return value */
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ status = true;
+ pObject->Description = new_name;
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, returns the description
+ * @param object_instance - object-instance number of the object
+ * @return description text or NULL if not found
+ */
+const char *Loop_Description_ANSI(uint32_t object_instance)
+{
+ const char *name = NULL;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Description == NULL) {
+ name = "";
+ } else {
+ name = pObject->Description;
+ }
+ }
+
+ return name;
+}
+
+/**
+ * For a given object instance-number, returns the out-of-service
+ * property value
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return out-of-service property value
+ */
+bool Loop_Out_Of_Service(uint32_t object_instance)
+{
+ struct object_data *pObject;
+ bool value = false;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Out_Of_Service;
+ }
+
+ return value;
+}
+
+/**
+ * For a given object instance-number, sets the out-of-service property value
+ *
+ * @param object_instance - object-instance number of the object
+ * @param value - boolean out-of-service value
+ *
+ * @return true if the out-of-service property value was set
+ */
+void Loop_Out_Of_Service_Set(uint32_t object_instance, bool value)
+{
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Out_Of_Service = value;
+ }
+}
+
+/**
+ * @brief For a given object instance-number, gets the reliability.
+ * @param object_instance - object-instance number of the object
+ * @return reliability value
+ */
+BACNET_RELIABILITY Loop_Reliability(uint32_t object_instance)
+{
+ BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ reliability = pObject->Reliability;
+ }
+
+ return reliability;
+}
+
+/**
+ * @brief For a given object instance-number, gets the Fault status flag
+ * @param object_instance - object-instance number of the object
+ * @return true the status flag is in Fault
+ */
+static bool Loop_Fault(uint32_t object_instance)
+{
+ struct object_data *pObject;
+ bool fault = false;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (pObject->Reliability != RELIABILITY_NO_FAULT_DETECTED) {
+ fault = true;
+ }
+ }
+
+ return fault;
+}
+
+/**
+ * @brief For a given object instance-number, sets the reliability
+ * @param object_instance - object-instance number of the object
+ * @param value - reliability enumerated value
+ * @return true if values are within range and property is set.
+ */
+bool Loop_Reliability_Set(uint32_t object_instance, BACNET_RELIABILITY value)
+{
+ struct object_data *pObject;
+ bool status = false;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (value <= 255) {
+ pObject->Reliability = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property indicates the current output value
+ * of the loop algorithm in units of the Output_Units property.
+ * @param object_instance [in] BACnet object instance number
+ * @return the present-value for a specific object instance
+ */
+float Loop_Present_Value(uint32_t object_instance)
+{
+ float value = 0.0f;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value = pObject->Present_Value;
+ }
+
+ return value;
+}
+
+/**
+ * @brief This property sets the current output value
+ * of the loop algorithm in units of the Output_Units property.
+ * The Present_Value property shall be writable when Out_Of_Service is TRUE.
+ * @param object_instance [in] BACnet object instance number
+ * @return true if the present-value for a specific object instance was set
+ */
+bool Loop_Present_Value_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Present_Value = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property, of type Unsigned, indicates the interval
+ * in milliseconds at which the loop algorithm updates the output
+ * (Present_Value property).
+ * @param object_instance [in] BACnet object instance number
+ * @return the update-interval for a specific object instance
+ */
+uint32_t Loop_Update_Interval(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value = pObject->Update_Interval;
+ }
+
+ return value;
+}
+
+/**
+ * @brief This property, of type Unsigned, sets the interval
+ * in milliseconds at which the loop algorithm updates the output
+ * (Present_Value property).
+ * @param object_instance [in] BACnet object instance number
+ * @param value [in] the update-interval for a specific object instance
+ * @return true if the update-interval for a specific object instance was set
+ */
+bool Loop_Update_Interval_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ pObject->Update_Interval = value;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, returns the output-units property value
+ * @param object_instance - object-instance number of the object
+ * @return output-units property value
+ */
+uint16_t Loop_Output_Units(uint32_t object_instance)
+{
+ uint16_t units = UNITS_NO_UNITS;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ units = pObject->Output_Units;
+ }
+
+ return units;
+}
+
+/**
+ * For a given object instance-number, sets the output-units property value
+ * @param object_instance - object-instance number of the object
+ * @param units - units property value
+ * @return true if the output-units property value was set
+ */
+bool Loop_Output_Units_Set(uint32_t object_instance, uint16_t units)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ pObject->Output_Units = units;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, determines if the reference is empty
+ * A BACnetObjectPropertyReference containing an object instance number
+ * equal to 4194303 is considered to be 'empty' or 'uninitialized'.
+ * @param value - object property reference
+ * @return true if the reference is empty
+ */
+static bool
+Object_Property_Reference_Empty(const BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+
+ if (value) {
+ if (value->object_identifier.instance == BACNET_MAX_INSTANCE) {
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, sets the reference to be empty
+ * A BACnetObjectPropertyReference containing an object instance number
+ * equal to 4194303 is considered to be 'empty' or 'uninitialized'.
+ * @param value - object property reference
+ * @return true if the reference is empty
+ */
+static void
+Object_Property_Reference_Set_Empty(BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ if (value) {
+ value->object_identifier.instance = BACNET_MAX_INSTANCE;
+ }
+}
+
+/**
+ * @brief This property is of type BACnetObjectPropertyReference.
+ * The output (Present_Value) of the control loop is written to the
+ * object and property designated by the Manipulated_Variable_Reference.
+ * It is normally the Present_Value of an Analog Output object
+ * used to position a device, but it could also be another object
+ * or property, such as that used to stage multiple Binary Outputs.
+ * @param object_instance - object-instance number of the object
+ * @param value - [out] BACnetObjectPropertyReference
+ */
+bool Loop_Manipulated_Variable_Reference(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ status = bacnet_object_property_reference_copy(
+ value, &pObject->Manipulated_Property_Reference);
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property is of type BACnetObjectPropertyReference.
+ * The output (Present_Value) of the control loop is written to the
+ * object and property designated by the Manipulated_Variable_Reference.
+ * It is normally the Present_Value of an Analog Output object
+ * used to position a device, but it could also be another object
+ * or property, such as that used to stage multiple Binary Outputs.
+ * @param object_instance - object-instance number of the object
+ * @param value - [in] BACnetObjectPropertyReference
+ * @return true if the property value was set
+ */
+bool Loop_Manipulated_Variable_Reference_Set(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ status = bacnet_object_property_reference_copy(
+ &pObject->Manipulated_Property_Reference, value);
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property is of type BACnetObjectPropertyReference.
+ * The Controlled_Variable_Reference identifies the property used
+ * to set the Controlled_Variable_Value property of the Loop object.
+ * It is normally the Present_Value property of an Analog Input object
+ * used for measuring a process variable, temperature, for example,
+ * but it could also be another object, such as an Analog Value,
+ * which calculates a minimum or maximum of a group of Analog Inputs
+ * for use in discriminator control.
+ * @param object_instance - object-instance number of the object
+ * @param value - [in] BACnetObjectPropertyReference
+ * @return true if the property value was retrieved
+ */
+bool Loop_Controlled_Variable_Reference(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ status = bacnet_object_property_reference_copy(
+ value, &pObject->Controlled_Variable_Reference);
+ }
+
+ return status;
+}
+
+/**
+ * @brief Sets the Controlled_Variable_Reference property value
+ * @param object_instance - object-instance number of the object
+ * @param value - [in] BACnetObjectPropertyReference
+ * @return true if the property value was set
+ */
+bool Loop_Controlled_Variable_Reference_Set(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ status = bacnet_object_property_reference_copy(
+ &pObject->Controlled_Variable_Reference, value);
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property, of type Real, is the value of the property
+ * of the object referenced by the Controlled_Variable_Reference property.
+ * This control loop compares the Controlled_Variable_Value with
+ * the Setpoint to calculate the error.
+ * @param object_instance [in] BACnet object instance number
+ * @return the Controlled_Variable_Value for a specific object instance
+ */
+float Loop_Controlled_Variable_Value(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value = pObject->Controlled_Variable_Value;
+ }
+
+ return value;
+}
+
+/**
+ * @brief This property sets the loop-controlled-variable value
+ * of the loop algorithm in units of the Output_Units property.
+ * The Present_Value property shall be writable when Out_Of_Service is TRUE.
+ * @param object_instance [in] BACnet object instance number
+ * @return true if the present-value for a specific object instance was set
+ */
+bool Loop_Controlled_Variable_Value_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Controlled_Variable_Value = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, returns the
+ * controlled-variable-units property value
+ * @param object_instance - object-instance number of the object
+ * @return output-units property value
+ */
+uint16_t Loop_Controlled_Variable_Units(uint32_t object_instance)
+{
+ uint16_t units = UNITS_NO_UNITS;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ units = pObject->Controlled_Variable_Units;
+ }
+
+ return units;
+}
+
+/**
+ * @brief For a given object instance-number, sets the
+ * controlled-variable-units property value
+ * @param object_instance - object-instance number of the object
+ * @param units - units property value
+ * @return true if the output-units property value was set
+ */
+bool Loop_Controlled_Variable_Units_Set(
+ uint32_t object_instance, uint16_t units)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ pObject->Controlled_Variable_Units = units;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property, of type BACnetSetpointReference, contains zero
+ * or one references. The absence of a reference indicates that the
+ * setpoint for this control loop is fixed and is contained in the
+ * Setpoint property. The presence of a reference indicates that
+ * the property of another object contains the setpoint value used
+ * for this Loop object and the reference specifies that property.
+ * @param object_instance - object-instance number of the object
+ * @param value - [in] BACnetObjectPropertyReference
+ * @return true if the property value was retrieved
+ */
+bool Loop_Setpoint_Reference(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ status = bacnet_object_property_reference_copy(
+ value, &pObject->Setpoint_Reference);
+ }
+
+ return status;
+}
+
+/**
+ * @brief Sets the Setpoint_Reference property value
+ * @param object_instance - object-instance number of the object
+ * @param value - [in] BACnetObjectPropertyReference
+ * @return true if the property value was set
+ */
+bool Loop_Setpoint_Reference_Set(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ status = bacnet_object_property_reference_copy(
+ &pObject->Setpoint_Reference, value);
+ }
+
+ return status;
+}
+
+/**
+ * @brief This property, of type Real, is the value of the loop setpoint
+ * or of the property of the object referenced by the Setpoint_Reference,
+ * expressed in units of the Controlled_Variable_Units property.
+ * @param object_instance [in] BACnet object instance number
+ * @return the Controlled_Variable_Value for a specific object instance
+ */
+float Loop_Setpoint(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value = pObject->Setpoint;
+ }
+
+ return value;
+}
+
+/**
+ * @brief This property sets the setpoint value
+ * @param object_instance [in] BACnet object instance number
+ * @param value [in] property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Setpoint_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Setpoint = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+BACNET_ACTION Loop_Action(uint32_t object_instance)
+{
+ BACNET_ACTION value = 0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value = pObject->Action;
+ }
+
+ return value;
+}
+
+/**
+ * @brief This property sets the action value
+ * @param object_instance [in] BACnet object instance number
+ * @param value [in] property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Action_Set(uint32_t object_instance, BACNET_ACTION value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (value < BACNET_ACTION_MAX) {
+ pObject->Action = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_Proportional_Constant(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Proportional_Constant;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Proportional_Constant_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Proportional_Constant = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, returns the units property value
+ * @param object_instance - object-instance number of the object
+ * @return units property value
+ */
+uint16_t Loop_Proportional_Constant_Units(uint32_t object_instance)
+{
+ uint16_t units = UNITS_NO_UNITS;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ units = pObject->Proportional_Constant_Units;
+ }
+
+ return units;
+}
+
+/**
+ * @brief For a given object instance-number, sets the units property value
+ * @param object_instance - object-instance number of the object
+ * @param units - units property value
+ * @return true if the units property value was set
+ */
+bool Loop_Proportional_Constant_Units_Set(
+ uint32_t object_instance, uint16_t units)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ pObject->Proportional_Constant_Units = units;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_Integral_Constant(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Integral_Constant;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Integral_Constant_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Integral_Constant = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, returns the units property value
+ * @param object_instance - object-instance number of the object
+ * @return units property value
+ */
+uint16_t Loop_Integral_Constant_Units(uint32_t object_instance)
+{
+ uint16_t units = UNITS_NO_UNITS;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ units = pObject->Integral_Constant_Units;
+ }
+
+ return units;
+}
+
+/**
+ * @brief For a given object instance-number, sets the units property value
+ * @param object_instance - object-instance number of the object
+ * @param units - units property value
+ * @return true if the units property value was set
+ */
+bool Loop_Integral_Constant_Units_Set(uint32_t object_instance, uint16_t units)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ pObject->Integral_Constant_Units = units;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_Derivative_Constant(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Derivative_Constant;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Derivative_Constant_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Derivative_Constant = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, returns the units property value
+ * @param object_instance - object-instance number of the object
+ * @return units property value
+ */
+uint16_t Loop_Derivative_Constant_Units(uint32_t object_instance)
+{
+ uint16_t units = UNITS_NO_UNITS;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ units = pObject->Derivative_Constant_Units;
+ }
+
+ return units;
+}
+
+/**
+ * @brief For a given object instance-number, sets the units property value
+ * @param object_instance - object-instance number of the object
+ * @param units - units property value
+ * @return true if the units property value was set
+ */
+bool Loop_Derivative_Constant_Units_Set(
+ uint32_t object_instance, uint16_t units)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ pObject->Derivative_Constant_Units = units;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_Bias(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Bias;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Bias_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Bias = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_Maximum_Output(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Maximum_Output;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Maximum_Output_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Maximum_Output = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_Minimum_Output(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Minimum_Output;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_Minimum_Output_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value)) {
+ pObject->Minimum_Output = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the priority-for-writing property value for a given object
+ * instance
+ * @param object_instance - object-instance number of the object
+ * @return priority-for-writing property value
+ */
+uint8_t Loop_Priority_For_Writing(uint32_t object_instance)
+{
+ uint8_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Priority_For_Writing;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the priority-for-writing property value for a given object
+ * instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the priority-for-writing property value was in range and set
+ */
+bool Loop_Priority_For_Writing_Set(uint32_t object_instance, uint8_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ /* Unsigned(1..16) */
+ if ((value >= BACNET_MIN_PRIORITY) && (value <= BACNET_MAX_PRIORITY)) {
+ pObject->Priority_For_Writing = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return the property value for a given object instance
+ */
+float Loop_COV_Increment(uint32_t object_instance)
+{
+ float value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->COV_Increment;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the property value was set
+ */
+bool Loop_COV_Increment_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (isfinite(value) && isgreater(value, 0.0)) {
+ pObject->COV_Increment = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * ReadProperty handler for this object. For the given ReadProperty
+ * data, the application_data is loaded or the error flags are set.
+ *
+ * @param rpdata - BACNET_READ_PROPERTY_DATA data, including
+ * requested data and space for the reply, or error response.
+ *
+ * @return number of APDU bytes in the response, zero if no data, or
+ * BACNET_STATUS_ERROR on error.
+ */
+int Loop_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
+{
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ BACNET_UNSIGNED_INTEGER unsigned_value;
+ BACNET_OBJECT_PROPERTY_REFERENCE reference_value;
+ float real_value;
+ uint8_t *apdu = NULL;
+
+ uint32_t enum_value = 0;
+ bool state = false;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len = encode_application_object_id(
+ &apdu[0], Object_Type, rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Loop_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len = encode_application_enumerated(&apdu[0], Object_Type);
+ break;
+ case PROP_PRESENT_VALUE:
+ real_value = Loop_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ state = Loop_Fault(rpdata->object_instance);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, state);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ state = Loop_Out_Of_Service(rpdata->object_instance);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = Loop_Out_Of_Service(rpdata->object_instance);
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_OUTPUT_UNITS:
+ enum_value = Loop_Output_Units(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_MANIPULATED_VARIABLE_REFERENCE:
+ Loop_Manipulated_Variable_Reference(
+ rpdata->object_instance, &reference_value);
+ apdu_len =
+ bacapp_encode_obj_property_ref(&apdu[0], &reference_value);
+ break;
+ case PROP_CONTROLLED_VARIABLE_REFERENCE:
+ Loop_Controlled_Variable_Reference(
+ rpdata->object_instance, &reference_value);
+ apdu_len =
+ bacapp_encode_obj_property_ref(&apdu[0], &reference_value);
+ break;
+ case PROP_CONTROLLED_VARIABLE_VALUE:
+ real_value =
+ Loop_Controlled_Variable_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_CONTROLLED_VARIABLE_UNITS:
+ enum_value =
+ Loop_Controlled_Variable_Units(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_SETPOINT_REFERENCE:
+ Loop_Setpoint_Reference(rpdata->object_instance, &reference_value);
+ apdu_len = bacapp_encode_obj_property_ref(apdu, &reference_value);
+ break;
+ case PROP_SETPOINT:
+ real_value = Loop_Setpoint(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_UPDATE_INTERVAL:
+ unsigned_value = Loop_Update_Interval(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_ACTION:
+ enum_value = Loop_Action(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_PRIORITY_FOR_WRITING:
+ unsigned_value = Loop_Priority_For_Writing(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_DESCRIPTION:
+ if (Loop_Description(rpdata->object_instance, &char_string)) {
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ }
+ break;
+ case PROP_RELIABILITY:
+ enum_value = Loop_Reliability(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+
+ case PROP_PROPORTIONAL_CONSTANT:
+ real_value = Loop_Proportional_Constant(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_PROPORTIONAL_CONSTANT_UNITS:
+ enum_value =
+ Loop_Proportional_Constant_Units(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_INTEGRAL_CONSTANT:
+ real_value = Loop_Integral_Constant(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_INTEGRAL_CONSTANT_UNITS:
+ enum_value = Loop_Integral_Constant_Units(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_DERIVATIVE_CONSTANT:
+ real_value = Loop_Derivative_Constant(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_DERIVATIVE_CONSTANT_UNITS:
+ enum_value =
+ Loop_Derivative_Constant_Units(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_BIAS:
+ real_value = Loop_Bias(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_MAXIMUM_OUTPUT:
+ real_value = Loop_Maximum_Output(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_MINIMUM_OUTPUT:
+ real_value = Loop_Minimum_Output(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_COV_INCREMENT:
+ real_value = Loop_COV_Increment(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ default:
+ if (Read_Property_Proprietary_Callback) {
+ apdu_len = Read_Property_Proprietary_Callback(rpdata);
+ } else {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+ break;
+ }
+
+ return apdu_len;
+}
+
+/**
+ * WriteProperty handler for this object. For the given WriteProperty
+ * data, the application_data is loaded or the error flags are set.
+ *
+ * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including
+ * requested data and space for the reply, or error response.
+ *
+ * @return false if an error is loaded, true if no errors
+ */
+bool Loop_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
+{
+ bool status = false; /* return value */
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value = { 0 };
+
+ /* decode the some of the request */
+ len = bacapp_decode_known_array_property(
+ wp_data->application_data, wp_data->application_data_len, &value,
+ wp_data->object_type, wp_data->object_property, wp_data->array_index);
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Present_Value_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
+ if (status) {
+ Loop_Out_Of_Service_Set(
+ wp_data->object_instance, value.type.Boolean);
+ }
+ break;
+ case PROP_ACTION:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
+ if (status) {
+ status = Loop_Action_Set(
+ wp_data->object_instance, value.type.Enumerated);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_UPDATE_INTERVAL:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ status = Loop_Update_Interval_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_OUTPUT_UNITS:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT16_MAX) {
+ status = Loop_Output_Units_Set(
+ wp_data->object_instance, value.type.Enumerated);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_CONTROLLED_VARIABLE_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Controlled_Variable_Value_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_CONTROLLED_VARIABLE_UNITS:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT16_MAX) {
+ status = Loop_Controlled_Variable_Units_Set(
+ wp_data->object_instance, value.type.Enumerated);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_PROPORTIONAL_CONSTANT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Proportional_Constant_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_PROPORTIONAL_CONSTANT_UNITS:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT16_MAX) {
+ status = Loop_Proportional_Constant_Units_Set(
+ wp_data->object_instance, value.type.Enumerated);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_INTEGRAL_CONSTANT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Integral_Constant_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_INTEGRAL_CONSTANT_UNITS:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT16_MAX) {
+ status = Loop_Integral_Constant_Units_Set(
+ wp_data->object_instance, value.type.Enumerated);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_DERIVATIVE_CONSTANT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Derivative_Constant_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_DERIVATIVE_CONSTANT_UNITS:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT16_MAX) {
+ status = Loop_Derivative_Constant_Units_Set(
+ wp_data->object_instance, value.type.Enumerated);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_BIAS:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status =
+ Loop_Bias_Set(wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_SETPOINT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Setpoint_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_MINIMUM_OUTPUT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Minimum_Output_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_MAXIMUM_OUTPUT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_Maximum_Output_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_PRIORITY_FOR_WRITING:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT8_MAX) {
+ status = Loop_Priority_For_Writing_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_MANIPULATED_VARIABLE_REFERENCE:
+ status = write_property_type_valid(
+ wp_data, &value,
+ BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE);
+ if (status) {
+ status = Loop_Manipulated_Variable_Reference_Set(
+ wp_data->object_instance,
+ &value.type.Object_Property_Reference);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_CONTROLLED_VARIABLE_REFERENCE:
+ status = write_property_type_valid(
+ wp_data, &value,
+ BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE);
+ if (status) {
+ status = Loop_Controlled_Variable_Reference_Set(
+ wp_data->object_instance,
+ &value.type.Object_Property_Reference);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_SETPOINT_REFERENCE:
+ status = write_property_type_valid(
+ wp_data, &value,
+ BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE);
+ if (status) {
+ status = Loop_Setpoint_Reference_Set(
+ wp_data->object_instance,
+ &value.type.Object_Property_Reference);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_COV_INCREMENT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Loop_COV_Increment_Set(
+ wp_data->object_instance, value.type.Real);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ default:
+ if (Loop_Property_Lists_Member(wp_data->object_property)) {
+ if (Write_Property_Proprietary_Callback) {
+ status = Write_Property_Proprietary_Callback(wp_data);
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ }
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ }
+ break;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Set the context used with load, unload, run, halt, and restart
+ * @param object_instance [in] BACnet object instance number
+ * @param context [in] pointer to the context
+ */
+void *Loop_Context_Get(uint32_t object_instance)
+{
+ struct object_data *pObject = Object_Data(object_instance);
+
+ if (pObject) {
+ return pObject->Context;
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Set the context used for vendor specific extensions
+ * @param object_instance [in] BACnet object instance number
+ * @param context [in] pointer to the context
+ */
+void Loop_Context_Set(uint32_t object_instance, void *context)
+{
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Context = context;
+ }
+}
+
+/**
+ * @brief Sets a callback used when the loop reads from BACnet Object
+ * reference value
+ * @param cb - callback used to provide indications
+ */
+void Loop_Read_Property_Internal_Callback_Set(read_property_function cb)
+{
+ Read_Property_Internal_Callback = cb;
+}
+
+/**
+ * @brief For a given object, reads a BACnet Object Property reference
+ * @param value - application value
+ * @param priority - BACnet priority 0=none,1..16
+ * @return true if values are within range and value is written.
+ */
+static bool Loop_Read_Variable_Reference_Update(
+ const BACNET_OBJECT_PROPERTY_REFERENCE *reference, float *value)
+{
+ BACNET_READ_PROPERTY_DATA data = { 0 };
+ uint8_t apdu[32] = { 0 };
+ int apdu_len = 0, len = 0;
+ bool status = false;
+
+ if (!Object_Property_Reference_Empty(reference)) {
+ data.object_type = reference->object_identifier.type;
+ data.object_instance = reference->object_identifier.instance;
+ data.object_property = reference->property_identifier;
+ data.array_index = reference->property_array_index;
+ data.application_data = apdu;
+ data.application_data_len = sizeof(apdu);
+ data.error_class = ERROR_CLASS_PROPERTY;
+ data.error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ if (Read_Property_Internal_Callback) {
+ apdu_len = Read_Property_Internal_Callback(&data);
+ }
+ if (apdu_len > 0) {
+ /* expecting only application tagged REAL values */
+ len = bacnet_real_application_decode(apdu, apdu_len, value);
+ if (len > 0) {
+ status = true;
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Sets a callback used when the loop is written from BACnet
+ * @param cb - callback used to provide indications
+ */
+void Loop_Write_Property_Internal_Callback_Set(write_property_function cb)
+{
+ Write_Property_Internal_Callback = cb;
+}
+
+/**
+ * @brief For a given object, writes to the manipulated-variable-reference
+ * @param pObject - object instance data
+ * @param value - application value
+ * @param priority - BACnet priority 0=none,1..16
+ * @return true if values are within range and value is written.
+ */
+static bool Loop_Write_Manipulated_Variable(
+ struct object_data *pObject, float value, uint8_t priority)
+{
+ BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
+ BACNET_OBJECT_PROPERTY_REFERENCE *member;
+ bool status = false;
+
+ if (pObject) {
+ member = &pObject->Manipulated_Property_Reference;
+ if (!Object_Property_Reference_Empty(member)) {
+ wp_data.object_type = member->object_identifier.type;
+ wp_data.object_instance = member->object_identifier.instance;
+ wp_data.object_property = member->property_identifier;
+ wp_data.array_index = member->property_array_index;
+ wp_data.error_class = ERROR_CLASS_PROPERTY;
+ wp_data.error_code = ERROR_CODE_SUCCESS;
+ wp_data.priority = priority;
+ wp_data.application_data_len =
+ encode_application_real(wp_data.application_data, value);
+ if (Write_Property_Internal_Callback) {
+ status = Write_Property_Internal_Callback(&wp_data);
+ if (status) {
+ wp_data.error_code = ERROR_CODE_SUCCESS;
+ }
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief PID algorithm
+ * @param pObject - object instance data
+ * @param elapsed_milliseconds - number of milliseconds elapsed
+ * @return computed PID output value
+ */
+static float
+Loop_PID_Algorithm(struct object_data *pObject, uint32_t elapsed_milliseconds)
+{
+ float output, error, elapsed_seconds;
+ float integral_sum, integral_min, integral_max;
+ float proportional, integral, derivative;
+
+ if (elapsed_milliseconds == 0) {
+ return pObject->Bias;
+ }
+ error = pObject->Setpoint - pObject->Controlled_Variable_Value;
+ if (pObject->Action == ACTION_REVERSE) {
+ /* In reverse action, an increase in the process variable
+ above the setpoint requires a decrease in the controller output
+ to bring the process variable back to the setpoint. */
+ error = -error;
+ }
+ proportional = pObject->Proportional_Constant * error;
+ elapsed_seconds = (float)elapsed_milliseconds;
+ elapsed_seconds /= 1000.0f;
+ integral_sum = error * elapsed_seconds;
+ pObject->Integral_Sum += integral_sum;
+ if (islessgreater(pObject->Integral_Constant, 0.0f)) {
+ /* clamp integral sum to prevent windup */
+ integral_max = pObject->Maximum_Output / pObject->Integral_Constant;
+ if (isgreater(pObject->Integral_Sum, integral_max)) {
+ pObject->Integral_Sum = integral_max;
+ }
+ integral_min = pObject->Minimum_Output / pObject->Integral_Constant;
+ if (isless(pObject->Integral_Sum, integral_min)) {
+ pObject->Integral_Sum = integral_min;
+ }
+ }
+ integral = pObject->Integral_Constant * pObject->Integral_Sum;
+ derivative = pObject->Derivative_Constant *
+ ((error - pObject->Error) / elapsed_seconds);
+ pObject->Error = error;
+ output = proportional + integral + derivative + pObject->Bias;
+ /* clamp the output within limits */
+ if (isgreater(output, pObject->Maximum_Output)) {
+ output = pObject->Maximum_Output;
+ }
+ if (isless(output, pObject->Minimum_Output)) {
+ output = pObject->Minimum_Output;
+ }
+
+ return output;
+}
+
+/**
+ * @brief Updates the object loop operation
+ * @param object_instance - object-instance number of the object
+ * @param elapsed_milliseconds - number of milliseconds elapsed
+ */
+void Loop_Timer(uint32_t object_instance, uint16_t elapsed_milliseconds)
+{
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ /* update any variable references */
+ Loop_Read_Variable_Reference_Update(
+ &pObject->Controlled_Variable_Reference,
+ &pObject->Controlled_Variable_Value);
+ Loop_Read_Variable_Reference_Update(
+ &pObject->Setpoint_Reference, &pObject->Setpoint);
+ /* loop algorithm updates the present-value */
+ if (!pObject->Out_Of_Service) {
+ /* When Out_Of_Service is TRUE:
+ (a) the Present_Value property shall be
+ decoupled from the algorithm;
+ */
+ pObject->Present_Value =
+ Loop_PID_Algorithm(pObject, elapsed_milliseconds);
+ }
+ if (pObject->Update_Interval) {
+ pObject->Update_Timer += elapsed_milliseconds;
+ /* NOTE: No property that represents the interval at which
+ the process variable is sampled or the algorithm is executed
+ is part of this object.
+ The Update_Interval value may be the same as these other values
+ but could also be different depending on the algorithm utilized.
+ The sampling or execution interval is a local matter and need
+ not be represented as part of this object.*/
+ if (pObject->Update_Timer >= pObject->Update_Interval) {
+ pObject->Update_Timer -= pObject->Update_Interval;
+ /* The property referenced by Manipulated_Variable_Reference
+ and other functions that depend on the state of the
+ Present_Value or Reliability properties shall
+ respond to changes made to these properties,
+ as if those changes had been made by the algorithm.*/
+ Loop_Write_Manipulated_Variable(
+ pObject, pObject->Present_Value,
+ pObject->Priority_For_Writing);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Creates a Loop object
+ * @param object_instance - object-instance number of the object
+ * @return the object-instance that was created, or BACNET_MAX_INSTANCE
+ */
+uint32_t Loop_Create(uint32_t object_instance)
+{
+ struct object_data *pObject = NULL;
+ int index;
+
+ if (!Object_List) {
+ Object_List = Keylist_Create();
+ }
+ if (object_instance > BACNET_MAX_INSTANCE) {
+ return BACNET_MAX_INSTANCE;
+ } else if (object_instance == BACNET_MAX_INSTANCE) {
+ /* wildcard instance */
+ /* the Object_Identifier property of the newly created object
+ shall be initialized to a value that is unique within the
+ responding BACnet-user device. The method used to generate
+ the object identifier is a local matter.*/
+ object_instance = Keylist_Next_Empty_Key(Object_List, 1);
+ }
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ /* already exists - signal success but don't change data */
+ return object_instance;
+ }
+ pObject = calloc(1, sizeof(struct object_data));
+ if (!pObject) {
+ /* no RAM available - signal failure */
+ return BACNET_MAX_INSTANCE;
+ }
+ index = Keylist_Data_Add(Object_List, object_instance, pObject);
+ if (index < 0) {
+ /* unable to add to list - signal failure */
+ free(pObject);
+ return BACNET_MAX_INSTANCE;
+ }
+ /* only need to set property values that are non-zero */
+ pObject->Output_Units = UNITS_NO_UNITS;
+ pObject->Controlled_Variable_Units = UNITS_NO_UNITS;
+ pObject->Proportional_Constant_Units = UNITS_NO_UNITS;
+ pObject->Integral_Constant_Units = UNITS_NO_UNITS;
+ pObject->Derivative_Constant_Units = UNITS_NO_UNITS;
+ pObject->Action = ACTION_DIRECT;
+ pObject->Maximum_Output = FLT_MAX;
+ pObject->Minimum_Output = FLT_MIN;
+ Object_Property_Reference_Set_Empty(
+ &pObject->Manipulated_Property_Reference);
+ Object_Property_Reference_Set_Empty(
+ &pObject->Controlled_Variable_Reference);
+ Object_Property_Reference_Set_Empty(&pObject->Setpoint_Reference);
+ pObject->Priority_For_Writing = BACNET_MAX_PRIORITY;
+ pObject->COV_Increment = 1.0f;
+ pObject->Description = NULL;
+ pObject->Object_Name = NULL;
+ pObject->Reliability = RELIABILITY_NO_FAULT_DETECTED;
+ pObject->Out_Of_Service = false;
+ pObject->Changed = false;
+ pObject->Context = NULL;
+
+ return object_instance;
+}
+
+/**
+ * @brief Deletes an object-instance
+ * @param object_instance - object-instance number of the object
+ * @return true if the object-instance was deleted
+ */
+bool Loop_Delete(uint32_t object_instance)
+{
+ bool status = false;
+ struct object_data *pObject =
+ Keylist_Data_Delete(Object_List, object_instance);
+
+ if (pObject) {
+ free(pObject);
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Deletes all the objects and their data
+ */
+void Loop_Cleanup(void)
+{
+ struct object_data *pObject;
+
+ if (Object_List) {
+ do {
+ pObject = Keylist_Data_Pop(Object_List);
+ if (pObject) {
+ free(pObject);
+ }
+ } while (pObject);
+
+ Keylist_Delete(Object_List);
+ Object_List = NULL;
+ }
+}
+
+/**
+ * @brief Returns the approximate size of each Loop object data
+ */
+size_t Loop_Size(void)
+{
+ return sizeof(struct object_data);
+}
+
+/**
+ * Initializes the object data
+ */
+void Loop_Init(void)
+{
+ if (!Object_List) {
+ Object_List = Keylist_Create();
+ }
+}
diff --git a/src/bacnet/basic/object/loop.h b/src/bacnet/basic/object/loop.h
new file mode 100644
index 00000000..9f375bd3
--- /dev/null
+++ b/src/bacnet/basic/object/loop.h
@@ -0,0 +1,214 @@
+/**
+ * @file
+ * @brief API for Loop object type
+ * @author Steve Karg
+ * @date November 2025
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#ifndef BACNET_BASIC_OBJECT_LOOP_H
+#define BACNET_BASIC_OBJECT_LOOP_H
+#include
+#include
+/* BACnet Stack defines - first */
+#include "bacnet/bacdef.h"
+/* BACnet Stack API */
+#include "bacnet/bacerror.h"
+#include "bacnet/timer_value.h"
+#include "bacnet/wp.h"
+#include "bacnet/rp.h"
+#include "bacnet/list_element.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+BACNET_STACK_EXPORT
+void Loop_Property_Lists(
+ const int **pRequired, const int **pOptional, const int **pProprietary);
+BACNET_STACK_EXPORT
+void Loop_Proprietary_Property_List_Set(const int *pProprietary);
+BACNET_STACK_EXPORT
+void Loop_Read_Property_Proprietary_Callback_Set(read_property_function cb);
+BACNET_STACK_EXPORT
+void Loop_Write_Property_Proprietary_Callback_Set(write_property_function cb);
+
+BACNET_STACK_EXPORT
+bool Loop_Valid_Instance(uint32_t object_instance);
+BACNET_STACK_EXPORT
+unsigned Loop_Count(void);
+BACNET_STACK_EXPORT
+uint32_t Loop_Index_To_Instance(unsigned index);
+BACNET_STACK_EXPORT
+unsigned Loop_Instance_To_Index(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+bool Loop_Object_Name(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *object_name);
+BACNET_STACK_EXPORT
+bool Loop_Name_Set(uint32_t object_instance, const char *new_name);
+BACNET_STACK_EXPORT
+const char *Loop_Name_ASCII(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+bool Loop_Description(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *description);
+BACNET_STACK_EXPORT
+bool Loop_Description_Set(uint32_t instance, const char *new_name);
+BACNET_STACK_EXPORT
+const char *Loop_Description_ANSI(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+bool Loop_Out_Of_Service(uint32_t instance);
+BACNET_STACK_EXPORT
+void Loop_Out_Of_Service_Set(uint32_t instance, bool oos_flag);
+
+BACNET_STACK_EXPORT
+BACNET_RELIABILITY Loop_Reliability(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Reliability_Set(uint32_t object_instance, BACNET_RELIABILITY value);
+
+BACNET_STACK_EXPORT
+float Loop_Present_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Present_Value_Set(uint32_t object_instance, float value);
+
+BACNET_STACK_EXPORT
+uint32_t Loop_Update_Interval(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Update_Interval_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+uint16_t Loop_Output_Units(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_Output_Units_Set(uint32_t instance, uint16_t units);
+
+BACNET_STACK_EXPORT
+bool Loop_Manipulated_Variable_Reference(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value);
+BACNET_STACK_EXPORT
+bool Loop_Manipulated_Variable_Reference_Set(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value);
+
+BACNET_STACK_EXPORT
+bool Loop_Controlled_Variable_Reference(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value);
+BACNET_STACK_EXPORT
+bool Loop_Controlled_Variable_Reference_Set(
+ uint32_t object_instance, BACNET_OBJECT_PROPERTY_REFERENCE *value);
+
+BACNET_STACK_EXPORT
+float Loop_Controlled_Variable_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Controlled_Variable_Value_Set(uint32_t object_instance, float value);
+
+BACNET_STACK_EXPORT
+uint16_t Loop_Controlled_Variable_Units(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_Controlled_Variable_Units_Set(uint32_t instance, uint16_t units);
+
+BACNET_STACK_EXPORT
+bool Loop_Setpoint_Reference(
+ uint32_t instance, BACNET_OBJECT_PROPERTY_REFERENCE *value);
+BACNET_STACK_EXPORT
+bool Loop_Setpoint_Reference_Set(
+ uint32_t instance, BACNET_OBJECT_PROPERTY_REFERENCE *value);
+
+BACNET_STACK_EXPORT
+float Loop_Setpoint(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Setpoint_Set(uint32_t object_instance, float value);
+
+BACNET_STACK_EXPORT
+BACNET_ACTION Loop_Action(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Action_Set(uint32_t object_instance, BACNET_ACTION value);
+
+BACNET_STACK_EXPORT
+float Loop_Proportional_Constant(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Proportional_Constant_Set(uint32_t object_instance, float value);
+BACNET_STACK_EXPORT
+uint16_t Loop_Proportional_Constant_Units(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_Proportional_Constant_Units_Set(uint32_t instance, uint16_t units);
+BACNET_STACK_EXPORT
+float Loop_Integral_Constant(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Integral_Constant_Set(uint32_t object_instance, float value);
+BACNET_STACK_EXPORT
+uint16_t Loop_Integral_Constant_Units(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_Integral_Constant_Units_Set(uint32_t instance, uint16_t units);
+BACNET_STACK_EXPORT
+float Loop_Derivative_Constant(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Derivative_Constant_Set(uint32_t object_instance, float value);
+BACNET_STACK_EXPORT
+uint16_t Loop_Derivative_Constant_Units(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_Derivative_Constant_Units_Set(uint32_t instance, uint16_t units);
+
+BACNET_STACK_EXPORT
+float Loop_Bias(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Bias_Set(uint32_t object_instance, float value);
+
+BACNET_STACK_EXPORT
+float Loop_Maximum_Output(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Maximum_Output_Set(uint32_t object_instance, float value);
+
+BACNET_STACK_EXPORT
+float Loop_Minimum_Output(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Minimum_Output_Set(uint32_t object_instance, float value);
+
+BACNET_STACK_EXPORT
+uint8_t Loop_Priority_For_Writing(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Priority_For_Writing_Set(uint32_t object_instance, uint8_t value);
+
+BACNET_STACK_EXPORT
+float Loop_COV_Increment(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_COV_Increment_Set(uint32_t instance, float value);
+
+BACNET_STACK_EXPORT
+bool Loop_Reliability_Evaluation_Inhibit(uint32_t instance);
+BACNET_STACK_EXPORT
+bool Loop_Reliability_Evaluation_Inhibit_Set(uint32_t instance, bool value);
+
+BACNET_STACK_EXPORT
+void Loop_Timer(uint32_t object_instance, uint16_t elapsed_milliseconds);
+
+BACNET_STACK_EXPORT
+void Loop_Write_Property_Internal_Callback_Set(write_property_function cb);
+BACNET_STACK_EXPORT
+void Loop_Read_Property_Internal_Callback_Set(read_property_function cb);
+
+BACNET_STACK_EXPORT
+int Loop_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata);
+BACNET_STACK_EXPORT
+bool Loop_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data);
+
+BACNET_STACK_EXPORT
+uint32_t Loop_Create(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Loop_Delete(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+void Loop_Cleanup(void);
+BACNET_STACK_EXPORT
+size_t Loop_Size(void);
+BACNET_STACK_EXPORT
+void Loop_Init(void);
+
+BACNET_STACK_EXPORT
+void *Loop_Context_Get(uint32_t object_instance);
+BACNET_STACK_EXPORT
+void Loop_Context_Set(uint32_t object_instance, void *context);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c
index 149d8def..5b02e98c 100644
--- a/src/bacnet/basic/server/bacnet_device.c
+++ b/src/bacnet/basic/server/bacnet_device.c
@@ -55,6 +55,7 @@
#include "bacnet/basic/object/iv.h"
#include "bacnet/basic/object/time_value.h"
#include "bacnet/basic/object/timer.h"
+#include "bacnet/basic/object/loop.h"
#include "bacnet/basic/object/channel.h"
#include "bacnet/basic/object/program.h"
#include "bacnet/basic/object/lo.h"
@@ -134,6 +135,7 @@
defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE) || \
defined(CONFIG_BACNET_BASIC_OBJECT_TIME_VALUE) || \
defined(CONFIG_BACNET_BASIC_OBJECT_TIMER) || \
+ defined(CONFIG_BACNET_BASIC_OBJECT_LOOP) || \
defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM) || \
defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE))
#define CONFIG_BACNET_BASIC_OBJECT_ALL
@@ -166,6 +168,7 @@
#define CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_TIME_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_TIMER
+#define CONFIG_BACNET_BASIC_OBJECT_LOOP
#define CONFIG_BACNET_BASIC_OBJECT_PROGRAM
#define CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE
#endif
@@ -828,6 +831,28 @@ static object_functions_t My_Object_Table[] = {
Timer_Delete,
Timer_Task },
#endif
+#if defined(CONFIG_BACNET_BASIC_OBJECT_LOOP)
+ { OBJECT_LOOP,
+ Loop_Init,
+ Loop_Count,
+ Loop_Index_To_Instance,
+ Loop_Valid_Instance,
+ Loop_Object_Name,
+ Loop_Read_Property,
+ Loop_Write_Property,
+ Loop_Property_Lists,
+ NULL /* ReadRangeInfo */,
+ NULL /* Iterator */,
+ NULL /* Value_Lists */,
+ NULL /* COV */,
+ NULL /* COV Clear */,
+ NULL /* Intrinsic Reporting */,
+ NULL /* Add_List_Element */,
+ NULL /* Remove_List_Element */,
+ Loop_Create,
+ Loop_Delete,
+ Loop_Timer },
+#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM)
{ OBJECT_BITSTRING_VALUE,
Program_Init,
diff --git a/src/bacnet/property.c b/src/bacnet/property.c
index d35274af..f03df104 100644
--- a/src/bacnet/property.c
+++ b/src/bacnet/property.c
@@ -1851,6 +1851,7 @@ static const int Loop_Properties_Optional[] = {
/* unordered list of properties */
PROP_DESCRIPTION,
PROP_RELIABILITY,
+ PROP_UPDATE_INTERVAL,
PROP_PROPORTIONAL_CONSTANT,
PROP_PROPORTIONAL_CONSTANT_UNITS,
PROP_INTEGRAL_CONSTANT,
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6b07cb04..5a0b54c4 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -165,6 +165,7 @@ list(APPEND testdirs
bacnet/basic/object/iv
bacnet/basic/object/lc
bacnet/basic/object/lo
+ bacnet/basic/object/loop
bacnet/basic/object/lsp
bacnet/basic/object/lsz
bacnet/basic/object/ms-input
diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt
index 80754004..c79271d4 100644
--- a/test/bacnet/basic/object/device/CMakeLists.txt
+++ b/test/bacnet/basic/object/device/CMakeLists.txt
@@ -68,6 +68,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/iv.c
${SRC_DIR}/bacnet/basic/object/lc.c
${SRC_DIR}/bacnet/basic/object/lo.c
+ ${SRC_DIR}/bacnet/basic/object/loop.c
${SRC_DIR}/bacnet/basic/object/lsp.c
${SRC_DIR}/bacnet/basic/object/lsz.c
${SRC_DIR}/bacnet/basic/object/ms-input.c
diff --git a/test/bacnet/basic/object/loop/CMakeLists.txt b/test/bacnet/basic/object/loop/CMakeLists.txt
new file mode 100644
index 00000000..060527d3
--- /dev/null
+++ b/test/bacnet/basic/object/loop/CMakeLists.txt
@@ -0,0 +1,77 @@
+# 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}/bacnet/basic/object/test
+ ${TST_DIR}/ztest/include
+ )
+
+add_executable(${PROJECT_NAME}
+ # File(s) under test
+ ${SRC_DIR}/bacnet/basic/object/loop.c
+ # Support files and stubs (pathname alphabetical)
+ ${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/bacaction.c
+ ${SRC_DIR}/bacnet/bacaddr.c
+ ${SRC_DIR}/bacnet/bacapp.c
+ ${SRC_DIR}/bacnet/bacdcode.c
+ ${SRC_DIR}/bacnet/bacdest.c
+ ${SRC_DIR}/bacnet/bacdevobjpropref.c
+ ${SRC_DIR}/bacnet/bacint.c
+ ${SRC_DIR}/bacnet/baclog.c
+ ${SRC_DIR}/bacnet/bacreal.c
+ ${SRC_DIR}/bacnet/bacstr.c
+ ${SRC_DIR}/bacnet/bactext.c
+ ${SRC_DIR}/bacnet/bactimevalue.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/datetime.c
+ ${SRC_DIR}/bacnet/indtext.c
+ ${SRC_DIR}/bacnet/hostnport.c
+ ${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/property.c
+ ${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
+ ${SRC_DIR}/bacnet/timestamp.c
+ ${SRC_DIR}/bacnet/wp.c
+ ${SRC_DIR}/bacnet/weeklyschedule.c
+ ${SRC_DIR}/bacnet/dailyschedule.c
+ ${SRC_DIR}/bacnet/calendar_entry.c
+ ${SRC_DIR}/bacnet/special_event.c
+ ${SRC_DIR}/bacnet/channel_value.c
+ ${SRC_DIR}/bacnet/secure_connect.c
+ # Test and test library files
+ ./src/main.c
+ ${TST_DIR}/bacnet/basic/object/test/datetime_local.c
+ ${TST_DIR}/bacnet/basic/object/test/device_mock.c
+ ${TST_DIR}/bacnet/basic/object/test/property_test.c
+ ${ZTST_DIR}/ztest_mock.c
+ ${ZTST_DIR}/ztest.c
+ )
diff --git a/test/bacnet/basic/object/loop/src/main.c b/test/bacnet/basic/object/loop/src/main.c
new file mode 100644
index 00000000..454e02c0
--- /dev/null
+++ b/test/bacnet/basic/object/loop/src/main.c
@@ -0,0 +1,429 @@
+/**
+ * @file
+ * @brief Unit test for Loop object
+ * @author Steve Karg
+ * @date October 2025
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * @addtogroup bacnet_tests
+ * @{
+ */
+static BACNET_WRITE_PROPERTY_DATA Write_Property_Internal_Data;
+static bool Write_Property_Internal(BACNET_WRITE_PROPERTY_DATA *data)
+{
+ memcpy(
+ &Write_Property_Internal_Data, data,
+ sizeof(BACNET_WRITE_PROPERTY_DATA));
+
+ return true;
+}
+
+static BACNET_READ_PROPERTY_DATA Read_Property_Internal_Data;
+static int Read_Property_Internal_Length;
+static int Read_Property_Internal(BACNET_READ_PROPERTY_DATA *data)
+{
+ memcpy(
+ &Read_Property_Internal_Data, data, sizeof(BACNET_READ_PROPERTY_DATA));
+
+ return Read_Property_Internal_Length;
+}
+
+static int Proprietary_Properties[] = { 512, 513, -1 };
+static uint8_t Proprietary_Serial_Number[16];
+
+/**
+ * @brief WriteProperty handler for this objects proprietary properties.
+ * For the given WriteProperty data, the application_data is loaded
+ * or the error code and class are set and the return value is false.
+ * @param data - BACNET_WRITE_PROPERTY_DATA data, including
+ * requested data and space for the reply, or error response.
+ * @return false if an error is loaded, true if no errors
+ */
+static bool Write_Property_Proprietary(BACNET_WRITE_PROPERTY_DATA *data)
+{
+ bool status = false;
+ int apdu_len = 0;
+ uint8_t *apdu = NULL;
+ size_t apdu_size = 0;
+ BACNET_OCTET_STRING octet_value = { 0 };
+
+ switch ((int)data->object_property) {
+ case 512:
+ apdu_len = bacnet_octet_string_application_decode(
+ apdu, apdu_size, &octet_value);
+ if (apdu_len > 0) {
+ octetstring_copy_value(
+ Proprietary_Serial_Number,
+ sizeof(Proprietary_Serial_Number), &octet_value);
+ status = true;
+ } else if (apdu_len == 0) {
+ status = false;
+ data->error_class = ERROR_CLASS_PROPERTY;
+ data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+
+ } else {
+ status = false;
+ data->error_class = ERROR_CLASS_PROPERTY;
+ data->error_code = ERROR_CODE_INVALID_DATA_ENCODING;
+ }
+ break;
+ case 513:
+ default:
+ status = false;
+ data->error_class = ERROR_CLASS_PROPERTY;
+ data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * @brief ReadProperty handler for this objects proprietary properties.
+ * For the given ReadProperty data, the application_data is loaded
+ * or the error code and class are set and the return value is
+ * BACNET_STATUS_ERROR.
+ * @param data - BACNET_READ_PROPERTY_DATA data, including
+ * requested data and space for the reply, or error response.
+ * @return false if an error is loaded, true if no errors
+ */
+static int Read_Property_Proprietary(BACNET_READ_PROPERTY_DATA *data)
+{
+ int apdu_len = 0;
+ uint8_t *apdu = NULL;
+ size_t apdu_size = 0;
+ BACNET_OCTET_STRING octet_value = { 0 };
+ BACNET_UNSIGNED_INTEGER unsigned_value = 0;
+
+ if ((data == NULL) || (data->application_data == NULL) ||
+ (data->application_data_len == 0)) {
+ return 0;
+ }
+ /* none of our proprietary properties are arrays */
+ if (data->array_index != BACNET_ARRAY_ALL) {
+ data->error_class = ERROR_CLASS_PROPERTY;
+ data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return BACNET_STATUS_ERROR;
+ }
+ apdu = data->application_data;
+ apdu_size = data->application_data_len;
+ switch ((int)data->object_property) {
+ case 512:
+ octetstring_init(
+ &octet_value, Proprietary_Serial_Number,
+ sizeof(Proprietary_Serial_Number));
+ apdu_len = bacnet_octet_string_application_encode(
+ apdu, apdu_size, &octet_value);
+ break;
+ case 513:
+ unsigned_value = Loop_Size();
+ apdu_len = bacnet_unsigned_application_encode(
+ apdu, apdu_size, unsigned_value);
+ break;
+ default:
+ data->error_class = ERROR_CLASS_PROPERTY;
+ data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Test
+ */
+static void test_Loop_Read_Write(void)
+{
+ const uint32_t instance = 123;
+ unsigned count = 0;
+ unsigned index = 0;
+ const char *sample_name = "Loop:0";
+ char *sample_context = "context";
+ const char *sample_description = "Loop Description";
+ const char *test_name = NULL;
+ uint32_t test_instance = 0;
+ bool status = false;
+ const int skip_fail_property_list[] = { -1 };
+ BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
+ BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ BACNET_CHARACTER_STRING cstring = { 0 };
+ int32_t i = 0,
+ units_properties[] = { PROP_OUTPUT_UNITS,
+ PROP_CONTROLLED_VARIABLE_UNITS,
+ PROP_PROPORTIONAL_CONSTANT_UNITS,
+ PROP_INTEGRAL_CONSTANT_UNITS,
+ PROP_DERIVATIVE_CONSTANT_UNITS,
+ -1 },
+ real_properties[] = { PROP_PRESENT_VALUE,
+ PROP_CONTROLLED_VARIABLE_VALUE,
+ PROP_SETPOINT,
+ PROP_PROPORTIONAL_CONSTANT,
+ PROP_INTEGRAL_CONSTANT,
+ PROP_DERIVATIVE_CONSTANT,
+ PROP_BIAS,
+ PROP_MAXIMUM_OUTPUT,
+ PROP_MINIMUM_OUTPUT,
+ PROP_COV_INCREMENT,
+ -1 };
+
+ Loop_Init();
+ Loop_Create(instance);
+ status = Loop_Valid_Instance(instance);
+ zassert_true(status, NULL);
+ status = Loop_Valid_Instance(instance - 1);
+ zassert_false(status, NULL);
+ index = Loop_Instance_To_Index(instance);
+ zassert_equal(index, 0, NULL);
+ test_instance = Loop_Index_To_Instance(index);
+ zassert_equal(instance, test_instance, NULL);
+ count = Loop_Count();
+ zassert_true(count > 0, NULL);
+ /* reliability and status flags */
+ status = Loop_Reliability_Set(instance, RELIABILITY_PROCESS_ERROR);
+ zassert_true(status, NULL);
+ /* add some proprietary properties */
+ Loop_Proprietary_Property_List_Set(Proprietary_Properties);
+ Loop_Read_Property_Proprietary_Callback_Set(Read_Property_Proprietary);
+ Loop_Write_Property_Proprietary_Callback_Set(Write_Property_Proprietary);
+ /* perform a general test for RP/WP */
+ bacnet_object_properties_read_write_test(
+ OBJECT_LOOP, instance, Loop_Property_Lists, Loop_Read_Property,
+ Loop_Write_Property, skip_fail_property_list);
+ /* test the ASCII name get/set */
+ status = Loop_Name_Set(instance, sample_name);
+ zassert_true(status, NULL);
+ test_name = Loop_Name_ASCII(instance);
+ zassert_equal(test_name, sample_name, NULL);
+ status = Loop_Object_Name(instance, &cstring);
+ zassert_true(status, NULL);
+ status = characterstring_ansi_same(&cstring, sample_name);
+ zassert_true(status, NULL);
+ status = Loop_Name_Set(instance, NULL);
+ zassert_true(status, NULL);
+ test_name = Loop_Name_ASCII(instance);
+ zassert_equal(test_name, NULL, NULL);
+ /* test specific WriteProperty values - common configuration */
+ wp_data.object_type = OBJECT_LOOP;
+ wp_data.object_instance = instance;
+ wp_data.array_index = BACNET_ARRAY_ALL;
+ wp_data.priority = BACNET_MAX_PRIORITY;
+ zassert_true(status, NULL);
+ /* out-of-service */
+ wp_data.object_property = PROP_OUT_OF_SERVICE;
+ value.tag = BACNET_APPLICATION_TAG_BOOLEAN;
+ value.type.Boolean = true;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Boolean = false;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 123;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ /* write present-value */
+ wp_data.object_property = PROP_PRESENT_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_REAL;
+ value.type.Real = 1.0f;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ /* write minimum-output and maximum-output */
+ wp_data.object_property = PROP_MINIMUM_OUTPUT;
+ value.tag = BACNET_APPLICATION_TAG_REAL;
+ value.type.Real = 1.0f;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.object_property = PROP_MAXIMUM_OUTPUT;
+ value.tag = BACNET_APPLICATION_TAG_REAL;
+ value.type.Real = 100.0f;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ /* action - out of range error */
+ wp_data.object_property = PROP_ACTION;
+ value.tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value.type.Enumerated = BACNET_ACTION_MAX;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* units - out of range error */
+ i = 0;
+ while (units_properties[i] != -1) {
+ wp_data.object_property = units_properties[i];
+ value.tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value.type.Enumerated = UINT16_MAX + 1UL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ i++;
+ }
+ /* REAL - out of range error */
+ i = 0;
+ while (real_properties[i] != -1) {
+ wp_data.object_property = real_properties[i];
+ value.tag = BACNET_APPLICATION_TAG_REAL;
+ value.type.Real = NAN;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ i++;
+ }
+ /* priority-for-writing - out of range error */
+ wp_data.object_property = PROP_PRIORITY_FOR_WRITING;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = BACNET_MIN_PRIORITY;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Unsigned_Int = BACNET_MAX_PRIORITY + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ value.type.Unsigned_Int = UINT8_MAX + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* read-only property */
+ wp_data.object_property = PROP_OBJECT_TYPE;
+ value.tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value.type.Enumerated = OBJECT_ANALOG_INPUT;
+ wp_data.array_index = BACNET_ARRAY_ALL;
+ wp_data.priority = BACNET_MAX_PRIORITY;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Loop_Write_Property(&wp_data);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL);
+ zassert_false(status, NULL);
+ /* == API testing where not already tested by read or write property == */
+ /* reliability and status flags API */
+ status = Loop_Reliability_Set(instance, RELIABILITY_PROCESS_ERROR);
+ zassert_true(status, NULL);
+ /* context API */
+ Loop_Context_Set(instance, sample_context);
+ zassert_true(sample_context == Loop_Context_Get(instance), NULL);
+ zassert_true(NULL == Loop_Context_Get(instance + 1), NULL);
+ /* description API */
+ status = Loop_Description_Set(instance, sample_description);
+ zassert_true(status, NULL);
+ zassert_equal(sample_description, Loop_Description_ANSI(instance), NULL);
+ status = Loop_Description(instance, &cstring);
+ zassert_true(status, NULL);
+ status = characterstring_ansi_same(&cstring, sample_description);
+ zassert_true(status, NULL);
+ status = Loop_Description_Set(instance, NULL);
+ zassert_true(status, NULL);
+ status = characterstring_init_ansi(&cstring, "");
+ zassert_true(status, NULL);
+ status =
+ characterstring_ansi_same(&cstring, Loop_Description_ANSI(instance));
+ zassert_true(status, NULL);
+ /* cleanup */
+ status = Loop_Delete(instance);
+ zassert_true(status, NULL);
+ Loop_Cleanup();
+}
+
+/**
+ * @brief Test
+ */
+static void test_Loop_Operation(void)
+{
+ const uint32_t instance = 123;
+ uint32_t test_instance = 0;
+ bool status = false;
+ uint32_t elapsed_time = 0;
+ BACNET_OBJECT_PROPERTY_REFERENCE reference = { 0 };
+
+ /* init */
+ Loop_Init();
+ Loop_Create(instance);
+ status = Loop_Valid_Instance(instance);
+ zassert_true(status, NULL);
+ /* set Kp, Ki, Kd, setpoint, etc */
+ /* connect the read and write property callbacks */
+ Loop_Write_Property_Internal_Callback_Set(Write_Property_Internal);
+ Loop_Read_Property_Internal_Callback_Set(Read_Property_Internal);
+ /* run the PID loop */
+ Loop_Timer(instance, elapsed_time);
+ elapsed_time += 1000;
+ Loop_Timer(instance, elapsed_time);
+ Loop_Update_Interval_Set(instance, 100);
+ elapsed_time += 100;
+ Loop_Timer(instance, elapsed_time);
+ elapsed_time += 100;
+ Loop_Timer(instance, elapsed_time);
+ elapsed_time += 100;
+ Loop_Timer(instance, elapsed_time);
+ /* references - test by referencing self */
+ reference.object_identifier.instance = instance;
+ reference.object_identifier.type = OBJECT_LOOP;
+ reference.property_array_index = BACNET_ARRAY_ALL;
+ reference.property_identifier = PROP_CONTROLLED_VARIABLE_VALUE;
+ Loop_Controlled_Variable_Reference_Set(instance, &reference);
+ reference.property_identifier = PROP_SETPOINT;
+ Loop_Setpoint_Reference_Set(instance, &reference);
+ reference.property_identifier = PROP_PRESENT_VALUE;
+ Loop_Manipulated_Variable_Reference_Set(instance, &reference);
+ elapsed_time += 100;
+ Loop_Timer(instance, elapsed_time);
+ /* cleanup instance */
+ status = Loop_Delete(instance);
+ zassert_true(status, NULL);
+ /* test create of next instance */
+ test_instance = Loop_Create(BACNET_MAX_INSTANCE);
+ zassert_not_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
+ test_instance = Loop_Create(test_instance);
+ zassert_not_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
+ test_instance = Loop_Create(BACNET_MAX_INSTANCE + 1);
+ zassert_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
+ /* cleanup all */
+ Loop_Cleanup();
+}
+/**
+ * @}
+ */
+
+void test_main(void)
+{
+ ztest_test_suite(
+ loop_tests, ztest_unit_test(test_Loop_Read_Write),
+ ztest_unit_test(test_Loop_Operation));
+
+ ztest_run_test_suite(loop_tests);
+}
diff --git a/test/bacnet/basic/object/test/property_test.c b/test/bacnet/basic/object/test/property_test.c
index 06aa2221..6c82af0d 100644
--- a/test/bacnet/basic/object/test/property_test.c
+++ b/test/bacnet/basic/object/test/property_test.c
@@ -216,6 +216,18 @@ int bacnet_object_property_read_test(
/* test an array index that must be implemented */
rpdata->array_index = 0;
read_len = read_property(rpdata);
+ if ((rpdata->object_property >= PROP_PROPRIETARY_RANGE_MIN) &&
+ (rpdata->object_property <= PROP_PROPRIETARY_RANGE_MAX)) {
+ /* all proprietary properties could be a BACnetARRAY */
+ if (read_len == BACNET_STATUS_ERROR) {
+ zassert_equal(
+ rpdata->error_code, ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY,
+ NULL);
+ }
+ is_array = false;
+ }
+ }
+ if (is_array) {
zassert_not_equal(
read_len, BACNET_STATUS_ERROR,
"property '%s' array_index=0: error code is %s.\n",
@@ -319,8 +331,8 @@ void bacnet_object_properties_read_write_test(
len, BACNET_STATUS_ERROR,
"property '%s' array_index=ALL: Missing in property list.\n",
bactext_property_name(rpdata.object_property));
- /* shrink the number space and skip proprietary range values */
- if (property == PROP_RESERVED_RANGE_MAX) {
+ /* shrink the number space and skip most proprietary range values */
+ if (property == (PROP_PROPRIETARY_RANGE_MIN + 1)) {
property = PROP_RESERVED_RANGE_MIN2 - 1;
}
/* shrink the number space to known values */
diff --git a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
index 09462e36..9c9acbc5 100644
--- a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
+++ b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
@@ -73,6 +73,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/iv.c
${SRC_DIR}/bacnet/basic/object/lc.c
${SRC_DIR}/bacnet/basic/object/lo.c
+ ${SRC_DIR}/bacnet/basic/object/loop.c
${SRC_DIR}/bacnet/basic/object/lsp.c
${SRC_DIR}/bacnet/basic/object/lsz.c
${SRC_DIR}/bacnet/basic/object/ms-input.c
|