From 74972bd025619bd95479a1a7783d3856e8570e63 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 13 Nov 2025 16:11:19 -0600 Subject: [PATCH] Added a basic creatable loop object with PID control (#1141) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 + CMakeLists.txt | 2 + apps/gateway/Makefile | 1 + apps/server-basic/Makefile | 1 + apps/server-mini/Makefile | 1 + apps/server/Makefile | 1 + doc/htdocs/index.html | 6 +- .../BACnet_Object_Definitions.vcxproj | 1 + .../BACnet_Object_Definitions.vcxproj.filters | 3 + .../bacnet-stack/bacnet-stack.vcxproj | 1 + .../bacnet-stack/bacnet-stack.vcxproj.filters | 3 + src/bacnet/bacapp.c | 8 +- src/bacnet/bacdef.h | 1 + src/bacnet/bacenum.h | 3 +- src/bacnet/basic/object/device.c | 9 + src/bacnet/basic/object/loop.c | 2269 +++++++++++++++++ src/bacnet/basic/object/loop.h | 214 ++ src/bacnet/basic/server/bacnet_device.c | 25 + src/bacnet/property.c | 1 + test/CMakeLists.txt | 1 + .../bacnet/basic/object/device/CMakeLists.txt | 1 + test/bacnet/basic/object/loop/CMakeLists.txt | 77 + test/bacnet/basic/object/loop/src/main.c | 429 ++++ test/bacnet/basic/object/test/property_test.c | 16 +- .../basic/server/bacnet_device/CMakeLists.txt | 1 + 25 files changed, 3070 insertions(+), 7 deletions(-) create mode 100644 src/bacnet/basic/object/loop.c create mode 100644 src/bacnet/basic/object/loop.h create mode 100644 test/bacnet/basic/object/loop/CMakeLists.txt create mode 100644 test/bacnet/basic/object/loop/src/main.c 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 - Loop + + Loop - + "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