From 4df896b820d83dae94ca320e78018ba0343b84c5 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 13 Mar 2025 15:51:37 -0500 Subject: [PATCH] Feature/basic-program-object (#940) * Added basic Program object and unit test. * Integrate the basic Program object with example applications. --- CMakeLists.txt | 2 + apps/gateway/Makefile | 1 + apps/server-basic/Makefile | 1 + apps/server/Makefile | 1 + .../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/bacenum.h | 17 +- src/bacnet/basic/object/device.c | 24 +- src/bacnet/basic/object/program.c | 1110 +++++++++++++++++ src/bacnet/basic/object/program.h | 134 ++ src/bacnet/basic/server/bacnet_device.c | 26 + test/CMakeLists.txt | 1 + .../bacnet/basic/object/device/CMakeLists.txt | 1 + .../basic/object/program/CMakeLists.txt | 72 ++ test/bacnet/basic/object/program/src/main.c | 60 + 17 files changed, 1442 insertions(+), 16 deletions(-) create mode 100644 src/bacnet/basic/object/program.c create mode 100644 src/bacnet/basic/object/program.h create mode 100644 test/bacnet/basic/object/program/CMakeLists.txt create mode 100644 test/bacnet/basic/object/program/src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1faf83ed..2b824974 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/object/osv.h src/bacnet/basic/object/piv.c src/bacnet/basic/object/piv.h + src/bacnet/basic/object/program.c + src/bacnet/basic/object/program.h src/bacnet/basic/object/schedule.c src/bacnet/basic/object/schedule.h src/bacnet/basic/object/structured_view.c diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile index 604309a1..4c9e73e7 100644 --- a/apps/gateway/Makefile +++ b/apps/gateway/Makefile @@ -36,6 +36,7 @@ BACNET_OBJECT_SRC := \ $(BACNET_OBJECT_DIR)/msv.c \ $(BACNET_OBJECT_DIR)/osv.c \ $(BACNET_OBJECT_DIR)/piv.c \ + $(BACNET_OBJECT_DIR)/program.c \ $(BACNET_OBJECT_DIR)/nc.c \ $(BACNET_OBJECT_DIR)/netport.c \ $(BACNET_OBJECT_DIR)/time_value.c \ diff --git a/apps/server-basic/Makefile b/apps/server-basic/Makefile index b13b6ab6..00fcf710 100644 --- a/apps/server-basic/Makefile +++ b/apps/server-basic/Makefile @@ -37,6 +37,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/nc.c \ $(BACNET_OBJECT_DIR)/netport.c \ $(BACNET_OBJECT_DIR)/osv.c \ + $(BACNET_OBJECT_DIR)/program.c \ $(BACNET_OBJECT_DIR)/structured_view.c \ $(BACNET_OBJECT_DIR)/time_value.c diff --git a/apps/server/Makefile b/apps/server/Makefile index 77c78c05..436021d5 100644 --- a/apps/server/Makefile +++ b/apps/server/Makefile @@ -30,6 +30,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/msv.c \ $(BACNET_OBJECT_DIR)/osv.c \ $(BACNET_OBJECT_DIR)/piv.c \ + $(BACNET_OBJECT_DIR)/program.c \ $(BACNET_OBJECT_DIR)/nc.c \ $(BACNET_OBJECT_DIR)/netport.c \ $(BACNET_OBJECT_DIR)/time_value.c \ diff --git a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj index fc113762..569386a5 100644 --- a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj +++ b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj @@ -197,6 +197,7 @@ + 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 8ac932a5..ba5cd764 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 @@ -81,6 +81,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 fce4b9b3..9ca090ec 100644 --- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj +++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj @@ -76,6 +76,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 cd9021bb..dec512a8 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 @@ -384,6 +384,9 @@ Source Files\src\bacnet\basic\object + + Source Files\src\bacnet\basic\object + Source Files\src\bacnet\basic\object diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 4a194d3a..f88beca6 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1088,7 +1088,7 @@ typedef enum { MAX_POLARITY = 2 } BACNET_POLARITY; -typedef enum { +typedef enum BACnetProgramRequest { PROGRAM_REQUEST_READY = 0, PROGRAM_REQUEST_LOAD = 1, PROGRAM_REQUEST_RUN = 2, @@ -1097,7 +1097,7 @@ typedef enum { PROGRAM_REQUEST_UNLOAD = 5 } BACNET_PROGRAM_REQUEST; -typedef enum { +typedef enum BACnetProgramState { PROGRAM_STATE_IDLE = 0, PROGRAM_STATE_LOADING = 1, PROGRAM_STATE_RUNNING = 2, @@ -1106,18 +1106,17 @@ typedef enum { PROGRAM_STATE_UNLOADING = 5 } BACNET_PROGRAM_STATE; -typedef enum { +typedef enum BACnetProgramError { PROGRAM_ERROR_NORMAL = 0, PROGRAM_ERROR_LOAD_FAILED = 1, PROGRAM_ERROR_INTERNAL = 2, PROGRAM_ERROR_PROGRAM = 3, PROGRAM_ERROR_OTHER = 4, - /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ - /* Enumerated values 64-65535 may be used by others subject to */ - /* the procedures and constraints described in Clause 23. */ - /* do the max range inside of enum so that - compilers will allocate adequate sized datatype for enum - which is used to store decoding */ + PROGRAM_ERROR_RESERVED_MIN = 5, + PROGRAM_ERROR_RESERVED_MAX = 63, + /* Enumerated values 0-63 are reserved for definition by ASHRAE. + Enumerated values 64-65535 may be used by others subject + to the procedures and constraints described in Clause 23. */ PROGRAM_ERROR_PROPRIETARY_MIN = 64, PROGRAM_ERROR_PROPRIETARY_MAX = 65535 } BACNET_PROGRAM_ERROR; diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 4eebf6db..b703e02f 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -37,6 +37,7 @@ #include "bacnet/basic/object/bv.h" #include "bacnet/basic/object/calendar.h" #include "bacnet/basic/object/command.h" +#include "bacnet/basic/object/program.h" #include "bacnet/basic/object/lc.h" #include "bacnet/basic/object/lsp.h" #include "bacnet/basic/object/lsz.h" @@ -64,6 +65,7 @@ #include "bacnet/basic/object/netport.h" #include "bacnet/basic/object/color_object.h" #include "bacnet/basic/object/color_temperature.h" +#include "bacnet/basic/object/program.h" /* external prototypes */ extern int Routed_Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); @@ -215,13 +217,13 @@ static object_functions_t My_Object_Table[] = { NULL /* Add_List_Element */, NULL /* Remove_List_Element */, Integer_Value_Create, Integer_Value_Delete, NULL /* Timer */ }, #endif - { OBJECT_COMMAND, Command_Init, Command_Count, Command_Index_To_Instance, - Command_Valid_Instance, Command_Object_Name, Command_Read_Property, - Command_Write_Property, Command_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 */, - NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, + { OBJECT_COMMAND, Command_Init, Command_Count, Command_Index_To_Instance, + Command_Valid_Instance, Command_Object_Name, Command_Read_Property, + Command_Write_Property, Command_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 */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #if defined(INTRINSIC_REPORTING) { OBJECT_NOTIFICATION_CLASS, Notification_Class_Init, Notification_Class_Count, Notification_Class_Index_To_Instance, @@ -376,6 +378,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_PROGRAM, Program_Init, Program_Count, + Program_Index_To_Instance, Program_Valid_Instance, + Program_Object_Name, Program_Read_Property, + Program_Write_Property, Program_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 */, + Program_Create, Program_Delete, Program_Timer }, { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, NULL /* Index_To_Instance */, NULL /* Valid_Instance */, NULL /* Object_Name */, NULL /* Read_Property */, diff --git a/src/bacnet/basic/object/program.c b/src/bacnet/basic/object/program.c new file mode 100644 index 00000000..c94f998f --- /dev/null +++ b/src/bacnet/basic/object/program.c @@ -0,0 +1,1110 @@ +/** + * @file + * @author Steve Karg + * @date March 2025 + * @brief The Program object type defines a standardized object whose + * properties represent the externally visible characteristics of an + * application program. + * @copyright SPDX-License-Identifier: MIT + */ +#include +#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/proplist.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/program.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_PROGRAM; + +struct object_data { + BACNET_PROGRAM_STATE Program_State; + BACNET_PROGRAM_REQUEST Program_Change; + BACNET_PROGRAM_ERROR Reason_For_Halt; + const char *Description_Of_Halt; + const char *Program_Location; + const char *Instance_Of; + const char *Description; + const char *Object_Name; + BACNET_RELIABILITY Reliability; + bool Out_Of_Service : 1; + bool Changed : 1; + void *Context; + void (*Load)(void *context, const char *location); + void (*Run)(void *context); + void (*Halt)(void *context); + void (*Restart)(void *context); + void (*Unload)(void *context); +}; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Properties_Required[] = { + /* unordered list of properties */ + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, PROP_PROGRAM_STATE, + PROP_PROGRAM_CHANGE, PROP_STATUS_FLAGS, + PROP_OUT_OF_SERVICE, -1 +}; + +static const int Properties_Optional[] = { + /* unordered list of properties */ + PROP_REASON_FOR_HALT, + PROP_DESCRIPTION_OF_HALT, + PROP_PROGRAM_LOCATION, + PROP_DESCRIPTION, + PROP_INSTANCE_OF, + PROP_RELIABILITY, + -1 +}; + +static const int Properties_Proprietary[] = { -1 }; + +/** + * 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 optkional properties for this object. + * @param pProprietary - pointer to list of int terminated by -1, of + * BACnet proprietary properties for this object. + */ +void Program_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Properties_Required; + } + if (pOptional) { + *pOptional = Properties_Optional; + } + if (pProprietary) { + *pProprietary = Properties_Proprietary; + } + + return; +} + +/** + * @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 Integer Value instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Program_Valid_Instance(uint32_t object_instance) +{ + struct object_data *pObject = Object_Data(object_instance); + + return (pObject != NULL); +} + +/** + * Determines the number of Integer Value objects + * + * @return Number of Integer Value objects + */ +unsigned Program_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..N index + * of Integer Value objects where N is Program_Count(). + * + * @param index - 0..MAX_PROGRAMS value + * + * @return object instance-number for the given index + */ +uint32_t Program_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 Integer Value objects where N is Program_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or MAX_PROGRAMS + * if not valid. + */ +unsigned Program_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * For a given object instance-number, determines the program-state + * + * @param object_instance - object-instance number of the object + * + * @return program-state of the object + */ +BACNET_PROGRAM_STATE Program_State(uint32_t object_instance) +{ + BACNET_PROGRAM_STATE value = 0; + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + value = pObject->Program_State; + } + + return value; +} + +/** + * For a given object instance-number, sets the program-state + * + * @param object_instance - object-instance number of the object + * @param value - integer value + * + * @return true if values are within range and present-value is set. + */ +bool Program_State_Set(uint32_t object_instance, BACNET_PROGRAM_STATE value) +{ + bool status = false; + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Program_State = value; + status = true; + } + + return status; +} + +/** + * 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 Program_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), "PROGRAM-%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 Program_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 *Program_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 Program_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 Program_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 *Program_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, return the Description_Of_Halt. + * + * @param object_instance - object-instance number of the object + * @param description - description pointer + * + * @return true/false + */ +bool Program_Description_Of_Halt( + 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_Of_Halt) { + status = characterstring_init_ansi( + description, pObject->Description_Of_Halt); + } else { + status = characterstring_init_ansi(description, ""); + } + } + + return status; +} + +/** + * @brief For a given object instance-number, sets the Description_Of_Halt + * @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 Program_Description_Of_Halt_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_Of_Halt = new_name; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Description_Of_Halt + * @param object_instance - object-instance number of the object + * @return text or NULL if not found + */ +const char *Program_Description_Of_Halt_ANSI(uint32_t object_instance) +{ + const char *name = NULL; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (pObject->Description_Of_Halt == NULL) { + name = ""; + } else { + name = pObject->Description_Of_Halt; + } + } + + return name; +} + +/** + * For a given object instance-number, return the Description_Of_Halt. + * + * @param object_instance - object-instance number of the object + * @param description - description pointer + * + * @return true/false + */ +bool Program_Location( + uint32_t object_instance, BACNET_CHARACTER_STRING *description) +{ + bool status = false; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (pObject->Program_Location) { + status = characterstring_init_ansi( + description, pObject->Program_Location); + } else { + status = characterstring_init_ansi(description, ""); + } + } + + return status; +} + +/** + * @brief For a given object instance-number, sets the Program_Location + * @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 Program_Location_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->Program_Location = new_name; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Program_Location + * @param object_instance - object-instance number of the object + * @return text or NULL if not found + */ +const char *Program_Location_ANSI(uint32_t object_instance) +{ + const char *name = NULL; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (pObject->Program_Location == NULL) { + name = ""; + } else { + name = pObject->Program_Location; + } + } + + return name; +} + +/** + * For a given object instance-number, return the Instance_Of string + * + * @param object_instance - object-instance number of the object + * @param description - description pointer + * + * @return true/false if the string was found + */ +bool Program_Instance_Of( + uint32_t object_instance, BACNET_CHARACTER_STRING *description) +{ + bool status = false; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (pObject->Instance_Of) { + status = + characterstring_init_ansi(description, pObject->Instance_Of); + } else { + status = characterstring_init_ansi(description, ""); + } + } + + return status; +} + +/** + * @brief For a given object instance-number, sets the Instance_Of + * @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 Program_Instance_Of_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->Instance_Of = new_name; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Instance_Of + * @param object_instance - object-instance number of the object + * @return text or NULL if not found + */ +const char *Program_Instance_Of_ANSI(uint32_t object_instance) +{ + const char *name = NULL; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (pObject->Instance_Of == NULL) { + name = ""; + } else { + name = pObject->Instance_Of; + } + } + + return name; +} + +/** + * For a given object instance-number, returns the program change value + * + * @param object_instance - object-instance number of the object + * + * @return program change property value + */ +BACNET_PROGRAM_REQUEST Program_Change(uint32_t object_instance) +{ + uint16_t units = UNITS_NO_UNITS; + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + units = pObject->Program_Change; + } + + return units; +} + +/** + * For a given object instance-number, sets the program change property value + * + * @param object_instance - object-instance number of the object + * @param program_change - property value + * + * @return true if the program change property value was set + */ +bool Program_Change_Set( + uint32_t object_instance, BACNET_PROGRAM_REQUEST program_change) +{ + bool status = false; + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Program_Change = program_change; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, returns the Reason_For_Halt + * + * @param object_instance - object-instance number of the object + * + * @return Reason_For_Halt property value + */ +BACNET_PROGRAM_ERROR Program_Reason_For_Halt(uint32_t object_instance) +{ + uint16_t units = UNITS_NO_UNITS; + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + units = pObject->Reason_For_Halt; + } + + return units; +} + +/** + * For a given object instance-number, sets the Reason_For_Halt property value + * + * @param object_instance - object-instance number of the object + * @param program_change - property value + * + * @return true if the Reason_For_Halt property value was set + */ +bool Program_Reason_For_Halt_Set( + uint32_t object_instance, BACNET_PROGRAM_ERROR reason) +{ + bool status = false; + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Reason_For_Halt = reason; + status = true; + } + + return status; +} + +/** + * 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 Program_Out_Of_Service(uint32_t object_instance) +{ + struct object_data *pObject = Object_Data(object_instance); + bool value = false; + + 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 Program_Out_Of_Service_Set(uint32_t object_instance, bool value) +{ + struct object_data *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 Program_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 Program_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 Program_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; +} + +/** + * 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, or + * BACNET_STATUS_ERROR on error. + */ +int Program_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + 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: + Program_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_DESCRIPTION: + if (Program_Description(rpdata->object_instance, &char_string)) { + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + state = Program_Fault(rpdata->object_instance); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, state); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + state = Program_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_OUT_OF_SERVICE: + state = Program_Out_Of_Service(rpdata->object_instance); + apdu_len = encode_application_boolean(&apdu[0], state); + break; + case PROP_PROGRAM_STATE: + enum_value = Program_State(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], enum_value); + break; + case PROP_PROGRAM_CHANGE: + enum_value = Program_Change(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], enum_value); + break; + case PROP_REASON_FOR_HALT: + enum_value = Program_Reason_For_Halt(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], enum_value); + break; + case PROP_DESCRIPTION_OF_HALT: + if (Program_Description_Of_Halt( + rpdata->object_instance, &char_string)) { + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_PROGRAM_LOCATION: + if (Program_Location(rpdata->object_instance, &char_string)) { + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_INSTANCE_OF: + if (Program_Instance_Of(rpdata->object_instance, &char_string)) { + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_RELIABILITY: + enum_value = Program_Reliability(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], enum_value); + break; + default: + 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 Program_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_application_data( + wp_data->application_data, wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + 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_PROGRAM_CHANGE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = Program_Change_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_OUT_OF_SERVICE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + Program_Out_Of_Service_Set( + wp_data->object_instance, value.type.Boolean); + } + break; + default: + if (property_lists_member( + Properties_Required, Properties_Optional, + Properties_Proprietary, wp_data->object_property)) { + 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 Program_Context_Set(uint32_t object_instance, void *context) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Context = context; + } +} + +/** + * @brief Set the Load function for the object + * @param object_instance [in] BACnet object instance number + * @param load [in] pointer to the Load function + */ +void Program_Load_Set( + uint32_t object_instance, void (*load)(void *context, const char *location)) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Load = load; + } +} + +/** + * @brief Set the Run function for the object + * @param object_instance [in] BACnet object instance number + * @param run [in] pointer to the Run function + */ +void Program_Run_Set(uint32_t object_instance, void (*run)(void *context)) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Run = run; + } +} + +/** + * @brief Set the Halt function for the object + * @param object_instance [in] BACnet object instance number + * @param halt [in] pointer to the Halt function + */ +void Program_Halt_Set(uint32_t object_instance, void (*halt)(void *context)) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Halt = halt; + } +} + +/** + * @brief Set the Restart function for the object + * @param object_instance [in] BACnet object instance number + * @param restart [in] pointer to the Restart function + */ +void Program_Restart_Set( + uint32_t object_instance, void (*restart)(void *context)) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Restart = restart; + } +} + +/** + * @brief Set the Unload function for the object + * @param object_instance [in] BACnet object instance number + * @param unload [in] pointer to the Unload function + */ +void Program_Unload_Set(uint32_t object_instance, void (*unload)(void *context)) +{ + struct object_data *pObject = Object_Data(object_instance); + + if (pObject) { + pObject->Unload = unload; + } +} + +/** + * @brief Updates the object program operation + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +void Program_Timer(uint32_t object_instance, uint16_t milliseconds) +{ + struct object_data *pObject; + + (void)milliseconds; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Run) { + pObject->Run(pObject->Context); + } + } +} + +/** + * @brief Creates a Integer Value object + * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE + */ +uint32_t Program_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; + } + pObject->Program_State = PROGRAM_STATE_IDLE; + pObject->Program_Change = PROGRAM_REQUEST_READY; + pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL; + pObject->Description_Of_Halt = NULL; + pObject->Program_Location = NULL; + pObject->Instance_Of = NULL; + pObject->Description = NULL; + pObject->Object_Name = NULL; + pObject->Reliability = RELIABILITY_NO_FAULT_DETECTED; + pObject->Out_Of_Service = false; + pObject->Context = NULL; + pObject->Load = NULL; + pObject->Run = NULL; + pObject->Halt = NULL; + pObject->Restart = NULL; + pObject->Unload = 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 Program_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 Program_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; + } +} + +/** + * Initializes the object data + */ +void Program_Init(void) +{ + if (!Object_List) { + Object_List = Keylist_Create(); + } +} diff --git a/src/bacnet/basic/object/program.h b/src/bacnet/basic/object/program.h new file mode 100644 index 00000000..ac31e9fe --- /dev/null +++ b/src/bacnet/basic/object/program.h @@ -0,0 +1,134 @@ +/** + * @file + * @brief API for Program object type + * @author Steve Karg + * @date March 2025 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_BASIC_OBJECT_PROGRAM_H +#define BACNET_BASIC_OBJECT_PROGRAM_H +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacerror.h" +#include "bacnet/wp.h" +#include "bacnet/rp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Program_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Program_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Program_Count(void); +BACNET_STACK_EXPORT +uint32_t Program_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Program_Instance_To_Index(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Program_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Program_Name_Set(uint32_t object_instance, const char *new_name); +BACNET_STACK_EXPORT +const char *Program_Name_ASCII(uint32_t object_instance); + +BACNET_STACK_EXPORT +int Program_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); + +BACNET_STACK_EXPORT +bool Program_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); + +BACNET_STACK_EXPORT +BACNET_PROGRAM_STATE Program_State(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Program_State_Set(uint32_t object_instance, BACNET_PROGRAM_STATE value); + +BACNET_STACK_EXPORT +BACNET_PROGRAM_REQUEST Program_Change(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Program_Change_Set( + uint32_t object_instance, BACNET_PROGRAM_REQUEST program_change); +BACNET_STACK_EXPORT +BACNET_PROGRAM_ERROR Program_Reason_For_Halt(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Program_Reason_For_Halt_Set( + uint32_t object_instance, BACNET_PROGRAM_ERROR reason); + +BACNET_STACK_EXPORT +bool Program_Description( + uint32_t object_instance, BACNET_CHARACTER_STRING *description); +BACNET_STACK_EXPORT +bool Program_Description_Set(uint32_t instance, const char *new_name); +BACNET_STACK_EXPORT +const char *Program_Description_ANSI(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Program_Location( + uint32_t object_instance, BACNET_CHARACTER_STRING *description); +BACNET_STACK_EXPORT +bool Program_Location_Set(uint32_t instance, const char *new_name); +BACNET_STACK_EXPORT +const char *Program_Location_ANSI(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Program_Instance_Of( + uint32_t object_instance, BACNET_CHARACTER_STRING *description); +BACNET_STACK_EXPORT +bool Program_Instance_Of_Set(uint32_t instance, const char *new_name); +BACNET_STACK_EXPORT +const char *Program_Instance_Of_ANSI(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Program_Description_Of_Halt( + uint32_t object_instance, BACNET_CHARACTER_STRING *description); +BACNET_STACK_EXPORT +bool Program_Description_Of_Halt_Set(uint32_t instance, const char *new_name); +BACNET_STACK_EXPORT +const char *Program_Description_Of_Halt_ANSI(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Program_Out_Of_Service(uint32_t instance); +BACNET_STACK_EXPORT +void Program_Out_Of_Service_Set(uint32_t instance, bool oos_flag); + +BACNET_STACK_EXPORT +void Program_Timer(uint32_t object_instance, uint16_t milliseconds); +BACNET_STACK_EXPORT +uint32_t Program_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Program_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Program_Cleanup(void); +BACNET_STACK_EXPORT +void Program_Init(void); + +/* API for the program requests */ +BACNET_STACK_EXPORT +void Program_Context_Set(uint32_t object_instance, void *context); +BACNET_STACK_EXPORT +void Program_Load_Set( + uint32_t object_instance, + void (*load)(void *context, const char *location)); +BACNET_STACK_EXPORT +void Program_Run_Set(uint32_t object_instance, void (*run)(void *context)); +BACNET_STACK_EXPORT +void Program_Halt_Set(uint32_t object_instance, void (*halt)(void *context)); +BACNET_STACK_EXPORT +void Program_Restart_Set( + uint32_t object_instance, void (*restart)(void *context)); +BACNET_STACK_EXPORT +void Program_Unload_Set( + uint32_t object_instance, void (*unload)(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 744c75c1..2bd4ce39 100644 --- a/src/bacnet/basic/server/bacnet_device.c +++ b/src/bacnet/basic/server/bacnet_device.c @@ -46,6 +46,7 @@ #include "bacnet/basic/object/iv.h" #include "bacnet/basic/object/time_value.h" #include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/program.h" #include "bacnet/basic/object/lo.h" #include "bacnet/basic/object/blo.h" #include "bacnet/basic/object/netport.h" @@ -102,6 +103,7 @@ defined(CONFIG_BACNET_BASIC_OBJECT_FILE) || \ defined(CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW) || \ defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM) || \ defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE)) #define CONFIG_BACNET_BASIC_OBJECT_ALL #endif @@ -130,6 +132,8 @@ #define CONFIG_BACNET_BASIC_OBJECT_FILE #define CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW #define CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE +#define CONFIG_BACNET_BASIC_OBJECT_PROGRAM +#define CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE #endif #if (BACNET_PROTOCOL_REVISION < 14) @@ -717,6 +721,28 @@ static object_functions_t Object_Table[] = { CharacterString_Value_Create, CharacterString_Value_Delete, NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM) + { OBJECT_BITSTRING_VALUE, + Program_Init, + Program_Count, + Program_Index_To_Instance, + Program_Valid_Instance, + Program_Object_Name, + Program_Read_Property, + Program_Write_Property, + Program_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 */, + Program_Create, + Program_Delete, + Program_Timer }, #endif { MAX_BACNET_OBJECT_TYPE, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4958e4b6..ac3ba45a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -160,6 +160,7 @@ list(APPEND testdirs bacnet/basic/object/mso bacnet/basic/object/msv bacnet/basic/object/netport + bacnet/basic/object/program bacnet/basic/object/nc bacnet/basic/object/objects bacnet/basic/object/osv diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index 18899101..d5d5003e 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -75,6 +75,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/netport.c ${SRC_DIR}/bacnet/basic/object/osv.c ${SRC_DIR}/bacnet/basic/object/piv.c + ${SRC_DIR}/bacnet/basic/object/program.c ${SRC_DIR}/bacnet/basic/object/schedule.c ${SRC_DIR}/bacnet/basic/object/structured_view.c ${SRC_DIR}/bacnet/basic/object/time_value.c diff --git a/test/bacnet/basic/object/program/CMakeLists.txt b/test/bacnet/basic/object/program/CMakeLists.txt new file mode 100644 index 00000000..f471b3ea --- /dev/null +++ b/test/bacnet/basic/object/program/CMakeLists.txt @@ -0,0 +1,72 @@ +# 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/program.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/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/cov.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/proplist.c + ${SRC_DIR}/bacnet/memcopy.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/wp.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.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 + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + # Test and test library files + ./src/main.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/program/src/main.c b/test/bacnet/basic/object/program/src/main.c new file mode 100644 index 00000000..59d3c3a5 --- /dev/null +++ b/test/bacnet/basic/object/program/src/main.c @@ -0,0 +1,60 @@ +/** + * @file + * @brief Unit test for the Program object type + * @author Steve Karg + * @date March 2025 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(ao_tests, testProgramObject) +#else +static void testProgramObject(void) +#endif +{ + bool status = false; + unsigned count = 0; + uint32_t object_instance = BACNET_MAX_INSTANCE, test_object_instance = 0; + const int skip_fail_property_list[] = { -1 }; + + Program_Init(); + object_instance = Program_Create(object_instance); + count = Program_Count(); + zassert_true(count == 1, NULL); + test_object_instance = Program_Index_To_Instance(0); + zassert_equal(object_instance, test_object_instance, NULL); + bacnet_object_properties_read_write_test( + OBJECT_PROGRAM, object_instance, Program_Property_Lists, + Program_Read_Property, Program_Write_Property, skip_fail_property_list); + bacnet_object_name_ascii_test( + object_instance, Program_Name_Set, Program_Name_ASCII); + status = Program_Delete(object_instance); + zassert_true(status, NULL); +} +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(program_object_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(program_object_tests, ztest_unit_test(testProgramObject)); + + ztest_run_test_suite(program_object_tests); +} +#endif