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