From 3e480f41e20604f187ac4a9d7558ceda10b8b53d Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 27 Feb 2025 15:49:29 -0600 Subject: [PATCH] Ported bacnet-basic from zephyr project to create basis for mini example. (#933) --- CMakeLists.txt | 23 +- Makefile | 4 + apps/Makefile | 13 +- apps/server-basic/Makefile | 71 + apps/server-basic/main.c | 186 ++ apps/server-client/Makefile | 1 - src/bacnet/basic/object/ao.c | 54 +- src/bacnet/basic/object/blo.c | 55 +- src/bacnet/basic/object/bo.c | 43 +- src/bacnet/basic/object/csv.c | 4 +- src/bacnet/basic/object/csv.h | 4 +- src/bacnet/basic/object/device.c | 33 +- src/bacnet/basic/object/lo.c | 62 +- src/bacnet/basic/object/mso.c | 9 +- src/bacnet/basic/server/bacnet_basic.c | 276 +++ src/bacnet/basic/server/bacnet_basic.h | 59 + src/bacnet/basic/server/bacnet_device.c | 2054 ++++++++++++++++++++ src/bacnet/basic/server/bacnet_port.c | 66 + src/bacnet/basic/server/bacnet_port.h | 27 + src/bacnet/basic/server/bacnet_port_ipv4.c | 101 + src/bacnet/basic/server/bacnet_port_ipv4.h | 30 + src/bacnet/basic/server/bacnet_port_ipv6.c | 104 + src/bacnet/basic/server/bacnet_port_ipv6.h | 30 + 23 files changed, 3162 insertions(+), 147 deletions(-) create mode 100644 apps/server-basic/Makefile create mode 100644 apps/server-basic/main.c create mode 100644 src/bacnet/basic/server/bacnet_basic.c create mode 100644 src/bacnet/basic/server/bacnet_basic.h create mode 100644 src/bacnet/basic/server/bacnet_device.c create mode 100644 src/bacnet/basic/server/bacnet_port.c create mode 100644 src/bacnet/basic/server/bacnet_port.h create mode 100644 src/bacnet/basic/server/bacnet_port_ipv4.c create mode 100644 src/bacnet/basic/server/bacnet_port_ipv4.h create mode 100644 src/bacnet/basic/server/bacnet_port_ipv6.c create mode 100644 src/bacnet/basic/server/bacnet_port_ipv6.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ab427fe..2bde7490 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,11 @@ option( ON) option( + BACNET_BUILD_BACMINI_APP + "compile the bacmini app" + ON) + + option( BACNET_BUILD_PIFACE_APP "compile the piface app" OFF) @@ -84,7 +89,7 @@ option( "build with uci" OFF) -set(BACNET_PROTOCOL_REVISION 19) +set(BACNET_PROTOCOL_REVISION 24) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) @@ -912,6 +917,22 @@ if(BACNET_STACK_BUILD_APPS) target_link_libraries(piface PRIVATE ${PROJECT_NAME}) endif(BACNET_BUILD_PIFACE_APP) + if(BACNET_BUILD_BACMINI_APP) + add_executable(bacmini + apps/server-basic/main.c + src/bacnet/basic/server/bacnet_basic.c + src/bacnet/basic/server/bacnet_device.c + src/bacnet/basic/server/bacnet_port.c + src/bacnet/basic/server/bacnet_port_ipv4.c + src/bacnet/basic/server/bacnet_port_ipv6.c + ) + target_link_libraries(bacmini PRIVATE ${PROJECT_NAME}) + target_compile_options(bacmini PRIVATE + # Unreachable code because we have endless loop. + $<$:/wd4702> + ) + endif(BACNET_BUILD_BACMINI_APP) + if(BACNET_BUILD_BACPOLL_APP) add_executable(bacpoll apps/server-client/main.c diff --git a/Makefile b/Makefile index 59d6dc76..ec8d6535 100644 --- a/Makefile +++ b/Makefile @@ -202,6 +202,10 @@ netnumis: server: $(MAKE) -s -C apps $@ +.PHONY: server-basic +server-basic: + $(MAKE) LEGACY=true NOTIFY=false -s -C apps $@ + .PHONY: server-client server-client: $(MAKE) LEGACY=true -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 8d99fdad..c29de1f8 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -184,9 +184,14 @@ ifeq (${LEGACY},true) BACNET_DEFINES += -DBACNET_STACK_DEPRECATED_DISABLE endif +ifeq (${NOTIFY},false) +# disable intrinsic reporting +else +BACNET_DEFINES += -DINTRINSIC_REPORTING +endif + BACNET_DEFINES += -DPRINT_ENABLED=1 BACNET_DEFINES += -DBACAPP_ALL -BACNET_DEFINES += -DINTRINSIC_REPORTING BACNET_DEFINES += -DBACNET_TIME_MASTER BACNET_DEFINES += -DBACNET_PROPERTY_LISTS=1 BACNET_DEFINES += -DBACNET_PROTOCOL_REVISION=24 @@ -217,7 +222,7 @@ SUBDIRS = lib readprop writeprop readfile writefile reinit server dcc \ whohas whois iam ucov scov timesync epics readpropm readrange \ writepropm uptransfer getevent uevent abort error event ack-alarm \ server-client add-list-element remove-list-element create-object \ - delete-object server-discover apdu writegroup + delete-object server-discover apdu writegroup server-basic ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter whatisnetnum netnumis @@ -406,6 +411,10 @@ scov: $(BACNET_LIB_TARGET) server: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ +.PHONY: server-basic +server-basic: $(BACNET_LIB_TARGET) + $(MAKE) -B -C $@ + .PHONY: server-client server-client: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ diff --git a/apps/server-basic/Makefile b/apps/server-basic/Makefile new file mode 100644 index 00000000..c8ed733b --- /dev/null +++ b/apps/server-basic/Makefile @@ -0,0 +1,71 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = bacmini +# BACnet objects supporting CreateObject that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +BACNET_SERVER_DIR = $(BACNET_SRC_DIR)/bacnet/basic/server +SRC = main.c \ + $(BACNET_SERVER_DIR)/bacnet_basic.c \ + $(BACNET_SERVER_DIR)/bacnet_device.c \ + $(BACNET_SERVER_DIR)/bacnet_port.c \ + $(BACNET_SERVER_DIR)/bacnet_port_ipv4.c \ + $(BACNET_SERVER_DIR)/bacnet_port_ipv6.c \ + $(BACNET_OBJECT_DIR)/ai.c \ + $(BACNET_OBJECT_DIR)/ao.c \ + $(BACNET_OBJECT_DIR)/av.c \ + $(BACNET_OBJECT_DIR)/bacfile.c \ + $(BACNET_OBJECT_DIR)/bi.c \ + $(BACNET_OBJECT_DIR)/bitstring_value.c \ + $(BACNET_OBJECT_DIR)/blo.c \ + $(BACNET_OBJECT_DIR)/bo.c \ + $(BACNET_OBJECT_DIR)/bv.c \ + $(BACNET_OBJECT_DIR)/calendar.c \ + $(BACNET_OBJECT_DIR)/channel.c \ + $(BACNET_OBJECT_DIR)/color_object.c \ + $(BACNET_OBJECT_DIR)/color_temperature.c \ + $(BACNET_OBJECT_DIR)/command.c \ + $(BACNET_OBJECT_DIR)/csv.c \ + $(BACNET_OBJECT_DIR)/iv.c \ + $(BACNET_OBJECT_DIR)/lc.c \ + $(BACNET_OBJECT_DIR)/lo.c \ + $(BACNET_OBJECT_DIR)/lsp.c \ + $(BACNET_OBJECT_DIR)/lsz.c \ + $(BACNET_OBJECT_DIR)/ms-input.c \ + $(BACNET_OBJECT_DIR)/mso.c \ + $(BACNET_OBJECT_DIR)/msv.c \ + $(BACNET_OBJECT_DIR)/nc.c \ + $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_OBJECT_DIR)/osv.c \ + $(BACNET_OBJECT_DIR)/structured_view.c \ + $(BACNET_OBJECT_DIR)/time_value.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend diff --git a/apps/server-basic/main.c b/apps/server-basic/main.c new file mode 100644 index 00000000..d4e5699f --- /dev/null +++ b/apps/server-basic/main.c @@ -0,0 +1,186 @@ +/** + * @file + * @brief BACnet Stack sample Smart Sensor (B-SS) main file + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bactext.h" +#include "bacnet/version.h" +/* some demo stuff needed */ +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/server/bacnet_basic.h" +#include "bacnet/basic/server/bacnet_port.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/ai.h" +#include "bacnet/basic/object/ao.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" + +static const char *Device_Name = "BACnet Smart Sensor (B-SS)"; +#define SENSOR_ID 1 + +/* table of default point names and units */ +struct BACnetObjectTable { + const uint8_t type; + const uint8_t instance; + const uint16_t units; + const float value; + const char *name; + const char *description; +}; +static struct BACnetObjectTable Object_Table[] = { + { OBJECT_ANALOG_INPUT, SENSOR_ID, UNITS_DEGREES_CELSIUS, 25.0f, + "Indoor Air Temperature", "indoor air temperature" }, + { OBJECT_ANALOG_OUTPUT, 1, UNITS_DEGREES_ANGULAR, 0.0f, "VAV Actuator", + "variable air valve actuator" }, + { OBJECT_ANALOG_OUTPUT, 2, UNITS_DEGREES_CELSIUS, 25.0f, + "Temperature Setpoint", "indoor air temperature setpoint" }, +}; +/* timer for Sensor Update Interval */ +static struct mstimer Sensor_Update_Timer; + +/** + * @brief BACnet Project Initialization Handler + * @param context [in] The context to pass to the callback function + * @note This is called from the BACnet task + */ +static void BACnet_Object_Table_Init(void *context) +{ + unsigned i; + + (void)context; + /* initialize child objects for this basic sample */ + for (i = 0; i < ARRAY_SIZE(Object_Table); i++) { + switch (Object_Table[i].type) { + case OBJECT_ANALOG_INPUT: + Analog_Input_Create(Object_Table[i].instance); + Analog_Input_Name_Set( + Object_Table[i].instance, Object_Table[i].name); + Analog_Input_Present_Value_Set( + Object_Table[i].instance, Object_Table[i].value); + Analog_Input_Units_Set( + Object_Table[i].instance, Object_Table[i].units); + Analog_Input_Description_Set( + Object_Table[i].instance, Object_Table[i].description); + break; + case OBJECT_ANALOG_OUTPUT: + Analog_Output_Create(Object_Table[i].instance); + Analog_Output_Name_Set( + Object_Table[i].instance, Object_Table[i].name); + Analog_Output_Relinquish_Default_Set( + Object_Table[i].instance, Object_Table[i].value); + Analog_Output_Units_Set( + Object_Table[i].instance, Object_Table[i].units); + Analog_Output_Description_Set( + Object_Table[i].instance, Object_Table[i].description); + break; + default: + break; + } + } + /* start the seconds cyclic timer */ + mstimer_set(&Sensor_Update_Timer, 1000); + srand(0); +} + +/** + * @brief BACnet Project Task Handler + * @param context [in] The context to pass to the callback function + * @note This is called from the BACnet task + */ +static void BACnet_Object_Task(void *context) +{ + float temperature = 0.0f, change = 0.0f; + + (void)context; + if (mstimer_expired(&Sensor_Update_Timer)) { + mstimer_reset(&Sensor_Update_Timer); + /* simulate a sensor reading, and update the BACnet object values */ + if (Analog_Input_Out_Of_Service(SENSOR_ID)) { + return; + } + temperature = Analog_Input_Present_Value(SENSOR_ID); + change = -1.0f + 2.0f * ((float)rand()) / RAND_MAX; + temperature += change; + Analog_Input_Present_Value_Set(SENSOR_ID, temperature); + } +} + +/** + * @brief Store the BACnet data after a WriteProperty for object property + * @param object_type - BACnet object type + * @param object_instance - BACnet object instance + * @param object_property - BACnet object property + * @param array_index - BACnet array index + * @param application_data - pointer to the data + * @param application_data_len - length of the data + */ +static void BACnet_Basic_Store( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + int application_data_len) +{ + (void)array_index; + (void)application_data; + (void)application_data_len; + debug_printf_stdout( + "BACnet Store: %s[%lu]-%s\n", bactext_object_type_name(object_type), + (unsigned long)object_instance, bactext_property_name(object_property)); +} + +/** Main function of server demo. + * + * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, + * datalink_receive, npdu_handler, + * dcc_timer_seconds, datalink_maintenance_timer, + * handler_cov_task, tsm_timer_milliseconds + * + * @param argc [in] Arg count. + * @param argv [in] Takes one argument: the Device Instance #. + * @return 0 on success. + */ +int main(int argc, char *argv[]) +{ + if (argc > 1) { + /* allow the device ID to be set */ + Device_Set_Object_Instance_Number(strtol(argv[1], NULL, 0)); + } + if (argc > 2) { + /* allow the device name to be set */ + Device_Name = argv[2]; + } + Device_Object_Name_ANSI_Init(Device_Name); + debug_printf_stdout("BACnet Device: %s\n", Device_Name); + debug_printf_stdout("BACnet Stack Version %s\n", BACNET_VERSION_TEXT); + debug_printf_stdout("BACnet Stack Max APDU: %d\n", MAX_APDU); + bacnet_basic_init_callback_set(BACnet_Object_Table_Init, NULL); + bacnet_basic_task_callback_set(BACnet_Object_Task, NULL); + bacnet_basic_store_callback_set(BACnet_Basic_Store); + bacnet_basic_init(); + if (bacnet_port_init()) { + /* OS based apps use DLENV for environment variables */ + dlenv_init(); + atexit(datalink_cleanup); + } + debug_printf_stdout("Server: initialized\n"); + for (;;) { + bacnet_basic_task(); + bacnet_port_task(); + } + + return 0; +} diff --git a/apps/server-client/Makefile b/apps/server-client/Makefile index fd9001fa..807dc343 100644 --- a/apps/server-client/Makefile +++ b/apps/server-client/Makefile @@ -3,7 +3,6 @@ # Executable file name - BACnet Server-Client Polling Application TARGET = bacpoll # BACnet objects that are used with this app -# BACnet objects that are used with this app BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object BACNET_CLIENT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/client SRC = main.c \ diff --git a/src/bacnet/basic/object/ao.c b/src/bacnet/basic/object/ao.c index 7635ca41..86d06b99 100644 --- a/src/bacnet/basic/object/ao.c +++ b/src/bacnet/basic/object/ao.c @@ -57,7 +57,8 @@ static analog_output_write_present_value_callback /* These three arrays are used by the ReadPropertyMultiple handler */ -static const int Analog_Output_Properties_Required[] = { +static const int Properties_Required[] = { + /* unordered list of required properties */ PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, @@ -74,12 +75,13 @@ static const int Analog_Output_Properties_Required[] = { -1 }; -static const int Analog_Output_Properties_Optional[] = { +static const int Properties_Optional[] = { + /* unordered list of optional properties */ PROP_RELIABILITY, PROP_DESCRIPTION, PROP_COV_INCREMENT, PROP_MIN_PRES_VALUE, PROP_MAX_PRES_VALUE, -1 }; -static const int Analog_Output_Properties_Proprietary[] = { -1 }; +static const int Properties_Proprietary[] = { -1 }; /** * @brief Returns the list of required, optional, and proprietary properties. @@ -95,13 +97,13 @@ void Analog_Output_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) { if (pRequired) { - *pRequired = Analog_Output_Properties_Required; + *pRequired = Properties_Required; } if (pOptional) { - *pOptional = Analog_Output_Properties_Optional; + *pOptional = Properties_Optional; } if (pProprietary) { - *pProprietary = Analog_Output_Properties_Proprietary; + *pProprietary = Properties_Proprietary; } return; @@ -985,7 +987,13 @@ int Analog_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) (rpdata->application_data_len == 0)) { return 0; } - + if (!property_lists_member( + Properties_Required, Properties_Optional, Properties_Proprietary, + rpdata->object_property)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + return BACNET_STATUS_ERROR; + } apdu = rpdata->application_data; apdu_size = rpdata->application_data_len; switch (rpdata->object_property) { @@ -1069,7 +1077,6 @@ int Analog_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_real( &apdu[0], Analog_Output_COV_Increment(rpdata->object_instance)); break; -#if (BACNET_PROTOCOL_REVISION >= 17) case PROP_CURRENT_COMMAND_PRIORITY: i = Analog_Output_Present_Value_Priority(rpdata->object_instance); if ((i >= BACNET_MIN_PRIORITY) && (i <= BACNET_MAX_PRIORITY)) { @@ -1078,7 +1085,6 @@ int Analog_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_null(&apdu[0]); } break; -#endif default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; @@ -1139,28 +1145,16 @@ bool Analog_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->object_instance, value.type.Boolean); } break; - case PROP_COV_INCREMENT: - case PROP_OBJECT_IDENTIFIER: - case PROP_OBJECT_TYPE: - case PROP_OBJECT_NAME: - case PROP_STATUS_FLAGS: - case PROP_EVENT_STATE: - case PROP_UNITS: - case PROP_RELIABILITY: - case PROP_PRIORITY_ARRAY: - case PROP_RELINQUISH_DEFAULT: - case PROP_MAX_PRES_VALUE: - case PROP_MIN_PRES_VALUE: - case PROP_DESCRIPTION: -#if (BACNET_PROTOCOL_REVISION >= 17) - case PROP_CURRENT_COMMAND_PRIORITY: -#endif - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + 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; } diff --git a/src/bacnet/basic/object/blo.c b/src/bacnet/basic/object/blo.c index 97dbb6ff..d3775546 100644 --- a/src/bacnet/basic/object/blo.c +++ b/src/bacnet/basic/object/blo.c @@ -59,7 +59,8 @@ static binary_lighting_output_blink_warn_callback /* These arrays are used by the ReadPropertyMultiple handler and property-list property (as of protocol-revision 14) */ -static const int Binary_Lighting_Output_Properties_Required[] = { +static const int Properties_Required[] = { + /* unordered list of required properties */ PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, @@ -76,11 +77,12 @@ static const int Binary_Lighting_Output_Properties_Required[] = { #endif -1 }; -static const int Binary_Lighting_Output_Properties_Optional[] = { +static const int Properties_Optional[] = { + /* unordered list of optional properties */ PROP_DESCRIPTION, PROP_RELIABILITY, PROP_FEEDBACK_VALUE, -1 }; -static const int Binary_Lighting_Output_Properties_Proprietary[] = { -1 }; +static const int Properties_Proprietary[] = { -1 }; /** * Returns the list of required, optional, and proprietary properties. @@ -97,13 +99,13 @@ void Binary_Lighting_Output_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) { if (pRequired) { - *pRequired = Binary_Lighting_Output_Properties_Required; + *pRequired = Properties_Required; } if (pOptional) { - *pOptional = Binary_Lighting_Output_Properties_Optional; + *pOptional = Properties_Optional; } if (pProprietary) { - *pProprietary = Binary_Lighting_Output_Properties_Proprietary; + *pProprietary = Properties_Proprietary; } return; @@ -1263,6 +1265,13 @@ int Binary_Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) (rpdata->application_data_len == 0)) { return 0; } + if (!property_lists_member( + Properties_Required, Properties_Optional, Properties_Proprietary, + rpdata->object_property)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + return BACNET_STATUS_ERROR; + } apdu = rpdata->application_data; apdu_size = rpdata->application_data_len; switch (rpdata->object_property) { @@ -1338,7 +1347,6 @@ int Binary_Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) rpdata->object_instance); apdu_len = encode_application_enumerated(&apdu[0], lighting_value); break; -#if (BACNET_PROTOCOL_REVISION >= 17) case PROP_CURRENT_COMMAND_PRIORITY: i = Binary_Lighting_Output_Present_Value_Priority( rpdata->object_instance); @@ -1348,7 +1356,6 @@ int Binary_Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_null(&apdu[0]); } break; -#endif case PROP_DESCRIPTION: characterstring_init_ansi( &char_string, @@ -1424,30 +1431,16 @@ bool Binary_Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->object_instance, value.type.Boolean); } break; - case PROP_OBJECT_IDENTIFIER: - case PROP_OBJECT_NAME: - case PROP_OBJECT_TYPE: - case PROP_TRACKING_VALUE: - case PROP_IN_PROGRESS: - case PROP_STATUS_FLAGS: - case PROP_BLINK_WARN_ENABLE: - case PROP_EGRESS_TIME: - case PROP_EGRESS_ACTIVE: - case PROP_PRIORITY_ARRAY: - case PROP_RELINQUISH_DEFAULT: - case PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY: -#if (BACNET_PROTOCOL_REVISION >= 17) - case PROP_CURRENT_COMMAND_PRIORITY: -#endif - case PROP_DESCRIPTION: - case PROP_RELIABILITY: - case PROP_FEEDBACK_VALUE: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + 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; } diff --git a/src/bacnet/basic/object/bo.c b/src/bacnet/basic/object/bo.c index 0c5bdc0c..33752ccb 100644 --- a/src/bacnet/basic/object/bo.c +++ b/src/bacnet/basic/object/bo.c @@ -56,24 +56,28 @@ static binary_output_write_present_value_callback Binary_Output_Write_Present_Value_Callback; /* These three arrays are used by the ReadPropertyMultiple handler */ -static const int Properties_Required[] = { PROP_OBJECT_IDENTIFIER, - PROP_OBJECT_NAME, - PROP_OBJECT_TYPE, - PROP_PRESENT_VALUE, - PROP_STATUS_FLAGS, - PROP_EVENT_STATE, - PROP_OUT_OF_SERVICE, - PROP_POLARITY, - PROP_PRIORITY_ARRAY, - PROP_RELINQUISH_DEFAULT, +static const int Properties_Required[] = { + /* unordered list of required properties */ + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, + PROP_STATUS_FLAGS, + PROP_EVENT_STATE, + PROP_OUT_OF_SERVICE, + PROP_POLARITY, + PROP_PRIORITY_ARRAY, + PROP_RELINQUISH_DEFAULT, #if (BACNET_PROTOCOL_REVISION >= 17) - PROP_CURRENT_COMMAND_PRIORITY, + PROP_CURRENT_COMMAND_PRIORITY, #endif - -1 }; + -1 +}; -static const int Properties_Optional[] = { PROP_RELIABILITY, PROP_DESCRIPTION, - PROP_ACTIVE_TEXT, PROP_INACTIVE_TEXT, - -1 }; +static const int Properties_Optional[] = { + /* unordered list of optional properties */ + PROP_RELIABILITY, PROP_DESCRIPTION, PROP_ACTIVE_TEXT, PROP_INACTIVE_TEXT, -1 +}; static const int Properties_Proprietary[] = { -1 }; @@ -987,6 +991,13 @@ int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) (rpdata->application_data_len == 0)) { return 0; } + if (!property_lists_member( + Properties_Required, Properties_Optional, Properties_Proprietary, + rpdata->object_property)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + return BACNET_STATUS_ERROR; + } apdu = rpdata->application_data; apdu_size = rpdata->application_data_len; switch (rpdata->object_property) { @@ -1073,7 +1084,6 @@ int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_character_string(&apdu[0], &char_string); break; -#if (BACNET_PROTOCOL_REVISION >= 17) case PROP_CURRENT_COMMAND_PRIORITY: i = Binary_Output_Present_Value_Priority(rpdata->object_instance); if ((i >= BACNET_MIN_PRIORITY) && (i <= BACNET_MAX_PRIORITY)) { @@ -1082,7 +1092,6 @@ int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_null(&apdu[0]); } break; -#endif default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; diff --git a/src/bacnet/basic/object/csv.c b/src/bacnet/basic/object/csv.c index bce11e1c..249242f5 100644 --- a/src/bacnet/basic/object/csv.c +++ b/src/bacnet/basic/object/csv.c @@ -125,7 +125,7 @@ uint32_t CharacterString_Value_Create(uint32_t object_instance) * @param object_instance - object-instance number of the object * @return true if the object is deleted */ -bool Characterstring_Value_Delete(uint32_t object_instance) +bool CharacterString_Value_Delete(uint32_t object_instance) { bool status = false; struct object_data *pObject = NULL; @@ -142,7 +142,7 @@ bool Characterstring_Value_Delete(uint32_t object_instance) /** * @brief Cleans up the object list and its data */ -void Characterstring_Value_Cleanup(void) +void CharacterString_Value_Cleanup(void) { struct object_data *pObject = NULL; diff --git a/src/bacnet/basic/object/csv.h b/src/bacnet/basic/object/csv.h index 0c301b15..577d2697 100644 --- a/src/bacnet/basic/object/csv.h +++ b/src/bacnet/basic/object/csv.h @@ -27,9 +27,9 @@ void CharacterString_Value_Property_Lists( BACNET_STACK_EXPORT uint32_t CharacterString_Value_Create(uint32_t object_instance); BACNET_STACK_EXPORT -bool Characterstring_Value_Delete(uint32_t object_instance); +bool CharacterString_Value_Delete(uint32_t object_instance); BACNET_STACK_EXPORT -void Characterstring_Value_Cleanup(void); +void CharacterString_Value_Cleanup(void); BACNET_STACK_EXPORT bool CharacterString_Value_Valid_Instance(uint32_t object_instance); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 686b7283..4eebf6db 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -52,28 +52,18 @@ #if defined(BACFILE) #include "bacnet/basic/object/bacfile.h" #endif /* defined(BACFILE) */ -#if (BACNET_PROTOCOL_REVISION >= 10) #include "bacnet/basic/object/bitstring_value.h" #include "bacnet/basic/object/csv.h" #include "bacnet/basic/object/iv.h" #include "bacnet/basic/object/osv.h" #include "bacnet/basic/object/piv.h" #include "bacnet/basic/object/time_value.h" -#endif -#if (BACNET_PROTOCOL_REVISION >= 14) #include "bacnet/basic/object/channel.h" #include "bacnet/basic/object/lo.h" -#endif -#if (BACNET_PROTOCOL_REVISION >= 16) #include "bacnet/basic/object/blo.h" -#endif -#if (BACNET_PROTOCOL_REVISION >= 17) #include "bacnet/basic/object/netport.h" -#endif -#if (BACNET_PROTOCOL_REVISION >= 24) #include "bacnet/basic/object/color_object.h" #include "bacnet/basic/object/color_temperature.h" -#endif /* external prototypes */ extern int Routed_Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); @@ -187,7 +177,8 @@ static object_functions_t My_Object_Table[] = { CharacterString_Value_Change_Of_Value, CharacterString_Value_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, - NULL /* Remove_List_Element */, NULL /* Create */, NULL /* Delete */, + NULL /* Remove_List_Element */, CharacterString_Value_Create, + CharacterString_Value_Delete, NULL /* Timer */ }, { OBJECT_OCTETSTRING_VALUE, OctetString_Value_Init, OctetString_Value_Count, OctetString_Value_Index_To_Instance, OctetString_Value_Valid_Instance, @@ -215,14 +206,6 @@ 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 */ }, -#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_INTEGER_VALUE, Integer_Value_Init, Integer_Value_Count, Integer_Value_Index_To_Instance, Integer_Value_Valid_Instance, Integer_Value_Object_Name, Integer_Value_Read_Property, @@ -230,8 +213,16 @@ static object_functions_t My_Object_Table[] = { 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) + 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 */ }, + #if defined(INTRINSIC_REPORTING) { OBJECT_NOTIFICATION_CLASS, Notification_Class_Init, Notification_Class_Count, Notification_Class_Index_To_Instance, Notification_Class_Valid_Instance, Notification_Class_Object_Name, diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c index efa618e3..59ee9263 100644 --- a/src/bacnet/basic/object/lo.c +++ b/src/bacnet/basic/object/lo.c @@ -63,7 +63,8 @@ static lighting_command_tracking_value_callback /* These arrays are used by the ReadPropertyMultiple handler and property-list property (as of protocol-revision 14) */ -static const int Lighting_Output_Properties_Required[] = { +static const int Properties_Required[] = { + /* unordered list of required properties */ PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, @@ -87,7 +88,8 @@ static const int Lighting_Output_Properties_Required[] = { #endif -1 }; -static const int Lighting_Output_Properties_Optional[] = { +static const int Properties_Optional[] = { + /* unordered list of optional properties */ PROP_DESCRIPTION, PROP_TRANSITION, #if (BACNET_PROTOCOL_REVISION >= 24) @@ -98,7 +100,7 @@ static const int Lighting_Output_Properties_Optional[] = { -1 }; -static const int Lighting_Output_Properties_Proprietary[] = { -1 }; +static const int Properties_Proprietary[] = { -1 }; /** * Returns the list of required, optional, and proprietary properties. @@ -115,13 +117,13 @@ void Lighting_Output_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) { if (pRequired) { - *pRequired = Lighting_Output_Properties_Required; + *pRequired = Properties_Required; } if (pOptional) { - *pOptional = Lighting_Output_Properties_Optional; + *pOptional = Properties_Optional; } if (pProprietary) { - *pProprietary = Lighting_Output_Properties_Proprietary; + *pProprietary = Properties_Proprietary; } return; @@ -2004,9 +2006,7 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) BACNET_BIT_STRING bit_string; BACNET_CHARACTER_STRING char_string; BACNET_LIGHTING_COMMAND lighting_command; -#if (BACNET_PROTOCOL_REVISION >= 24) BACNET_OBJECT_ID object_id = { 0 }; -#endif float real_value = (float)1.414; uint32_t unsigned_value = 0; unsigned i = 0; @@ -2017,6 +2017,13 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) (rpdata->application_data_len == 0)) { return 0; } + if (!property_lists_member( + Properties_Required, Properties_Optional, Properties_Proprietary, + rpdata->object_property)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + return BACNET_STATUS_ERROR; + } apdu = rpdata->application_data; apdu_size = rpdata->application_data_len; switch (rpdata->object_property) { @@ -2120,7 +2127,6 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) Lighting_Output_Default_Priority(rpdata->object_instance); apdu_len = encode_application_unsigned(&apdu[0], unsigned_value); break; -#if (BACNET_PROTOCOL_REVISION >= 17) case PROP_CURRENT_COMMAND_PRIORITY: i = Lighting_Output_Present_Value_Priority(rpdata->object_instance); if ((i >= BACNET_MIN_PRIORITY) && (i <= BACNET_MAX_PRIORITY)) { @@ -2129,8 +2135,6 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_null(&apdu[0]); } break; -#endif -#if (BACNET_PROTOCOL_REVISION >= 24) case PROP_COLOR_OVERRIDE: apdu_len = encode_application_boolean( &apdu[0], @@ -2148,7 +2152,6 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_object_id( &apdu[0], object_id.type, object_id.instance); break; -#endif case PROP_DESCRIPTION: characterstring_init_ansi( &char_string, @@ -2274,33 +2277,16 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) &wp_data->error_code); } break; - case PROP_OBJECT_IDENTIFIER: - case PROP_OBJECT_NAME: - case PROP_OBJECT_TYPE: - case PROP_TRACKING_VALUE: - case PROP_IN_PROGRESS: - case PROP_STATUS_FLAGS: - case PROP_BLINK_WARN_ENABLE: - case PROP_EGRESS_TIME: - case PROP_EGRESS_ACTIVE: - case PROP_PRIORITY_ARRAY: - case PROP_RELINQUISH_DEFAULT: - case PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY: -#if (BACNET_PROTOCOL_REVISION >= 17) - case PROP_CURRENT_COMMAND_PRIORITY: -#endif -#if (BACNET_PROTOCOL_REVISION >= 24) - case PROP_COLOR_OVERRIDE: - case PROP_COLOR_REFERENCE: - case PROP_OVERRIDE_COLOR_REFERENCE: -#endif - case PROP_DESCRIPTION: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + 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; } diff --git a/src/bacnet/basic/object/mso.c b/src/bacnet/basic/object/mso.c index 99962e6c..db526e45 100644 --- a/src/bacnet/basic/object/mso.c +++ b/src/bacnet/basic/object/mso.c @@ -934,6 +934,13 @@ int Multistate_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) (rpdata->application_data_len == 0)) { return 0; } + if (!property_lists_member( + Properties_Required, Properties_Optional, Properties_Proprietary, + rpdata->object_property)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + return BACNET_STATUS_ERROR; + } apdu = rpdata->application_data; apdu_size = rpdata->application_data_len; switch (rpdata->object_property) { @@ -1024,7 +1031,6 @@ int Multistate_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_character_string(&apdu[0], &char_string); break; -#if (BACNET_PROTOCOL_REVISION >= 17) case PROP_CURRENT_COMMAND_PRIORITY: i = Multistate_Output_Present_Value_Priority( rpdata->object_instance); @@ -1034,7 +1040,6 @@ int Multistate_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_null(&apdu[0]); } break; -#endif default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; diff --git a/src/bacnet/basic/server/bacnet_basic.c b/src/bacnet/basic/server/bacnet_basic.c new file mode 100644 index 00000000..0a0230c7 --- /dev/null +++ b/src/bacnet/basic/server/bacnet_basic.c @@ -0,0 +1,276 @@ +/** + * @file + * @brief BACnet Stack initialization and task handler + * @author Steve Karg + * @date March 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack core API */ +#include "bacnet/npdu.h" +#include "bacnet/dcc.h" +#include "bacnet/iam.h" +/* BACnet Stack basic services */ +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +/* BACnet Stack basic objects */ +#include "bacnet/basic/object/device.h" +/* objects */ +#if (BACNET_PROTOCOL_REVISION >= 17) +#include "bacnet/basic/object/netport.h" +#endif +#include "bacnet/basic/object/device.h" +/* me */ +#include "bacnet/basic/server/bacnet_basic.h" +#include "bacnet/basic/server/bacnet_port.h" + +/* 1s timer for basic non-critical timed tasks */ +static struct mstimer BACnet_Task_Timer; +/* task timer for object functionality */ +static struct mstimer BACnet_Object_Timer; +/* uptimer for BACnet task */ +static unsigned long BACnet_Uptime_Seconds; +/* packet counter for BACnet task */ +static unsigned long BACnet_Packet_Count; +/* local Device ID to track changes */ +static uint32_t Device_ID = 0xFFFFFFFF; +/* callbacks for custom features in BACnet thread */ +static bacnet_basic_callback BACnet_Init_Callback; +static void *BACnet_Init_Context; +static bacnet_basic_callback BACnet_Task_Callback; +static void *BACnet_Task_Context; +static bacnet_basic_store_callback BACnet_Store_Callback; + +/** + * @brief Set the callback for the BACnet initialization + * @param callback [in] The callback function called after initialization + * @param context [in] The context to pass to the callback function + */ +void bacnet_basic_init_callback_set( + bacnet_basic_callback callback, void *context) +{ + BACnet_Init_Callback = callback; + BACnet_Init_Context = context; +} + +/** + * @brief BACnet Task Callback Handler + */ +static void bacnet_init_callback_handler(void) +{ + if (BACnet_Init_Callback) { + BACnet_Init_Callback(BACnet_Init_Context); + } +} + +/** + * @brief Set the callback for the BACnet Task + * @param callback [in] The callback function to call during the task + * @param context [in] The context to pass to the callback function + */ +void bacnet_basic_task_callback_set( + bacnet_basic_callback callback, void *context) +{ + BACnet_Task_Callback = callback; + BACnet_Task_Context = context; +} + +/** + * @brief BACnet Task Callback Handler + */ +static void bacnet_task_callback_handler(void) +{ + if (BACnet_Task_Callback) { + BACnet_Task_Callback(BACnet_Task_Context); + } +} + +/** + * @brief Set the callback for the BACnet WriteProperty Store + * @param callback [in] The callback function to call after a successful + * WriteProperty with the data in BACnet binary encoded format (small!) + */ +void bacnet_basic_store_callback_set(bacnet_basic_store_callback callback) +{ + BACnet_Store_Callback = callback; +} + +/** + * @brief Store the BACnet data after a WriteProperty for object property + * @param object_type - BACnet object type + * @param object_instance - BACnet object instance + * @param object_property - BACnet object property + * @param array_index - BACnet array index + * @param application_data - pointer to the data + * @param application_data_len - length of the data + */ +static void bacnet_store_callback_handler( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + int application_data_len) +{ + if (BACnet_Store_Callback) { + BACnet_Store_Callback( + object_type, object_instance, object_property, array_index, + application_data, application_data_len); + } +} + +/** + * @brief Get the BACnet device uptime in seconds + * @return The number of seconds the BACnet device has been running + */ +unsigned long bacnet_basic_uptime_seconds(void) +{ + return BACnet_Uptime_Seconds; +} + +/** + * @brief Get the BACnet device uptime in seconds + * @return The number of seconds the BACnet device has been running + */ +unsigned long bacnet_basic_packet_count(void) +{ + return BACnet_Packet_Count; +} + +/** + * @brief Set the BACnet task device object timer interval + * @param milliseconds [in] The number of milliseconds for the timer interval + */ +void bacnet_basic_task_object_timer_set(unsigned long milliseconds) +{ + mstimer_set(&BACnet_Object_Timer, milliseconds); +} + +/** + * @brief Store the BACnet data after a successful WriteProperty for + * an object property + * @param wp_data - pointer to the write property data + */ +bool bacnet_basic_write_property_store(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + BACNET_ARRAY_INDEX array_index = BACNET_ARRAY_ALL; + + if (property_list_bacnet_array_member( + wp_data->object_type, wp_data->object_property)) { + array_index = wp_data->array_index; + } else if (wp_data->object_property == PROP_PRESENT_VALUE) { + /* indirect Priority_Array write */ + if (Device_Objects_Property_List_Member( + wp_data->object_type, wp_data->object_instance, + PROP_PRIORITY_ARRAY)) { + /* store the priority as an array index to be used on restore */ + array_index = wp_data->priority; + } + } else { + array_index = wp_data->array_index; + } + bacnet_store_callback_handler( + wp_data->object_type, wp_data->object_instance, + wp_data->object_property, array_index, wp_data->application_data, + wp_data->application_data_len); + + return true; +} + +/** + * @brief Initialize the BACnet device object, the service handlers, and timers + */ +void bacnet_basic_init(void) +{ + /* set up our confirmed service unrecognized service handler - required! */ + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, handler_write_property_multiple); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_SUBSCRIBE_COV, handler_cov_subscribe); + /* handle communication so we can shutup when asked, or restart */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_REINITIALIZE_DEVICE, handler_reinitialize_device); + /* start the 1 second timer for non-critical cyclic tasks */ + mstimer_set(&BACnet_Task_Timer, 1000L); + /* start the timer for more time sensitive object specific cyclic tasks */ + if (mstimer_interval(&BACnet_Object_Timer) == 0) { + mstimer_set(&BACnet_Object_Timer, 100UL); + } + Device_Write_Property_Store_Callback_Set(bacnet_basic_write_property_store); + Device_Init(NULL); + /* initialize user data in this thread */ + bacnet_init_callback_handler(); +} + +/* local buffer for incoming PDUs to process */ +static uint8_t PDUBuffer[MAX_MPDU]; + +/** + * @brief non-blocking BACnet task + */ +void bacnet_basic_task(void) +{ + bool hello_world = false; + uint16_t pdu_len = 0; + BACNET_ADDRESS src = { 0 }; + uint32_t elapsed_milliseconds = 0; + uint32_t elapsed_seconds = 0; + + /* hello, World! */ + if (Device_ID != Device_Object_Instance_Number()) { + Device_ID = Device_Object_Instance_Number(); + hello_world = true; + } + if (hello_world) { + Send_I_Am(&Handler_Transmit_Buffer[0]); + } + /* handle non-time-critical cyclic tasks */ + if (mstimer_expired(&BACnet_Task_Timer)) { + /* 1 second tasks */ + mstimer_reset(&BACnet_Task_Timer); + /* presume that the elapsed time is the interval time */ + elapsed_milliseconds = mstimer_interval(&BACnet_Task_Timer); + elapsed_seconds = elapsed_milliseconds / 1000; + BACnet_Uptime_Seconds += elapsed_seconds; + dcc_timer_seconds(elapsed_seconds); + datalink_maintenance_timer(elapsed_seconds); + handler_cov_timer_seconds(elapsed_seconds); + } + while (!handler_cov_fsm()) { + /* waiting for COV processing to be IDLE */ + } + /* object specific cyclic tasks */ + if (mstimer_expired(&BACnet_Object_Timer)) { + elapsed_milliseconds = mstimer_elapsed(&BACnet_Object_Timer); + mstimer_restart(&BACnet_Object_Timer); + Device_Timer(elapsed_milliseconds); + } + /* handle the messaging */ + pdu_len = datalink_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0); + if (pdu_len) { + npdu_handler(&src, &PDUBuffer[0], pdu_len); + BACnet_Packet_Count++; + } + /* call user task in this thread */ + bacnet_task_callback_handler(); +} diff --git a/src/bacnet/basic/server/bacnet_basic.h b/src/bacnet/basic/server/bacnet_basic.h new file mode 100644 index 00000000..84a6b0aa --- /dev/null +++ b/src/bacnet/basic/server/bacnet_basic.h @@ -0,0 +1,59 @@ +/** + * @file + * @brief BACnet Basic Stack initialization and basic task handler + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_BASIC_H +#define BACNET_BASIC_H + +#include +#include +#include "bacnet/bacdef.h" + +/** + * @brief Callback function for BACnet initialization and task + * @param context - pointer to the context + */ +typedef void (*bacnet_basic_callback)(void *context); + +/** + * @brief Store the BACnet data after a WriteProperty for object property + * @param object_type - BACnet object type + * @param object_instance - BACnet object instance + * @param object_property - BACnet object property + * @param array_index - BACnet array index + * @param application_data - pointer to the data + * @param application_data_len - length of the data + */ +typedef void (*bacnet_basic_store_callback)( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + int application_data_len); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_basic_init(void); +void bacnet_basic_init_callback_set( + bacnet_basic_callback callback, void *context); + +void bacnet_basic_task(void); +void bacnet_basic_task_callback_set( + bacnet_basic_callback callback, void *context); +void bacnet_basic_task_object_timer_set(unsigned long milliseconds); + +void bacnet_basic_store_callback_set(bacnet_basic_store_callback callback); + +unsigned long bacnet_basic_uptime_seconds(void); +unsigned long bacnet_basic_packet_count(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c new file mode 100644 index 00000000..744c75c1 --- /dev/null +++ b/src/bacnet/basic/server/bacnet_device.c @@ -0,0 +1,2054 @@ +/** + * @file + * @brief Base "class" for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. + * @author Steve Karg + * @date March 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacstr.h" +#include "bacnet/bacenum.h" +#include "bacnet/apdu.h" +#include "bacnet/dcc.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/property.h" +#include "bacnet/version.h" +#include "bacnet/basic/services.h" +/* objects */ +#include "bacnet/basic/object/acc.h" +#include "bacnet/basic/object/ai.h" +#include "bacnet/basic/object/ao.h" +#include "bacnet/basic/object/av.h" +#include "bacnet/basic/object/bi.h" +#include "bacnet/basic/object/bo.h" +#include "bacnet/basic/object/bv.h" +#include "bacnet/basic/object/calendar.h" +#include "bacnet/basic/object/command.h" +#include "bacnet/basic/object/lc.h" +#include "bacnet/basic/object/lsp.h" +#include "bacnet/basic/object/lsz.h" +#include "bacnet/basic/object/ms-input.h" +#include "bacnet/basic/object/mso.h" +#include "bacnet/basic/object/msv.h" +#include "bacnet/basic/object/schedule.h" +#include "bacnet/basic/object/structured_view.h" +#include "bacnet/basic/object/trendlog.h" +#include "bacnet/basic/object/nc.h" +#include "bacnet/basic/object/bacfile.h" +#include "bacnet/basic/object/bitstring_value.h" +#include "bacnet/basic/object/csv.h" +#include "bacnet/basic/object/iv.h" +#include "bacnet/basic/object/time_value.h" +#include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/lo.h" +#include "bacnet/basic/object/blo.h" +#include "bacnet/basic/object/netport.h" +#include "bacnet/basic/object/color_object.h" +#include "bacnet/basic/object/color_temperature.h" +#include "bacnet/basic/object/device.h" + +#ifdef CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION +#define BACNET_DEVICE_VERSION CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION +#else +#define BACNET_DEVICE_VERSION "1.0.0" +#endif + +#ifdef CONFIG_BACNET_BASIC_DEVICE_OBJECT_NAME +#define BACNET_DEVICE_OBJECT_NAME CONFIG_BACNET_BASIC_DEVICE_OBJECT_NAME +#else +#define BACNET_DEVICE_OBJECT_NAME "BACnet Basic Device" +#endif + +#ifdef CONFIG_BACNET_BASIC_DEVICE_DESCRIPTION +#define BACNET_DEVICE_DESCRIPTION CONFIG_BACNET_BASIC_DEVICE_DESCRIPTION +#else +#define BACNET_DEVICE_DESCRIPTION "BACnet Basic Server Device" +#endif + +#ifdef CONFIG_BACNET_BASIC_DEVICE_MODEL_NAME +#define BACNET_DEVICE_MODEL_NAME CONFIG_BACNET_BASIC_DEVICE_MODEL_NAME +#else +#define BACNET_DEVICE_MODEL_NAME "GNU Basic Server Model 42" +#endif + +#if !( \ + defined(CONFIG_BACNET_BASIC_OBJECT_ALL) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_CALENDAR) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_ZONE) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_CHANNEL) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_COLOR) || \ + defined(CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE) || \ + 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_CHARACTERSTRING_VALUE)) +#define CONFIG_BACNET_BASIC_OBJECT_ALL +#endif + +#if defined(CONFIG_BACNET_BASIC_OBJECT_ALL) +#define CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT +#define CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT +#define CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE +#define CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT +#define CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT +#define CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE +#define CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT +#define CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT +#define CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE +#define CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT +#define CONFIG_BACNET_BASIC_OBJECT_CALENDAR +#define CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE +#define CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT +#define CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_ZONE +#define CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT +#define CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL +#define CONFIG_BACNET_BASIC_OBJECT_CHANNEL +#define CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT +#define CONFIG_BACNET_BASIC_OBJECT_COLOR +#define CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE +#define CONFIG_BACNET_BASIC_OBJECT_FILE +#define CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW +#define CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE +#endif + +#if (BACNET_PROTOCOL_REVISION < 14) +#ifdef CONFIG_BACNET_BASIC_OBJECT_CHANNEL +#undef CONFIG_BACNET_BASIC_OBJECT_CHANNEL +#warning "Channel configured, but BACnet Protocol Revision < 14" +#endif +#ifdef CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT +#undef CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT +#warning "Lighting Output configured, but BACnet Protocol Revision < 14" +#endif +#endif + +#if (BACNET_PROTOCOL_REVISION < 16) +#ifdef CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT +#undef CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT +#warning "Binary Lighting Output configured, but BACnet Protocol Revision < 16" +#endif +#endif + +#if (BACNET_PROTOCOL_REVISION < 17) +#ifdef CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT +#undef CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT +#warning "Network Port is configured, but BACnet Protocol Revision < 17" +#endif +#endif + +#if (BACNET_PROTOCOL_REVISION < 24) +#ifdef CONFIG_BACNET_BASIC_OBJECT_COLOR +#undef CONFIG_BACNET_BASIC_OBJECT_COLOR +#warning "Color configured, but BACnet Protocol Revision < 24" +#endif +#ifdef CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE +#undef CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE +#warning "Color Temperature configured, but BACnet Protocol Revision < 24" +#endif +#endif + +static object_functions_t Object_Table[] = { + { OBJECT_DEVICE, + NULL, /* don't init - recursive! */ + Device_Count, + Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, + Device_Object_Name, + Device_Read_Property_Local, + Device_Write_Property_Local, + Device_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(CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT) + { OBJECT_ANALOG_INPUT, + Analog_Input_Init, + Analog_Input_Count, + Analog_Input_Index_To_Instance, + Analog_Input_Valid_Instance, + Analog_Input_Object_Name, + Analog_Input_Read_Property, + Analog_Input_Write_Property, + Analog_Input_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Analog_Input_Encode_Value_List, + Analog_Input_Change_Of_Value, + Analog_Input_Change_Of_Value_Clear, + Analog_Input_Intrinsic_Reporting, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Analog_Input_Create, + Analog_Input_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT) + { OBJECT_ANALOG_OUTPUT, + Analog_Output_Init, + Analog_Output_Count, + Analog_Output_Index_To_Instance, + Analog_Output_Valid_Instance, + Analog_Output_Object_Name, + Analog_Output_Read_Property, + Analog_Output_Write_Property, + Analog_Output_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Analog_Output_Encode_Value_List, + Analog_Output_Change_Of_Value, + Analog_Output_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Analog_Output_Create, + Analog_Output_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE) + { OBJECT_ANALOG_VALUE, + Analog_Value_Init, + Analog_Value_Count, + Analog_Value_Index_To_Instance, + Analog_Value_Valid_Instance, + Analog_Value_Object_Name, + Analog_Value_Read_Property, + Analog_Value_Write_Property, + Analog_Value_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Analog_Value_Encode_Value_List, + Analog_Value_Change_Of_Value, + Analog_Value_Change_Of_Value_Clear, + Analog_Value_Intrinsic_Reporting, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Analog_Value_Create, + Analog_Value_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT) + { OBJECT_BINARY_INPUT, + Binary_Input_Init, + Binary_Input_Count, + Binary_Input_Index_To_Instance, + Binary_Input_Valid_Instance, + Binary_Input_Object_Name, + Binary_Input_Read_Property, + Binary_Input_Write_Property, + Binary_Input_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Binary_Input_Encode_Value_List, + Binary_Input_Change_Of_Value, + Binary_Input_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Binary_Input_Create, + Binary_Input_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT) + { OBJECT_BINARY_OUTPUT, + Binary_Output_Init, + Binary_Output_Count, + Binary_Output_Index_To_Instance, + Binary_Output_Valid_Instance, + Binary_Output_Object_Name, + Binary_Output_Read_Property, + Binary_Output_Write_Property, + Binary_Output_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Binary_Output_Encode_Value_List, + Binary_Output_Change_Of_Value, + Binary_Output_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Binary_Output_Create, + Binary_Output_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE) + { OBJECT_BINARY_VALUE, + Binary_Value_Init, + Binary_Value_Count, + Binary_Value_Index_To_Instance, + Binary_Value_Valid_Instance, + Binary_Value_Object_Name, + Binary_Value_Read_Property, + Binary_Value_Write_Property, + Binary_Value_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Binary_Value_Encode_Value_List, + Binary_Value_Change_Of_Value, + Binary_Value_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Binary_Value_Create, + Binary_Value_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT) + { OBJECT_MULTI_STATE_INPUT, + Multistate_Input_Init, + Multistate_Input_Count, + Multistate_Input_Index_To_Instance, + Multistate_Input_Valid_Instance, + Multistate_Input_Object_Name, + Multistate_Input_Read_Property, + Multistate_Input_Write_Property, + Multistate_Input_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Multistate_Input_Encode_Value_List, + Multistate_Input_Change_Of_Value, + Multistate_Input_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Multistate_Input_Create, + Multistate_Input_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT) + { OBJECT_MULTI_STATE_OUTPUT, + Multistate_Output_Init, + Multistate_Output_Count, + Multistate_Output_Index_To_Instance, + Multistate_Output_Valid_Instance, + Multistate_Output_Object_Name, + Multistate_Output_Read_Property, + Multistate_Output_Write_Property, + Multistate_Output_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Multistate_Output_Encode_Value_List, + Multistate_Output_Change_Of_Value, + Multistate_Output_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Multistate_Output_Create, + Multistate_Output_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE) + { OBJECT_MULTI_STATE_VALUE, + Multistate_Value_Init, + Multistate_Value_Count, + Multistate_Value_Index_To_Instance, + Multistate_Value_Valid_Instance, + Multistate_Value_Object_Name, + Multistate_Value_Read_Property, + Multistate_Value_Write_Property, + Multistate_Value_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + Multistate_Value_Encode_Value_List, + Multistate_Value_Change_Of_Value, + Multistate_Value_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + Multistate_Value_Create, + Multistate_Value_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT) + { OBJECT_NETWORK_PORT, + Network_Port_Init, + Network_Port_Count, + Network_Port_Index_To_Instance, + Network_Port_Valid_Instance, + Network_Port_Object_Name, + Network_Port_Read_Property, + Network_Port_Write_Property, + Network_Port_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 */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_CALENDAR) + { OBJECT_CALENDAR, + Calendar_Init, + Calendar_Count, + Calendar_Index_To_Instance, + Calendar_Valid_Instance, + Calendar_Object_Name, + Calendar_Read_Property, + Calendar_Write_Property, + Calendar_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 */, + Calendar_Create, + Calendar_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE) + { OBJECT_INTEGER_VALUE, + Integer_Value_Init, + Integer_Value_Count, + Integer_Value_Index_To_Instance, + Integer_Value_Valid_Instance, + Integer_Value_Object_Name, + Integer_Value_Read_Property, + Integer_Value_Write_Property, + Integer_Value_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 */, + Integer_Value_Create, + Integer_Value_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT) + { OBJECT_LIFE_SAFETY_POINT, + Life_Safety_Point_Init, + Life_Safety_Point_Count, + Life_Safety_Point_Index_To_Instance, + Life_Safety_Point_Valid_Instance, + Life_Safety_Point_Object_Name, + Life_Safety_Point_Read_Property, + Life_Safety_Point_Write_Property, + Life_Safety_Point_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 */, + Life_Safety_Point_Create, + Life_Safety_Point_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_ZONE) + { OBJECT_LIFE_SAFETY_ZONE, + Life_Safety_Zone_Init, + Life_Safety_Zone_Count, + Life_Safety_Zone_Index_To_Instance, + Life_Safety_Zone_Valid_Instance, + Life_Safety_Zone_Object_Name, + Life_Safety_Zone_Read_Property, + Life_Safety_Zone_Write_Property, + Life_Safety_Zone_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 */, + Life_Safety_Zone_Create, + Life_Safety_Zone_Delete, + NULL /* Timer */ }, +#endif + +#if defined(CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL) + { OBJECT_LOAD_CONTROL, + Load_Control_Init, + Load_Control_Count, + Load_Control_Index_To_Instance, + Load_Control_Valid_Instance, + Load_Control_Object_Name, + Load_Control_Read_Property, + Load_Control_Write_Property, + Load_Control_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 */, + Load_Control_Create, + Load_Control_Delete, + Load_Control_Timer }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT) + { OBJECT_LIGHTING_OUTPUT, + Lighting_Output_Init, + Lighting_Output_Count, + Lighting_Output_Index_To_Instance, + Lighting_Output_Valid_Instance, + Lighting_Output_Object_Name, + Lighting_Output_Read_Property, + Lighting_Output_Write_Property, + Lighting_Output_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 */, + Lighting_Output_Create, + Lighting_Output_Delete, + Lighting_Output_Timer }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_CHANNEL) + { OBJECT_CHANNEL, + Channel_Init, + Channel_Count, + Channel_Index_To_Instance, + Channel_Valid_Instance, + Channel_Object_Name, + Channel_Read_Property, + Channel_Write_Property, + Channel_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 */, + Channel_Create, + Channel_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT) + { OBJECT_BINARY_LIGHTING_OUTPUT, + Binary_Lighting_Output_Init, + Binary_Lighting_Output_Count, + Binary_Lighting_Output_Index_To_Instance, + Binary_Lighting_Output_Valid_Instance, + Binary_Lighting_Output_Object_Name, + Binary_Lighting_Output_Read_Property, + Binary_Lighting_Output_Write_Property, + Binary_Lighting_Output_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 */, + Binary_Lighting_Output_Create, + Binary_Lighting_Output_Delete, + Binary_Lighting_Output_Timer }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_COLOR) + { OBJECT_COLOR, + Color_Init, + Color_Count, + Color_Index_To_Instance, + Color_Valid_Instance, + Color_Object_Name, + Color_Read_Property, + Color_Write_Property, + Color_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 */, + Color_Create, + Color_Delete, + Color_Timer }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE) + { OBJECT_COLOR_TEMPERATURE, + Color_Temperature_Init, + Color_Temperature_Count, + Color_Temperature_Index_To_Instance, + Color_Temperature_Valid_Instance, + Color_Temperature_Object_Name, + Color_Temperature_Read_Property, + Color_Temperature_Write_Property, + Color_Temperature_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 */, + Color_Temperature_Create, + Color_Temperature_Delete, + Color_Temperature_Timer }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_FILE) + { OBJECT_FILE, + bacfile_init, + bacfile_count, + bacfile_index_to_instance, + bacfile_valid_instance, + bacfile_object_name, + bacfile_read_property, + bacfile_write_property, + BACfile_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 */, + bacfile_create, + bacfile_delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW) + { OBJECT_STRUCTURED_VIEW, + Structured_View_Init, + Structured_View_Count, + Structured_View_Index_To_Instance, + Structured_View_Valid_Instance, + Structured_View_Object_Name, + Structured_View_Read_Property, + NULL /* Write_Property */, + Structured_View_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 */, + Structured_View_Create, + Structured_View_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE) + { OBJECT_BITSTRING_VALUE, + BitString_Value_Init, + BitString_Value_Count, + BitString_Value_Index_To_Instance, + BitString_Value_Valid_Instance, + BitString_Value_Object_Name, + BitString_Value_Read_Property, + BitString_Value_Write_Property, + BitString_Value_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + BitString_Value_Encode_Value_List, + BitString_Value_Change_Of_Value, + BitString_Value_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + BitString_Value_Create, + BitString_Value_Delete, + NULL /* Timer */ }, +#endif +#if defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE) + { OBJECT_CHARACTERSTRING_VALUE, + CharacterString_Value_Init, + CharacterString_Value_Count, + CharacterString_Value_Index_To_Instance, + CharacterString_Value_Valid_Instance, + CharacterString_Value_Object_Name, + CharacterString_Value_Read_Property, + CharacterString_Value_Write_Property, + CharacterString_Value_Property_Lists, + NULL /* ReadRangeInfo */, + NULL /* Iterator */, + CharacterString_Value_Encode_Value_List, + CharacterString_Value_Change_Of_Value, + CharacterString_Value_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + CharacterString_Value_Create, + CharacterString_Value_Delete, + NULL /* Timer */ }, +#endif + { + MAX_BACNET_OBJECT_TYPE, + NULL /* Init */, + NULL /* Count */, + NULL /* Index_To_Instance */, + NULL /* Valid_Instance */, + NULL /* Object_Name */, + NULL /* Read_Property */, + NULL /* Write_Property */, + NULL /* 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 */ + } +}; + +/* local data */ +static const char *Application_Software_Version = BACNET_DEVICE_VERSION; +static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static BACNET_CHARACTER_STRING My_Object_Name; +static const char *Device_Name_Default = BACNET_DEVICE_OBJECT_NAME; +static const char *Device_Description_Default = BACNET_DEVICE_DESCRIPTION; +static const char *Model_Name = BACNET_DEVICE_MODEL_NAME; +static uint32_t Database_Revision; +static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE; +static BACNET_CHARACTER_STRING Reinit_Password; +static const char *BACnet_Version = BACNET_VERSION_TEXT; +static write_property_function Device_Write_Property_Store_Callback; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Device_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_REVISION, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_OBJECT_LIST, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_DEVICE_ADDRESS_BINDING, + PROP_DATABASE_REVISION, + -1 +}; + +static const int Device_Properties_Optional[] = { PROP_DESCRIPTION, + PROP_LOCATION, +#if defined(BACDL_MSTP) + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, +#endif + PROP_LOCAL_DATE, + PROP_LOCAL_TIME, + PROP_UTC_OFFSET, + PROP_DAYLIGHT_SAVINGS_STATUS, + -1 }; + +static const int Device_Properties_Proprietary[] = { -1 }; + +/** Glue function to let the Device object, when called by a handler, + * lookup which Object type needs to be invoked. + * @ingroup ObjHelpers + * @param Object_Type [in] The type of BACnet Object the handler wants to + * access. + * @return Pointer to the group of object helper functions that implement this + * type of Object. + */ +static struct object_functions * +Device_Objects_Find_Functions(BACNET_OBJECT_TYPE Object_Type) +{ + struct object_functions *pObject = NULL; + + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + /* handle each object type */ + if (pObject->Object_Type == Object_Type) { + return (pObject); + } + + pObject++; + } + + return (NULL); +} + +/** For a given object type, returns the special property list. + * This function is used for ReadPropertyMultiple calls which want + * just Required, just Optional, or All properties. + * @ingroup ObjIntf + * + * @param object_type [in] The desired BACNET_OBJECT_TYPE whose properties + * are to be listed. + * @param pPropertyList [out] Reference to the structure which will, on return, + * list, separately, the Required, Optional, and Proprietary object + * properties with their counts. + */ +void Device_Objects_Property_List( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + struct special_property_list_t *pPropertyList) +{ + struct object_functions *pObject = NULL; + + (void)object_instance; + pPropertyList->Required.pList = NULL; + pPropertyList->Optional.pList = NULL; + pPropertyList->Proprietary.pList = NULL; + + /* If we can find an entry for the required object type + * and there is an Object_List_RPM fn ptr then call it + * to populate the pointers to the individual list counters. + */ + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) { + pObject->Object_RPM_List( + &pPropertyList->Required.pList, &pPropertyList->Optional.pList, + &pPropertyList->Proprietary.pList); + } + + /* Fetch the counts if available otherwise zero them */ + pPropertyList->Required.count = pPropertyList->Required.pList == NULL + ? 0 + : property_list_count(pPropertyList->Required.pList); + + pPropertyList->Optional.count = pPropertyList->Optional.pList == NULL + ? 0 + : property_list_count(pPropertyList->Optional.pList); + + pPropertyList->Proprietary.count = pPropertyList->Proprietary.pList == NULL + ? 0 + : property_list_count(pPropertyList->Proprietary.pList); + + return; +} + +void Device_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Device_Properties_Required; + } + if (pOptional) { + *pOptional = Device_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Device_Properties_Proprietary; + } + + return; +} + +/** + * @brief Determine if the object property is a member of this object instance + * @param object_type - object type of the object + * @param object_instance - object-instance number of the object + * @param object_property - object-property to be checked + * @return true if the property is a member of this object instance + */ +bool Device_Objects_Property_List_Member( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property) +{ + bool found = false; + struct special_property_list_t property_list = { 0 }; + + Device_Objects_Property_List(object_type, object_instance, &property_list); + found = property_list_member(property_list.Required.pList, object_property); + if (!found) { + found = + property_list_member(property_list.Optional.pList, object_property); + } + if (!found) { + found = property_list_member( + property_list.Proprietary.pList, object_property); + } + + return found; +} + +/** + * @brief Sets the ReinitializeDevice password + * + * The password shall be a null terminated C string of up to + * 20 ASCII characters for those devices that require the password. + * + * For those devices that do not require a password, set to NULL or + * point to a zero length C string (null terminated). + * + * @param the ReinitializeDevice password; can be NULL or empty string + */ +bool Device_Reinitialize_Password_Set(const char *password) +{ + characterstring_init_ansi(&Reinit_Password, password); + + return true; +} + +/** + * @brief Commands a Device re-initialization, to a given state. + * The request's password must match for the operation to succeed. + * This implementation provides a framework, but doesn't + * actually *DO* anything. + * @note You could use a mix of states and passwords to multiple outcomes. + * @note You probably want to restart *after* the simple ack has been sent + * from the return handler, so just set a local flag here. + * @ingroup ObjIntf + * + * @param rd_data [in,out] The information from the RD request. + * On failure, the error class and code will be set. + * @return True if succeeds (password is correct), else False. + */ +bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) +{ + bool status = false; + bool password_success = false; + + /* From 16.4.1.1.2 Password + This optional parameter shall be a CharacterString of up to + 20 characters. For those devices that require the password as a + protection, the service request shall be denied if the parameter + is absent or if the password is incorrect. For those devices that + do not require a password, this parameter shall be ignored.*/ + if (characterstring_length(&Reinit_Password) > 0) { + if (characterstring_length(&rd_data->password) > 20) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + } else if (characterstring_same(&rd_data->password, &Reinit_Password)) { + password_success = true; + } else { + rd_data->error_class = ERROR_CLASS_SECURITY; + rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE; + } + } else { + password_success = true; + } + if (password_success) { + switch (rd_data->state) { + case BACNET_REINIT_COLDSTART: + case BACNET_REINIT_WARMSTART: + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + /* Note: you could use a mix of state + and password to multiple things */ + /* note: you probably want to restart *after* the + simple ack has been sent from the return handler + so just set a flag from here */ + Reinitialize_State = rd_data->state; + status = true; + break; + case BACNET_REINIT_STARTBACKUP: + case BACNET_REINIT_ENDBACKUP: + case BACNET_REINIT_STARTRESTORE: + case BACNET_REINIT_ENDRESTORE: + case BACNET_REINIT_ABORTRESTORE: + if (dcc_communication_disabled()) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_COMMUNICATION_DISABLED; + } else { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + } + break; + default: + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + break; + } + } + + return status; +} + +BACNET_REINITIALIZED_STATE Device_Reinitialized_State(void) +{ + return Reinitialize_State; +} + +unsigned Device_Count(void) +{ + return 1; +} + +uint32_t Device_Index_To_Instance(unsigned index) +{ + (void)index; + return Object_Instance_Number; +} + +bool Device_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + + if (object_instance == Object_Instance_Number) { + status = characterstring_copy(object_name, &My_Object_Name); + } + + return status; +} + +/** + * @brief Initialize the Device Object Name with an ANSI C string + * @param value [in] The object name as a null-terminated string + * @return True on success, else False + */ +bool Device_Object_Name_ANSI_Init(const char *value) +{ + return characterstring_init_ansi(&My_Object_Name, value); +} + +/** + * @brief Get the Device Object Name as a C string + * @return The object name as a null-terminated string + */ +char *Device_Object_Name_ANSI(void) +{ + return (char *)characterstring_value(&My_Object_Name); +} + +bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; /*return value */ + + if (!characterstring_same(&My_Object_Name, object_name)) { + /* Make the change and update the database revision */ + status = characterstring_copy(&My_Object_Name, object_name); + Device_Inc_Database_Revision(); + } + + return status; +} + +/* methods to manipulate the data */ + +/** Return the Object Instance number for our (single) Device Object. + * This is a key function, widely invoked by the handler code, since + * it provides "our" (ie, local) address. + * @ingroup ObjIntf + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Device_Object_Instance_Number(void) +{ + return Object_Instance_Number; +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + Object_Instance_Number = object_id; + } else { + status = false; + } + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + return (Object_Instance_Number == object_id); +} + +BACNET_DEVICE_STATUS Device_System_Status(void) +{ + return System_Status; +} + +int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) +{ + /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ + int result = -1; + + (void)local; + if (status < MAX_DEVICE_STATUS) { + System_Status = status; + result = 0; + } + + return result; +} + +uint16_t Device_Vendor_Identifier(void) +{ + return BACNET_VENDOR_ID; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + +/** + * @brief Get the Database Revision property of the Device Object + * @return The Database Revision property of the Device Object + */ +uint32_t Device_Database_Revision(void) +{ + return Database_Revision; +} + +/** + * @brief Set the Database Revision property of the Device Object + * @param revision [in] The new value for the Database Revision property + */ +void Device_Set_Database_Revision(uint32_t revision) +{ + Database_Revision = revision; +} + +/* + * Shortcut for incrementing database revision as this is potentially + * the most common operation if changing object names and ids is + * implemented. + */ +void Device_Inc_Database_Revision(void) +{ + Database_Revision++; +} + +/** Get the total count of objects supported by this Device Object. + * @note Since many network clients depend on the object list + * for discovery, it must be consistent! + * @return The count of objects, for all supported Object types. + */ +unsigned Device_Object_List_Count(void) +{ + unsigned count = 0; /* number of objects */ + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count += pObject->Object_Count(); + } + pObject++; + } + + return count; +} + +/** Lookup the Object at the given array index in the Device's Object List. + * Even though we don't keep a single linear array of objects in the Device, + * this method acts as though we do and works through a virtual, concatenated + * array of all of our object type arrays. + * + * @param array_index [in] The desired array index (1 to N) + * @param object_type [out] The object's type, if found. + * @param instance [out] The object's instance number, if found. + * @return True if found, else false. + */ +bool Device_Object_List_Identifier( + uint32_t array_index, BACNET_OBJECT_TYPE *object_type, uint32_t *instance) +{ + bool status = false; + uint32_t count = 0; + uint32_t object_index = 0; + struct object_functions *pObject = NULL; + + /* array index zero is length - so invalid */ + if (array_index == 0) { + return status; + } + object_index = array_index - 1; + /* initialize the default return values */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count && pObject->Object_Index_To_Instance) { + object_index -= count; + count = pObject->Object_Count(); + if (object_index < count) { + *object_type = pObject->Object_Type; + *instance = pObject->Object_Index_To_Instance(object_index); + status = true; + break; + } + } + pObject++; + } + + return status; +} + +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +int Device_Object_List_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_OBJECT_TYPE object_type; + uint32_t instance; + bool found; + + if (object_instance == Device_Object_Instance_Number()) { + /* single element is zero based, add 1 for BACnetARRAY which is one + * based */ + array_index++; + found = + Device_Object_List_Identifier(array_index, &object_type, &instance); + if (found) { + apdu_len = + encode_application_object_id(apdu, object_type, instance); + } + } + + return apdu_len; +} + +/** Determine if we have an object with the given object_name. + * If the object_type and object_instance pointers are not null, + * and the lookup succeeds, they will be given the resulting values. + * @param object_name [in] The desired Object Name to look for. + * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object. + * @param object_instance [out] The object instance number of the matching + * Object. + * @return True on success or else False if not found. + */ +bool Device_Valid_Object_Name( + const BACNET_CHARACTER_STRING *object_name1, + BACNET_OBJECT_TYPE *object_type, + uint32_t *object_instance) +{ + bool found = false; + BACNET_OBJECT_TYPE type = OBJECT_NONE; + uint32_t instance; + uint32_t max_objects = 0, i = 0; + bool check_id = false; + BACNET_CHARACTER_STRING object_name2; + struct object_functions *pObject = NULL; + + max_objects = Device_Object_List_Count(); + for (i = 1; i <= max_objects; i++) { + check_id = Device_Object_List_Identifier(i, &type, &instance); + if (check_id) { + pObject = Device_Objects_Find_Functions((BACNET_OBJECT_TYPE)type); + if ((pObject != NULL) && (pObject->Object_Name != NULL) && + (pObject->Object_Name(instance, &object_name2) && + characterstring_same(object_name1, &object_name2))) { + found = true; + if (object_type) { + *object_type = type; + } + if (object_instance) { + *object_instance = instance; + } + break; + } + } + } + + return found; +} + +/** Determine if we have an object of this type and instance number. + * @param object_type [in] The desired BACNET_OBJECT_TYPE + * @param object_instance [in] The object instance number to be looked up. + * @return True if found, else False if no such Object in this device. + */ +bool Device_Valid_Object_Id( + BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + bool status = false; /* return value */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions((BACNET_OBJECT_TYPE)object_type); + if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) { + status = pObject->Object_Valid_Instance(object_instance); + } + + return status; +} + +/** Copy a child object's object_name value, given its ID. + * @param object_type [in] The BACNET_OBJECT_TYPE of the child Object. + * @param object_instance [in] The object instance number of the child Object. + * @param object_name [out] The Object Name found for this child Object. + * @return True on success or else False if not found. + */ +bool Device_Object_Name_Copy( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_CHARACTER_STRING *object_name) +{ + struct object_functions *pObject = NULL; + bool found = false; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Name != NULL)) { + found = pObject->Object_Name(object_instance, object_name); + } + + return found; +} + +/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or + BACNET_STATUS_ABORT for abort message */ +int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string = { 0 }; + BACNET_CHARACTER_STRING char_string = { 0 }; + BACNET_DATE bdate; + BACNET_TIME btime; + int16_t utc_offset_minutes; + bool dst_active; + uint32_t i = 0; + uint32_t count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + uint16_t apdu_max = 0; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + apdu_max = rpdata->application_data_len; + switch ((int)rpdata->object_property) { + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, Device_Description_Default); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + characterstring_init_ansi(&char_string, "USA"); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = + encode_application_enumerated(&apdu[0], Device_System_Status()); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, BACNET_VENDOR_NAME); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = encode_application_unsigned(&apdu[0], BACNET_VENDOR_ID); + break; + case PROP_MODEL_NAME: + characterstring_init_ansi(&char_string, Model_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + characterstring_init_ansi(&char_string, BACnet_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + characterstring_init_ansi( + &char_string, Application_Software_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = + encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_VERSION); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_REVISION); + break; + case PROP_PROTOCOL_SERVICES_SUPPORTED: + /* Note: list of services that are executed, not initiated. */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) { + /* automatic lookup based on handlers set */ + bitstring_set_bit( + &bit_string, (uint8_t)i, + apdu_service_supported((BACNET_SERVICES_SUPPORTED)i)); + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + /* Note: this is the list of objects that can be in this device, + not a list of objects that this device can access */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { + /* initialize all the object types to not-supported */ + bitstring_set_bit(&bit_string, (uint8_t)i, false); + } + /* set the object types with objects to supported */ + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) { + bitstring_set_bit(&bit_string, pObject->Object_Type, true); + } + pObject++; + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Device_Object_List_Element_Encode, count, apdu, apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = encode_application_enumerated( + &apdu[0], Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout()); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = encode_application_unsigned(&apdu[0], apdu_retries()); + break; + case PROP_DEVICE_ADDRESS_BINDING: + /* FIXME: encode the list here, if it exists */ + break; + case PROP_DATABASE_REVISION: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Database_Revision()); + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + apdu_len = + encode_application_unsigned(&apdu[0], dlmstp_max_info_frames()); + break; + case PROP_MAX_MASTER: + apdu_len = + encode_application_unsigned(&apdu[0], dlmstp_max_master()); + break; +#endif + case PROP_LOCAL_TIME: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_time(&apdu[0], &btime); + break; + case PROP_LOCAL_DATE: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_date(&apdu[0], &bdate); + break; + case PROP_UTC_OFFSET: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_signed(&apdu[0], utc_offset_minutes); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); + apdu_len = encode_application_boolean(&apdu[0], dst_active); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** Looks up the common Object and Property, and encodes its Value in an + * APDU. Sets the error class and code if request is not appropriate. + * @param pObject - object table + * @param rpdata [in,out] Structure with the requested Object & Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +static int Read_Property_Common( + struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + if ((rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + /* Device Object exception: requested instance + may not match our instance if a wildcard */ + if (rpdata->object_type == OBJECT_DEVICE) { + rpdata->object_instance = Object_Instance_Number; + } + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + } + break; + case PROP_OBJECT_NAME: + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name( + rpdata->object_instance, &char_string); + } + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_OBJECT_TYPE: + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + apdu_len = encode_application_enumerated( + &apdu[0], rpdata->object_type); + } + break; +#if (BACNET_PROTOCOL_REVISION >= 14) + case PROP_PROPERTY_LIST: + Device_Objects_Property_List( + rpdata->object_type, rpdata->object_instance, &property_list); + apdu_len = property_list_encode( + rpdata, property_list.Required.pList, + property_list.Optional.pList, property_list.Proprietary.pList); + break; +#endif + default: + if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + break; + } + + return apdu_len; +} + +/** Looks up the requested Object and Property, and encodes its Value in an + * APDU. + * @ingroup ObjIntf + * If the Object or Property can't be found, sets the error class and code. + * + * @param rpdata [in,out] Structure with the desired Object and Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(rpdata->object_type); + if (pObject) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(rpdata->object_instance)) { + apdu_len = Read_Property_Common(pObject, rpdata); + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value - false=error */ + int len = 0; + uint8_t encoding = 0; + size_t length = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* 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; + } + if ((wp_data->object_property != PROP_OBJECT_LIST) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + switch ((int)wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + if ((value.type.Object_Id.type == OBJECT_DEVICE) && + (Device_Set_Object_Instance_Number( + value.type.Object_Id.instance))) { + /* we could send an I-Am broadcast to let the world know */ + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int <= 255) { + dlmstp_set_max_info_frames(value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_MAX_MASTER: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value.type.Unsigned_Int > 0) && + (value.type.Unsigned_Int <= 127)) { + dlmstp_set_max_master(value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#endif + case PROP_OBJECT_NAME: + if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + length = characterstring_length(&value.type.Character_String); + if (length < 1) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else if (length < characterstring_capacity(&My_Object_Name)) { + encoding = + characterstring_encoding(&value.type.Character_String); + if (encoding < MAX_CHARACTER_STRING_ENCODING) { + /* All the object names in a device must be unique. */ + if (Device_Valid_Object_Name( + &value.type.Character_String, NULL, NULL)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + } else { + Device_Set_Object_Name( + &value.type.Character_String); + status = true; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = + ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + if (property_lists_member( + Device_Properties_Required, Device_Properties_Optional, + Device_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; + } + /* not using len at this time */ + (void)len; + + return status; +} + +/** + * @brief Handles the writing of the object name property + * @param wp_data [in,out] WriteProperty data structure + * @param Object_Write_Property object specific function to write the property + * @return True on success, else False if there is an error. + */ +static bool Device_Write_Property_Object_Name( + BACNET_WRITE_PROPERTY_DATA *wp_data, + write_property_function Object_Write_Property) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_CHARACTER_STRING value; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + int apdu_size = 0; + uint8_t *apdu = NULL; + + if (!wp_data) { + return false; + } + if (wp_data->array_index != BACNET_ARRAY_ALL) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + apdu = wp_data->application_data; + apdu_size = wp_data->application_data_len; + len = bacnet_character_string_application_decode(apdu, apdu_size, &value); + if (len > 0) { + if ((characterstring_encoding(&value) != CHARACTER_ANSI_X34) || + (characterstring_length(&value) == 0) || + (!characterstring_printable(&value))) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + status = true; + } + } else if (len == 0) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + if (status) { + /* All the object names in a device must be unique */ + if (Device_Valid_Object_Name(&value, &object_type, &object_instance)) { + if ((object_type == wp_data->object_type) && + (object_instance == wp_data->object_instance)) { + /* writing same name to same object */ + status = true; + } else { + /* name already exists in some object */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + status = false; + } + } else { + status = Object_Write_Property(wp_data); + } + } + + return status; +} + +/** + * @brief Set the callback for a WriteProperty successful operation + * @param cb [in] The function to be called, or NULL to disable + */ +void Device_Write_Property_Store_Callback_Set(write_property_function cb) +{ + Device_Write_Property_Store_Callback = cb; +} + +/** + * @brief Store the value of a property when WriteProperty is successful + */ +static void Device_Write_Property_Store(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + if (Device_Write_Property_Store_Callback) { + (void)Device_Write_Property_Store_Callback(wp_data); + } +} + +/** Looks up the requested Object and Property, and set the new Value in it, + * if allowed. + * If the Object or Property can't be found, sets the error class and code. + * @ingroup ObjIntf + * + * @param wp_data [in,out] Structure with the desired Object and Property info + * and new Value on entry, and APDU message on return. + * @return True on success, else False if there is an error. + */ +bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(wp_data->object_type); + if (pObject) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(wp_data->object_instance)) { + if (pObject->Object_Write_Property) { +#if (BACNET_PROTOCOL_REVISION >= 14) + if (wp_data->object_property == PROP_PROPERTY_LIST) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + return status; + } +#endif + if (wp_data->object_property == PROP_OBJECT_NAME) { + status = Device_Write_Property_Object_Name( + wp_data, pObject->Object_Write_Property); + } else { + status = pObject->Object_Write_Property(wp_data); + } + if (status) { + Device_Write_Property_Store(wp_data); + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** Looks up the requested Object, and fills the Property Value list. + * If the Object or Property can't be found, returns false. + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance number to be looked up. + * @param [out] The value list + * @return True if the object instance supports this feature + * and was encoded correctly + */ +bool Device_Encode_Value_List( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE *value_list) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Value_List) { + status = + pObject->Object_Value_List(object_instance, value_list); + } + } + } + + return (status); +} + +/** Checks the COV flag in the requested Object + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance to be looked up. + * @return True if the COV flag is set + */ +bool Device_COV(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_COV) { + status = pObject->Object_COV(object_instance); + } + } + } + + return (status); +} + +/** Clears the COV flag in the requested Object + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance to be looked up. + */ +void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_COV_Clear) { + pObject->Object_COV_Clear(object_instance); + } + } + } +} + +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer(uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } +} + +/** Looks up the requested Object to see if the functionality is supported. + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @return True if the object instance supports this feature. + */ +bool Device_Value_List_Supported(BACNET_OBJECT_TYPE object_type) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Value_List) { + status = true; + } + } + + return (status); +} + +/** Initialize the Device Object. + Initialize the group of object helper functions for any supported Object. + Initialize each of the Device Object child Object instances. + * @ingroup ObjIntf + * @param object_table [in,out] array of structure with object functions. + * Each Child Object must provide some implementation of each of these + * functions in order to properly support the default handlers. + */ +void Device_Init(object_functions_t *object_table) +{ + struct object_functions *pObject = NULL; + + /* we don't use the object table passed in + since there is extra stuff we don't need in there. */ + (void)object_table; + /* our local object table */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + if (Object_Instance_Number > BACNET_MAX_INSTANCE) { + Object_Instance_Number = BACNET_MAX_INSTANCE; + } + characterstring_init_ansi(&My_Object_Name, Device_Name_Default); +} diff --git a/src/bacnet/basic/server/bacnet_port.c b/src/bacnet/basic/server/bacnet_port.c new file mode 100644 index 00000000..25f466fd --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port.c @@ -0,0 +1,66 @@ +/** + * @file + * @brief The BACnet/IPv4 datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/object/netport.h" +#if defined(BACDL_BIP) +#include "bacnet/basic/server/bacnet_port_ipv4.h" +#elif defined(BACDL_BIP6) +#include "bacnet/basic/server/bacnet_port_ipv6.h" +#endif +/* me! */ +#include "bacnet/basic/server/bacnet_port.h" + +/* timer used to renew Foreign Device Registration */ +static struct mstimer BACnet_Task_Timer; + +/** + * @brief Periodic tasks for the BACnet datalink layer + */ +void bacnet_port_task(void) +{ + uint32_t elapsed_milliseconds = 0; + uint32_t elapsed_seconds = 0; + + if (mstimer_expired(&BACnet_Task_Timer)) { + /* 1 second tasks */ + mstimer_reset(&BACnet_Task_Timer); + /* presume that the elapsed time is the interval time */ + elapsed_milliseconds = mstimer_interval(&BACnet_Task_Timer); + elapsed_seconds = elapsed_milliseconds / 1000; +#if defined(BACDL_BIP) + bacnet_port_ipv4_task(elapsed_seconds); +#elif defined(BACDL_BIP6) + bacnet_port_ipv6_task(elapsed_seconds); +#endif + } +} + +/** + * @brief Initialize the datalink network port + */ +bool bacnet_port_init(void) +{ + bool status = false; + /* start the 1 second timer for non-critical cyclic tasks */ + mstimer_set(&BACnet_Task_Timer, 1000L); +#if defined(BACDL_BIP) + status = bacnet_port_ipv4_init(); +#elif defined(BACDL_BIP6) + status = bacnet_port_ipv6_init(); +#endif + return status; +} diff --git a/src/bacnet/basic/server/bacnet_port.h b/src/bacnet/basic/server/bacnet_port.h new file mode 100644 index 00000000..a8d70f09 --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port.h @@ -0,0 +1,27 @@ +/** + * @file + * @brief The BACnet datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#ifndef BACNET_PORT_H +#define BACNET_PORT_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +bool bacnet_port_init(void); +void bacnet_port_task(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/server/bacnet_port_ipv4.c b/src/bacnet/basic/server/bacnet_port_ipv4.c new file mode 100644 index 00000000..90356acd --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port_ipv4.c @@ -0,0 +1,101 @@ +/** + * @file + * @brief The BACnet/IPv4 datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/object/netport.h" +#include "bacnet/basic/bbmd/h_bbmd.h" +#include "bacnet/datalink/bip.h" +#include "bacnet/datalink/bvlc.h" +#include "bacnet/datalink/datalink.h" +/* me! */ +#include "bacnet/basic/server/bacnet_port_ipv4.h" + +#if defined(BACDL_BIP) + +/* timer used to renew Foreign Device Registration */ +static uint16_t BBMD_Timer_Seconds; +static uint16_t BBMD_TTL_Seconds = 60000; +static BACNET_IP_ADDRESS BBMD_Address; + +/** + * @brief Initialize the datalink network port + * @param ttl_seconds [in] The time-to-live in seconds for the Foreign Device + * Registration + * @param bbmd_address [in] The address of the BBMD + */ +void bacnet_port_ipv4_foreign_device_init( + const uint16_t ttl_seconds, const BACNET_IP_ADDRESS *bbmd_address) +{ + BBMD_TTL_Seconds = ttl_seconds; + if (bbmd_address) { + memcpy(&BBMD_Address, bbmd_address, sizeof(BACNET_IP_ADDRESS)); + } +} + +/** + * @brief Renew the Foreign Device Registration + */ +void bacnet_port_ipv4_task(uint16_t elapsed_seconds) +{ + if (BBMD_Timer_Seconds) { + if (BBMD_Timer_Seconds <= elapsed_seconds) { + BBMD_Timer_Seconds = 0; + } else { + BBMD_Timer_Seconds -= elapsed_seconds; + } + if (BBMD_Timer_Seconds == 0) { + if (BBMD_Address.port > 0) { + (void)bvlc_register_with_bbmd(&BBMD_Address, BBMD_TTL_Seconds); + } + BBMD_Timer_Seconds = (uint16_t)BBMD_TTL_Seconds; + } + } +} + +/** + * Initialize the network port object. + */ +bool bacnet_port_ipv4_init(void) +{ + const uint32_t instance = 1; + BACNET_IP_ADDRESS addr = { 0 }; + uint8_t prefix = 0; + + if (!bip_init(NULL)) { + return false; + } + Network_Port_Object_Instance_Number_Set(0, instance); + Network_Port_Name_Set(instance, "BACnet/IP Port"); + Network_Port_Type_Set(instance, PORT_TYPE_BIP); + bip_get_addr(&addr); + prefix = bip_get_subnet_prefix(); + Network_Port_BIP_Port_Set(instance, addr.port); + Network_Port_IP_Address_Set( + instance, addr.address[0], addr.address[1], addr.address[2], + addr.address[3]); + Network_Port_IP_Subnet_Prefix_Set(instance, prefix); + Network_Port_Link_Speed_Set(instance, 0.0); + /* common NP data */ + Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); + Network_Port_Out_Of_Service_Set(instance, false); + Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN); + Network_Port_APDU_Length_Set(instance, MAX_APDU); + Network_Port_Network_Number_Set(instance, 0); + /* last thing - clear pending changes - we don't want to set these + since they are already set */ + Network_Port_Changes_Pending_Set(instance, false); + + return true; +} +#endif diff --git a/src/bacnet/basic/server/bacnet_port_ipv4.h b/src/bacnet/basic/server/bacnet_port_ipv4.h new file mode 100644 index 00000000..0a1debe5 --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port_ipv4.h @@ -0,0 +1,30 @@ +/** + * @file + * @brief API for the BACnet datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#ifndef BACNET_PORT_IPV4_H +#define BACNET_PORT_IPV4_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/datalink/bvlc.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_port_ipv4_foreign_device_init( + const uint16_t ttl_seconds, const BACNET_IP_ADDRESS *bbmd_address); +void bacnet_port_ipv4_task(uint16_t elapsed_seconds); +bool bacnet_port_ipv4_init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/server/bacnet_port_ipv6.c b/src/bacnet/basic/server/bacnet_port_ipv6.c new file mode 100644 index 00000000..73b34fcc --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port_ipv6.c @@ -0,0 +1,104 @@ +/** + * @file + * @brief The BACnet datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/object/netport.h" +#include "bacnet/basic/bbmd6/h_bbmd6.h" +#include "bacnet/datalink/bip6.h" +#include "bacnet/datalink/bvlc6.h" +#include "bacnet/datalink/datalink.h" +/* me! */ +#include "bacnet/basic/server/bacnet_port_ipv6.h" + +#if defined(BACDL_BIP6) + +/* timer used to renew Foreign Device Registration */ +static uint16_t BBMD_Timer_Seconds; +static uint16_t BBMD_TTL_Seconds = 60000; +static BACNET_IP6_ADDRESS BBMD_Address; + +/** + * @brief Initialize the datalink network port + * @param ttl_seconds [in] The time-to-live in seconds for the Foreign Device + * Registration + * @param bbmd_address [in] The address of the BBMD + */ +void bacnet_port_ipv6_foreign_device_init( + const uint16_t ttl_seconds, const BACNET_IP6_ADDRESS *bbmd_address) +{ + BBMD_TTL_Seconds = ttl_seconds; + if (bbmd_address) { + memcpy(&BBMD_Address, bbmd_address, sizeof(BACNET_IP6_ADDRESS)); + } +} + +/** + * @brief Renew the Foreign Device Registration + */ +void bacnet_port_ipv6_task(uint16_t elapsed_seconds) +{ + if (BBMD_Timer_Seconds) { + if (BBMD_Timer_Seconds <= elapsed_seconds) { + BBMD_Timer_Seconds = 0; + } else { + BBMD_Timer_Seconds -= elapsed_seconds; + } + if (BBMD_Timer_Seconds == 0) { + if (BBMD_Address.port > 0) { + (void)bvlc6_register_with_bbmd(&BBMD_Address, BBMD_TTL_Seconds); + } + BBMD_Timer_Seconds = BBMD_TTL_Seconds; + } + } +} + +/** + * Initialize the network port object. + * @return true if successful + */ +bool bacnet_port_ipv6_init(void) +{ + uint32_t instance = 1; + uint8_t prefix = 0; + BACNET_ADDRESS addr = { 0 }; + BACNET_IP6_ADDRESS addr6 = { 0 }; + + if (!bip6_init(NULL)) { + return false; + } + Network_Port_Object_Instance_Number_Set(0, instance); + Network_Port_Name_Set(instance, "BACnet/IPv6 Port"); + Network_Port_Type_Set(instance, PORT_TYPE_BIP6); + Network_Port_BIP6_Port_Set(instance, bip6_get_port()); + bip6_get_my_address(&addr); + Network_Port_MAC_Address_Set(instance, &addr.mac[0], addr.mac_len); + bip6_get_addr(&addr6); + Network_Port_IPv6_Address_Set(instance, &addr6.address[0]); + bip6_get_broadcast_addr(&addr6); + Network_Port_IPv6_Multicast_Address_Set(instance, &addr6.address[0]); + Network_Port_IPv6_Subnet_Prefix_Set(instance, prefix); + + Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); + Network_Port_Link_Speed_Set(instance, 0.0); + Network_Port_Out_Of_Service_Set(instance, false); + Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN); + Network_Port_APDU_Length_Set(instance, MAX_APDU); + Network_Port_Network_Number_Set(instance, 0); + /* last thing - clear pending changes - we don't want to set these + since they are already set */ + Network_Port_Changes_Pending_Set(instance, false); + + return true; +} +#endif diff --git a/src/bacnet/basic/server/bacnet_port_ipv6.h b/src/bacnet/basic/server/bacnet_port_ipv6.h new file mode 100644 index 00000000..9952c9f4 --- /dev/null +++ b/src/bacnet/basic/server/bacnet_port_ipv6.h @@ -0,0 +1,30 @@ +/** + * @file + * @brief The BACnet/IPv6 datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#ifndef BACNET_PORT_IPV6_H +#define BACNET_PORT_IPV6_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/datalink/bvlc6.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_port_ipv6_foreign_device_init( + const uint16_t ttl_seconds, const BACNET_IP6_ADDRESS *bbmd_address); +void bacnet_port_ipv6_task(uint16_t elapsed_seconds); +bool bacnet_port_ipv6_init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif