From 1176b0d966b7b2c2b46bcfe74b15ef7e05140efd Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Wed, 28 Feb 2024 12:56:09 -0600 Subject: [PATCH] Feature/bacnet discover dnet devices (#583) * Added an example application to discover devices and their objects and properties on a specific destination network. The application uses a BACnet Discovery FSM module along with the BACnet R/W FSM. The BACnet Discovery module stores the binary property data in Keylists and includes device object property queries and iterators. * Added callback from BACnet R/W FSM module for I-Am messages. * Removed dependency in BACnet R/W FSM module on rpm_ack_decode_service_request() which uses calloc/free value lists. Created an alternate RPM-ACK to RP-ACK processing function. * Changed RPM handler to skip over unknown property values --- CMakeLists.txt | 25 + Makefile | 8 + apps/Makefile | 6 +- apps/server-discover/Makefile | 45 + apps/server-discover/main.c | 291 ++++++ apps/whois/main.c | 1 - src/bacnet/basic/client/bac-discover.c | 1155 ++++++++++++++++++++++++ src/bacnet/basic/client/bac-discover.h | 136 +++ src/bacnet/basic/client/bac-rw.c | 213 ++--- src/bacnet/basic/client/bac-rw.h | 18 + src/bacnet/basic/service/h_rpm_a.c | 25 +- src/bacnet/property.c | 4 +- src/bacnet/rp.h | 9 + src/bacnet/rpm.c | 133 +++ src/bacnet/rpm.h | 7 + 15 files changed, 1945 insertions(+), 131 deletions(-) create mode 100644 apps/server-discover/Makefile create mode 100644 apps/server-discover/main.c create mode 100644 src/bacnet/basic/client/bac-discover.c create mode 100644 src/bacnet/basic/client/bac-discover.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ca99bcb4..be0f1a9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,11 @@ option( "compile the bacpoll app" ON) + option( + BACNET_BUILD_BACDISCOVER_APP + "compile the bacdiscover app" + ON) + option( BACDL_ETHERNET "compile with ethernet support" @@ -70,6 +75,18 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() +# Build types +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") + message(STATUS "Maximum optimization for speed") +elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") + message(STATUS "Maximum optimization for speed, debug info included") +elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") + message(STATUS "Maximum optimization for size") +else () + message(STATUS "Minimal optimization, debug info included") + add_compile_definitions(DEBUG_ENABLED=1) +endif () + option( BACNET_STACK_DEPRECATED_DISABLE "Disable deprecation compile warnings" @@ -671,6 +688,14 @@ if(BACNET_STACK_BUILD_APPS) target_link_libraries(bacpoll PRIVATE ${PROJECT_NAME}) endif(BACNET_BUILD_BACPOLL_APP) + if(BACNET_BUILD_BACDISCOVER_APP) + add_executable(bacdiscover + apps/server-discover/main.c + src/bacnet/basic/client/bac-discover.c + src/bacnet/basic/client/bac-rw.c) + target_link_libraries(bacdiscover PRIVATE ${PROJECT_NAME}) + endif(BACNET_BUILD_BACDISCOVER_APP) + if(NOT BACDL_ETHERNET) add_executable(readbdt apps/readbdt/main.c) target_link_libraries(readbdt PRIVATE ${PROJECT_NAME}) diff --git a/Makefile b/Makefile index 69129c99..5e8bafcb 100644 --- a/Makefile +++ b/Makefile @@ -157,6 +157,14 @@ netnumis: server: $(MAKE) -s -C apps $@ +.PHONY: server-client +server-client: + $(MAKE) LEGACY=true -s -C apps $@ + +.PHONY: server-discover +server-discover: + $(MAKE) LEGACY=true -s -C apps $@ + .PHONY: mstpcap mstpcap: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index e81d2e52..95d5cbfe 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -193,7 +193,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 apdu + delete-object server-discover apdu ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter whatisnetnum netnumis @@ -374,6 +374,10 @@ server: $(BACNET_LIB_TARGET) server-client: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ +.PHONY: server-discover +server-discover: $(BACNET_LIB_TARGET) + $(MAKE) -B -C $@ + .PHONY: timesync timesync: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ diff --git a/apps/server-discover/Makefile b/apps/server-discover/Makefile new file mode 100644 index 00000000..65b72cf4 --- /dev/null +++ b/apps/server-discover/Makefile @@ -0,0 +1,45 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name - BACnet Server-Discover Application +TARGET = bacdiscover +# 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 \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_CLIENT_DIR)/bac-discover.c \ + $(BACNET_CLIENT_DIR)/bac-rw.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-discover/main.c b/apps/server-discover/main.c new file mode 100644 index 00000000..bb5a3735 --- /dev/null +++ b/apps/server-discover/main.c @@ -0,0 +1,291 @@ +/** + * @file + * @author Steve Karg + * @date 2022 + * @brief Application to acquire devices and their object list from a BACnet + * network. + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +/* core library */ +#include "bacnet/config.h" +#include "bacnet/apdu.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/bacstr.h" +#include "bacnet/bactext.h" +#include "bacnet/dcc.h" +#include "bacnet/iam.h" +#include "bacnet/npdu.h" +#include "bacnet/version.h" +#include "bacnet/whois.h" +/* basic services */ +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/client/bac-discover.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" + +#if BACNET_SVC_SERVER +#error "App requires server-only features disabled! Set BACNET_SVC_SERVER=0" +#endif + +/* current version of the BACnet stack */ +static const char *BACnet_Version = BACNET_VERSION_TEXT; +/** Buffer used for receiving */ +static uint8_t Rx_Buf[MAX_MPDU]; +/* task timer for various BACnet timeouts */ +static struct mstimer BACnet_Task_Timer; +/* task timer for TSM timeouts */ +static struct mstimer BACnet_TSM_Timer; +/* task timer for printing data */ +static struct mstimer BACnet_Print_Timer; +/* flag to determine if devices or both devices and objects are printed */ +static bool Print_Summary = false; + +/** + * @brief Print the list of discovered devices and their objects + */ +void print_discovered_devices(void) +{ + unsigned int device_index = 0; + unsigned int device_count = 0; + unsigned int object_count = 0; + unsigned int object_index = 0; + unsigned int property_count = 0; + uint32_t device_id = 0; + BACNET_OBJECT_ID object_id = { 0 }; + unsigned long milliseconds = 0; + size_t heap_ram = 0; + char model_name[MAX_CHARACTER_STRING_BYTES] = { 0 }; + char object_name[MAX_CHARACTER_STRING_BYTES] = { 0 }; + + device_count = bacnet_discover_device_count(); + printf("----list of %u devices ----\n", device_count); + for (device_index = 0; device_index < device_count; device_index++) { + device_id = bacnet_discover_device_instance(device_index); + object_count = bacnet_discover_device_object_count(device_id); + milliseconds = bacnet_discover_device_elapsed_milliseconds(device_id); + heap_ram = bacnet_discover_device_memory(device_id); + /* convert to KB next highest value */ + bacnet_discover_property_name(device_id, OBJECT_DEVICE, device_id, + PROP_MODEL_NAME, model_name, sizeof(model_name), ""); + printf( + "device[%u] %7u \"%s\" object_list[%d] in %lums using %lu bytes\n", + device_index, device_id, model_name, object_count, milliseconds, + (unsigned long)heap_ram); + if (Print_Summary) { + continue; + } + for (object_index = 0; object_index < object_count; object_index++) { + if (bacnet_discover_device_object_identifier( + device_id, object_index, &object_id)) { + property_count = bacnet_discover_object_property_count( + device_id, object_id.type, object_id.instance); + bacnet_discover_property_name(device_id, object_id.type, + object_id.instance, PROP_OBJECT_NAME, object_name, + sizeof(object_name), ""); + printf(" object_list[%d] %s %u \"%s\" has %u properties\n", + object_index, bactext_object_type_name(object_id.type), + object_id.instance, object_name, property_count); + } + } + } +} + +/** + * @brief Non-blocking task for running BACnet server tasks + */ +static void bacnet_server_task(void) +{ + static bool initialized = false; + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + const unsigned timeout_ms = 5; + + if (!initialized) { + initialized = true; + /* broadcast an I-Am on startup */ + Send_I_Am(&Handler_Transmit_Buffer[0]); + } + /* input */ + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout_ms); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* 1 second tasks */ + if (mstimer_expired(&BACnet_Task_Timer)) { + mstimer_reset(&BACnet_Task_Timer); + dcc_timer_seconds(1); + datalink_maintenance_timer(1); + dlenv_maintenance_timer(1); + } + if (mstimer_expired(&BACnet_TSM_Timer)) { + mstimer_reset(&BACnet_TSM_Timer); + tsm_timer_milliseconds(mstimer_interval(&BACnet_TSM_Timer)); + } +} + +/** + * @brief Initialize the handlers for this server device + */ +static void bacnet_server_init(void) +{ + Device_Init(NULL); + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + /* we need to handle who-has to support dynamic object binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); + /* set the handler for all the services we don't implement */ + /* It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + /* 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); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); + mstimer_set(&BACnet_Task_Timer, 1000); + mstimer_set(&BACnet_TSM_Timer, 50); +} + +/** + * @brief Print the usage information for this application + */ +static void print_usage(const char *filename) +{ + printf("Usage: %s [--dnet]\n", filename); + printf(" [--discover-seconds][--print-seconds][--print-summary]\n"); + printf(" [--version][--help]\n"); +} + +/** + * @brief Print the help information for this application + */ +static void print_help(const char *filename) +{ + printf("Simulate a BACnet server-discovery device.\n"); + printf("--discover-seconds:\n" + "Number of seconds to wait before initiating the next discovery.\n"); + printf("--print-seconds:\n" + "Number of seconds to wait before printing list of devices.\n"); + printf("--print-summary:\n" + "Print only the list of devices.\n"); + printf("--dnet N\n" + "Optional BACnet network number N for directed requests.\n" + "Valid range is from 0 to 65535 where 0 is the local connection\n" + "and 65535 is network broadcast.\n"); + (void)filename; +} + +/** + * @brief Main function of server-client demo. + * @param argc [in] Arg count. + * @param argv [in] Takes one argument: the Device Instance #. + * @return 0 on success. + */ +int main(int argc, char *argv[]) +{ + int argi = 0; + unsigned int target_args = 0; + const char *filename = NULL; + uint32_t device_id = BACNET_MAX_INSTANCE; + long long_value = -1; + /* data from the command line */ + unsigned long print_seconds = 60; + unsigned long discover_seconds = 60; + uint16_t dnet = 0; + + filename = filename_remove_path(argv[0]); + for (argi = 1; argi < argc; argi++) { + if (strcmp(argv[argi], "--help") == 0) { + print_usage(filename); + print_help(filename); + return 0; + } + if (strcmp(argv[argi], "--version") == 0) { + printf("%s %s\n", filename, BACNET_VERSION_TEXT); + printf("Copyright (C) 2024 by Steve Karg and others.\n" + "This is free software; see the source for copying " + "conditions.\n" + "There is NO warranty; not even for MERCHANTABILITY or\n" + "FITNESS FOR A PARTICULAR PURPOSE.\n"); + return 0; + } + if (strcmp(argv[argi], "--discover-seconds") == 0) { + if (++argi < argc) { + discover_seconds = strtol(argv[argi], NULL, 0); + } + } else if (strcmp(argv[argi], "--print-seconds") == 0) { + if (++argi < argc) { + print_seconds = strtol(argv[argi], NULL, 0); + } + } else if (strcmp(argv[argi], "--print-summary") == 0) { + Print_Summary = true; + } else if (strcmp(argv[argi], "--dnet") == 0) { + if (++argi < argc) { + long_value = strtol(argv[argi], NULL, 0); + if ((long_value >= 0) && (long_value <= UINT16_MAX)) { + dnet = (uint16_t)long_value; + } + } + } else { + if (target_args == 0) { + device_id = strtol(argv[argi], NULL, 0); + target_args++; + } + } + } + if (device_id > BACNET_MAX_INSTANCE) { + debug_perror("device-instance=%u - it must be less than %u\n", + device_id, BACNET_MAX_INSTANCE); + return 1; + } + Device_Set_Object_Instance_Number(device_id); + debug_aprintf("BACnet Server-Discovery Demo\n" + "BACnet Stack Version %s\n" + "BACnet Device ID: %u\n" + "DNET: %u every %lu seconds\n" + "Print Devices: every %lu seconds (0=none)\n" + "Max APDU: %d\n", + BACnet_Version, Device_Object_Instance_Number(), dnet, discover_seconds, + print_seconds, MAX_APDU); + dlenv_init(); + atexit(datalink_cleanup); + bacnet_server_init(); + /* configure the discovery module */ + bacnet_discover_dnet_set(dnet); + bacnet_discover_seconds_set(discover_seconds); + bacnet_discover_init(); + atexit(bacnet_discover_cleanup); + mstimer_set(&BACnet_Print_Timer, print_seconds * 1000UL); + /* loop forever */ + for (;;) { + bacnet_server_task(); + bacnet_discover_task(); + if (mstimer_expired(&BACnet_Print_Timer)) { + mstimer_reset(&BACnet_Print_Timer); + print_discovered_devices(); + } + } + + return 0; +} diff --git a/apps/whois/main.c b/apps/whois/main.c index fb0dd9af..a21fcc87 100644 --- a/apps/whois/main.c +++ b/apps/whois/main.c @@ -45,7 +45,6 @@ #include "bacnet/basic/sys/mstimer.h" #include "bacnet/basic/sys/filename.h" #include "bacnet/basic/services.h" -#include "bacnet/basic/services.h" #include "bacnet/basic/tsm/tsm.h" #if defined(BACDL_MSTP) #include "rs485.h" diff --git a/src/bacnet/basic/client/bac-discover.c b/src/bacnet/basic/client/bac-discover.c new file mode 100644 index 00000000..c85191bd --- /dev/null +++ b/src/bacnet/basic/client/bac-discover.c @@ -0,0 +1,1155 @@ +/** + * @file + * @author Steve Karg + * @date 2024 + * @brief Discover all BACnet devices on a destination network + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bactext.h" +#include "bacnet/bacapp.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/keylist.h" +#include "bacnet/basic/services.h" +#include "bacnet/property.h" +/* us */ +#include "bacnet/basic/client/bac-rw.h" +#include "bacnet/basic/client/bac-discover.h" + +/* send a Who-Is to discover new devices */ +static struct mstimer WhoIs_Timer; +/* property R/W process interval timer */ +static struct mstimer Read_Write_Timer; +/* list of devices */ +static OS_Keylist Device_List = NULL; +/* discovery destination network */ +static uint16_t Target_DNET = 0; +/* re-discovery time */ +static unsigned long Discovery_Milliseconds; +/* states of discovery */ +typedef enum bacnet_discover_state_enum { + BACNET_DISCOVER_STATE_INIT = 0, + BACNET_DISCOVER_STATE_BINDING, + BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST, + BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_RESPONSE, + BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST, + BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE, + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST, + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE, + BACNET_DISCOVER_STATE_OBJECT_NEXT, + BACNET_DISCOVER_STATE_DONE +} BACNET_DISCOVER_STATE; + +typedef struct bacnet_property_data_t { + uint8_t *application_data; + int application_data_len; +} BACNET_PROPERTY_DATA; + +typedef struct bacnet_object_data_t { + OS_Keylist Property_List; + /* used for discovering object data */ + uint32_t Property_List_Size; + uint32_t Property_List_Index; +} BACNET_OBJECT_DATA; + +typedef struct bacnet_device_data_t { + OS_Keylist Object_List; + /* used for discovering device data */ + uint32_t Object_List_Size; + uint32_t Object_List_Index; + /* timer and stats */ + struct mstimer Discovery_Timer; + unsigned long Discovery_Elapsed_Milliseconds; + BACNET_DISCOVER_STATE Discovery_State; +} BACNET_DEVICE_DATA; + +/** + * @brief Add a ReadProperty reply data value to the property-list + * @param list - Keylist to add the property to + * @param key - BACnet property key + * @return Pointer to the property data structure + */ +static BACNET_PROPERTY_DATA *bacnet_property_data_add(OS_Keylist list, KEY key) +{ + BACNET_PROPERTY_DATA *data = NULL; + int index; + + data = Keylist_Data(list, key); + if (!data) { + data = calloc(1, sizeof(BACNET_PROPERTY_DATA)); + if (data) { + index = Keylist_Data_Add(list, key, data); + if (index < 0) { + free(data); + data = NULL; + } + } + } + + return data; +} + +/** + * @brief Remove all the property data from the property-list + * @param list - Keylist to remove the property from + */ +static void bacnet_property_data_cleanup(OS_Keylist list) +{ + BACNET_PROPERTY_DATA *data = NULL; + + do { + data = Keylist_Data_Pop(list); + if (data) { + free(data->application_data); + free(data); + } + } while (data); + Keylist_Delete(list); +} + +/** + * @brief Add an object to the object list + * @param list - Keylist to add the object to + * @param object_type - BACnet object type + * @param object_instance - BACnet object instance + * @return Pointer to the object data structure + */ +static BACNET_OBJECT_DATA *bacnet_object_data_add( + OS_Keylist list, BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + BACNET_OBJECT_DATA *data = NULL; + KEY key; + int index; + + key = KEY_ENCODE(object_type, object_instance); + data = Keylist_Data(list, key); + if (!data) { + data = calloc(1, sizeof(BACNET_OBJECT_DATA)); + if (data) { + data->Property_List = Keylist_Create(); + data->Property_List_Size = 0; + data->Property_List_Index = 0; + index = Keylist_Data_Add(list, key, data); + if (index < 0) { + free(data); + data = NULL; + } + } + } + + return data; +} + +/** + * @brief Add an object to the object list + * @param list - Keylist to add the object to + * @param object_type - BACnet object type + * @param object_instance - BACnet object instance + * @return Pointer to the object data structure + */ +static int bacnet_object_list_index( + OS_Keylist list, BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + KEY key; + + key = KEY_ENCODE(object_type, object_instance); + return Keylist_Index(list, key); +} + +/** + * @brief Remove all the property data from the object-list + * @param list - Keylist to remove the object and properties from + */ +static void bacnet_object_data_cleanup(OS_Keylist list) +{ + BACNET_OBJECT_DATA *data = NULL; + + do { + data = Keylist_Data_Pop(list); + if (data) { + bacnet_property_data_cleanup(data->Property_List); + free(data); + } + } while (data); + Keylist_Delete(list); +} + +/** + * @brief Add a new device to the device list + * @param list - Keylist to add the device to + * @param device_instance - BACnet device instance + * @return Pointer to the device data structure only if new + */ +static BACNET_DEVICE_DATA *bacnet_device_data_add(uint32_t device_instance) +{ + BACNET_DEVICE_DATA *data = NULL; + KEY key = device_instance; + int index; + + if (Device_List) { + data = Keylist_Data(Device_List, key); + if (!data) { + /* device is not in the list */ + data = calloc(1, sizeof(BACNET_DEVICE_DATA)); + if (data) { + data->Object_List = Keylist_Create(); + data->Discovery_State = BACNET_DISCOVER_STATE_INIT; + mstimer_set(&data->Discovery_Timer, 0); + /* other properties are already zeros */ + /* add to list */ + index = Keylist_Data_Add(Device_List, key, data); + if (index < 0) { + free(data); + data = NULL; + } + } + } + } + + return data; +} + +/** + * @brief Get an existing device from the device list + * @param list - Keylist to get the device from + * @param device_id - BACnet device instance + * @return Pointer to the device data structure + */ +static BACNET_DEVICE_DATA *bacnet_device_data( + OS_Keylist list, uint32_t device_id) +{ + KEY key = device_id; + BACNET_DEVICE_DATA *device_data; + + device_data = Keylist_Data(list, key); + + return device_data; +} + +/** + * @brief Remove all the device data from the device-list + */ +void bacnet_discover_cleanup(void) +{ + BACNET_DEVICE_DATA *data = NULL; + + do { + data = Keylist_Data_Pop(Device_List); + if (data) { + bacnet_object_data_cleanup(data->Object_List); + free(data); + } + } while (data); + Keylist_Delete(Device_List); +} + +/** + * @brief get the number of devices discovered + * @return the number of devices discovered + */ +int bacnet_discover_device_count(void) +{ + int count; + + count = Keylist_Count(Device_List); + + return count; +} + +/** + * @brief get the device ID at a particular index + * @param index - 0..N of max devices + * @return the device ID at a particular index, or UINT32_MAX if not found + */ +uint32_t bacnet_discover_device_instance(unsigned index) +{ + uint32_t instance = UINT32_MAX; + KEY key; + + if (Keylist_Index_Key(Device_List, index, &key)) { + instance = key; + } + + return instance; +} + +/** + * @brief get the number of objects discovered in a device + * @param device_id - ID of the destination device + * @return the number of objects discovered in a device + */ +int bacnet_discover_device_object_count(uint32_t device_id) +{ + int count = 0; + BACNET_DEVICE_DATA *device; + KEY key = device_id; + + device = Keylist_Data(Device_List, key); + if (device) { + count = Keylist_Count(device->Object_List); + } + + return count; +} + +/** + * @brief get the number of objects discovered in a device + * @param device_id - ID of the destination device + * @param index - 0..N of max objects in a device + * @param object_id - object type and instance if object exists + * @return true if an object ID was found at this index + */ +bool bacnet_discover_device_object_identifier( + uint32_t device_id, unsigned index, BACNET_OBJECT_ID *object_id) +{ + bool status = false; + BACNET_DEVICE_DATA *device; + KEY key = device_id; + + device = Keylist_Data(Device_List, key); + if (device) { + if (Keylist_Index_Key(device->Object_List, index, &key)) { + if (object_id) { + object_id->type = KEY_DECODE_TYPE(key); + object_id->instance = KEY_DECODE_ID(key); + } + status = true; + } + } + + return status; +} + +/** + * @brief Determine the amount of heap data used by a device + * @param device_id - BACnet device instance + * @return the amount of heap data used by a device + */ +size_t bacnet_discover_device_memory(uint32_t device_id) +{ + size_t heap_size = 0; + size_t object_count = 0, property_count = 0; + size_t i, j; + KEY key = device_id; + BACNET_DEVICE_DATA *device; + BACNET_OBJECT_DATA *object; + BACNET_PROPERTY_DATA *property; + + device = Keylist_Data(Device_List, key); + if (device) { + heap_size += sizeof(BACNET_DEVICE_DATA); + object_count = Keylist_Count(device->Object_List); + heap_size += (object_count * sizeof(BACNET_OBJECT_DATA)); + for (i = 0; i < object_count; i++) { + object = Keylist_Data_Index(device->Object_List, i); + if (object) { + property_count = Keylist_Count(object->Property_List); + heap_size += (property_count * sizeof(BACNET_PROPERTY_DATA)); + for (j = 0; j < property_count; j++) { + property = Keylist_Data_Index(object->Property_List, j); + if (property) { + heap_size += property->application_data_len; + } + } + } + } + } + + return heap_size; +} + +/** + * @brief get the elapsed time it took to discover a device + * @param device_id - ID of the destination device + * @return the elapsed time it took to discover a device + */ +unsigned long bacnet_discover_device_elapsed_milliseconds(uint32_t device_id) +{ + unsigned long milliseconds = 0; + BACNET_DEVICE_DATA *device; + KEY key = device_id; + + device = Keylist_Data(Device_List, key); + if (device) { + milliseconds = device->Discovery_Elapsed_Milliseconds; + } + + return milliseconds; +} + +/** + * @brief Get a property value from the device cache + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance number of the object to be read. + * @param object_property - BACnet property identifier + * @param value property value stored if available (see tag for type) + * @return true if found and value loaded + */ +bool bacnet_discover_property_value(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_APPLICATION_DATA_VALUE *value) +{ + bool status = false; + BACNET_DEVICE_DATA *device; + BACNET_OBJECT_DATA *object; + BACNET_PROPERTY_DATA *property; + KEY key = device_id; + int len = 0; + + if (!value) { + return false; + } + device = Keylist_Data(Device_List, key); + if (device) { + key = KEY_ENCODE(object_type, object_instance); + object = Keylist_Data(device->Object_List, key); + if (object) { + key = object_property; + property = Keylist_Data(object->Property_List, key); + if (property) { + if (property->application_data_len > 0) { + len = + bacapp_decode_known_property(property->application_data, + property->application_data_len, value, object_type, + object_property); + if (len > 0) { + status = true; + } + } else { + bacapp_value_list_init(value, 1); + status = true; + } + } + } + } + + return status; +} + +/** + * @brief Get a name property value from the device object property cache + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance number of the object to be read. + * @param object_property - BACnet property identifier + * @param buffer [out] Buffer to hold the property name. + * @param buffer_len [in] Length of the buffer. + * @param default_string [in] String to use if the property is not found. + * @return true if found and value copied, else false and default_string copied. + */ +bool bacnet_discover_property_name(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + char *buffer, + size_t buffer_len, + const char *default_string) +{ + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + bool status = false; + + if (buffer && buffer_len) { + status = bacnet_discover_property_value( + device_id, object_type, object_instance, object_property, &value); + if (status && value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + if (characterstring_valid(&value.type.Character_String)) { + strncpy(buffer, + characterstring_value(&value.type.Character_String), + buffer_len - 1); + } else { + status = false; + } + } + } + if (!status) { + strncpy(buffer, default_string, buffer_len); + } + + return status; +} + +/** + * @brief Get the object property count from object property cache + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance number of the object to be read. + * @return number of object properties + */ +unsigned int bacnet_discover_object_property_count(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + unsigned int count = 0; + BACNET_DEVICE_DATA *device; + BACNET_OBJECT_DATA *object; + KEY key = device_id; + + device = Keylist_Data(Device_List, key); + if (device) { + key = KEY_ENCODE(object_type, object_instance); + object = Keylist_Data(device->Object_List, key); + if (object) { + count = Keylist_Count(object->Property_List); + } + } + + return count; +} + +/** + * @brief get the number of objects discovered in a device + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance number of the object to be queried + * @param index - 0..N of max properties in an object instance + * @param property_id - property identifier if object exists + * @return true if an object property ID was found at this index + */ +bool bacnet_discover_object_property_identifier(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + unsigned index, + uint32_t *property_id) +{ + bool status = false; + BACNET_DEVICE_DATA *device; + BACNET_OBJECT_DATA *object; + KEY key = device_id; + + device = Keylist_Data(Device_List, key); + if (device) { + key = KEY_ENCODE(object_type, object_instance); + object = Keylist_Data(device->Object_List, key); + if (object) { + if (Keylist_Index_Key(object->Property_List, index, &key)) { + if (property_id) { + *property_id = key; + } + status = true; + } + } + } + + return status; +} + +/** + * @brief add a ReadProperty reply value from a device object property + * @param device_id [in] Device instance number where data originated + * @param rp_data [in] Pointer to the BACNET_READ_PROPERTY_DATA structure, + * which is packed with the information from the ReadProperty request. + * @param value [in] pointer to the BACNET_APPLICATION_DATA_VALUE structure + * which is packed with the decoded value from the ReadProperty request. + * @param device_data [in] Pointer to the device data structure + */ +static void bacnet_device_object_property_add(uint32_t device_id, + BACNET_READ_PROPERTY_DATA *rp_data, + BACNET_APPLICATION_DATA_VALUE *value, + BACNET_DEVICE_DATA *device_data) +{ + BACNET_OBJECT_DATA *object_data; + BACNET_PROPERTY_DATA *property_data; + + if (!rp_data || !value || !device_data) { + return; + } + if ((rp_data->object_type == OBJECT_DEVICE) && + (rp_data->object_instance == device_id) && + (rp_data->object_property == PROP_OBJECT_LIST)) { + if (value->tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + device_data->Object_List_Size = value->type.Unsigned_Int; + device_data->Object_List_Index = 0; + if (device_data->Discovery_State == + BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST) { + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_RESPONSE; + } + } else if (value->tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + if (rp_data->array_index <= device_data->Object_List_Size) { + object_data = bacnet_object_data_add(device_data->Object_List, + value->type.Object_Id.type, value->type.Object_Id.instance); + debug_printf("add %u object-list[%u] %s-%lu %s.\n", device_id, + device_data->Object_List_Index, + bactext_object_type_name(value->type.Object_Id.type), + (unsigned long)value->type.Object_Id.instance, + object_data ? "success" : "fail"); + if (device_data->Discovery_State == + BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST) { + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE; + } + } + } + } else { + /* move to next state */ + if (device_data->Discovery_State == + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST) { + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE; + } + object_data = bacnet_object_data_add(device_data->Object_List, + rp_data->object_type, rp_data->object_instance); + if (!object_data) { + debug_perror("%s-%u object fail to add!\n", + bactext_object_type_name(rp_data->object_type), + rp_data->object_instance); + return; + } + property_data = bacnet_property_data_add( + object_data->Property_List, rp_data->object_property); + if (!property_data) { + debug_perror("%s-%u %s property fail to add!\n", + bactext_object_type_name(rp_data->object_type), + rp_data->object_instance, + bactext_property_name(rp_data->object_property)); + return; + } + if (rp_data->application_data_len > 0) { + if (property_data->application_data_len != + rp_data->application_data_len) { + free(property_data->application_data); + property_data->application_data = + calloc(1, rp_data->application_data_len); + } + if (property_data->application_data) { + property_data->application_data_len = + rp_data->application_data_len; + memcpy(property_data->application_data, + rp_data->application_data, rp_data->application_data_len); + } else { + debug_perror("%s-%u %s property fail to allocate!\n", + bactext_object_type_name(rp_data->object_type), + rp_data->object_instance, + bactext_property_name(rp_data->object_property)); + } + } else { + free(property_data->application_data); + property_data->application_data = NULL; + property_data->application_data_len = 0; + } + if (rp_data->array_index == BACNET_ARRAY_ALL) { + debug_printf("%u object-list[%d] %s-%lu %s added.\n", device_id, + bacnet_object_list_index(device_data->Object_List, + rp_data->object_type, rp_data->object_instance), + bactext_object_type_name(rp_data->object_type), + (unsigned long)rp_data->object_instance, + bactext_property_name(rp_data->object_property)); + } else { + debug_printf("%u object-list[%d] %s-%lu %s[%lu] added.\n", + device_id, + bacnet_object_list_index(device_data->Object_List, + rp_data->object_type, rp_data->object_instance), + bactext_object_type_name(rp_data->object_type), + (unsigned long)rp_data->object_instance, + bactext_property_name(rp_data->object_property), + (unsigned long)rp_data->array_index); + } + } +} + +/** + * @brief Handle the error from a ReadProperty or ReadPropertyMultiple + * @param device_id - device instance number where data originated + * @param error_code - BACnet Error code + */ +static void Device_Error_Handler(uint32_t device_id, + BACNET_ERROR_CODE error_code, + BACNET_DEVICE_DATA *device_data) +{ + if (device_data) { + debug_printf( + "%u - %s\n", device_id, bactext_error_code_name((int)error_code)); + switch (device_data->Discovery_State) { + case BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST: + /* resend request */ + if (device_data->Object_List_Index != 0) { + device_data->Object_List_Index--; + } + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE; + break; + case BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST: + /* resend request */ + if (device_data->Object_List_Index != 0) { + device_data->Object_List_Index--; + } + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE; + break; + default: + break; + } + } +} + +/** + * @brief Reply with the value from the ReadProperty request + * @param device_id [in] Device instance number + * @param rp_data [in] Pointer to the BACNET_READ_PROPERTY_DATA structure, + * which is packed with the information from the ReadProperty request. + * @param value [in] pointer to the BACNET_APPLICATION_DATA_VALUE structure + * which is packed with the decoded value from the ReadProperty request. + */ +static void bacnet_read_property_reply(uint32_t device_id, + BACNET_READ_PROPERTY_DATA *rp_data, + BACNET_APPLICATION_DATA_VALUE *value) +{ + BACNET_DEVICE_DATA *device_data; + + if (!rp_data) { + return; + } + device_data = bacnet_device_data(Device_List, device_id); + if (!device_data) { + return; + } + if (rp_data->error_code != ERROR_CODE_SUCCESS) { + Device_Error_Handler(device_id, rp_data->error_code, device_data); + } else if (value) { + bacnet_device_object_property_add( + device_id, rp_data, value, device_data); + } +} + +/** + * @brief Non-blocking task for running BACnet discover state machine + * @param device_id - Device ID from discovered device + * @param device_data - Pointer to the device data structure + */ +void bacnet_discover_device_fsm( + uint32_t device_id, BACNET_DEVICE_DATA *device_data) +{ + KEY key = 0; + BACNET_OBJECT_TYPE object_type = 0; + uint32_t object_instance = 0; + bool status = false; + + if (!device_data) { + return; + } + switch (device_data->Discovery_State) { + case BACNET_DISCOVER_STATE_INIT: + status = bacnet_read_property_queue( + device_id, OBJECT_DEVICE, device_id, PROP_OBJECT_LIST, 0); + if (status) { + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST; + } else { + debug_perror("%u object-list-size fail to queue!\n", device_id); + } + break; + case BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST: + /* waiting for response */ + return; + case BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_RESPONSE: + device_data->Object_List_Index = 0; + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE; + break; + case BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST: + /* waiting for response */ + break; + case BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE: + device_data->Object_List_Index++; + if (device_data->Object_List_Index <= + device_data->Object_List_Size) { + debug_printf("%u object-list[%u] size=%u.\n", device_id, + device_data->Object_List_Index, + device_data->Object_List_Size); + status = bacnet_read_property_queue(device_id, OBJECT_DEVICE, + device_id, PROP_OBJECT_LIST, + device_data->Object_List_Index); + if (status) { + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST; + return; + } else { + debug_perror("%u object-list[%u] %s-%u fail to queue!\n", + device_id, device_data->Object_List_Index, + bactext_object_type_name(object_type), + (unsigned)object_instance); + device_data->Object_List_Index--; + } + } else { + device_data->Object_List_Index = 0; + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE; + } + break; + case BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST: + /* waiting for response */ + break; + case BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE: + if (device_data->Object_List_Index < + device_data->Object_List_Size) { + if (Keylist_Index_Key(device_data->Object_List, + device_data->Object_List_Index, &key)) { + object_type = KEY_DECODE_TYPE(key); + object_instance = KEY_DECODE_ID(key); + debug_printf("%u object-list[%u] %s-%u read ALL.\n", + device_id, device_data->Object_List_Index, + bactext_object_type_name(object_type), + (unsigned)object_instance); + status = bacnet_read_property_queue(device_id, object_type, + object_instance, PROP_ALL, BACNET_ARRAY_ALL); + } + if (status) { + device_data->Discovery_State = + BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST; + device_data->Object_List_Index++; + } else { + debug_perror("%u object-list[%u] %s-%u fail to queue!\n", + device_id, device_data->Object_List_Index, + bactext_object_type_name(object_type), + (unsigned)object_instance); + } + } else { + /* track the duration */ + device_data->Discovery_Elapsed_Milliseconds = + mstimer_elapsed(&device_data->Discovery_Timer); + /* rediscover in the future */ + mstimer_set( + &device_data->Discovery_Timer, Discovery_Milliseconds); + device_data->Discovery_State = BACNET_DISCOVER_STATE_DONE; + } + break; + case BACNET_DISCOVER_STATE_DONE: + /* finished getting all the object properties */ + if (mstimer_expired(&device_data->Discovery_Timer)) { + mstimer_set(&device_data->Discovery_Timer, 0); + device_data->Discovery_State = BACNET_DISCOVER_STATE_INIT; + } + break; + default: + debug_perror("%u unknown state %u!\n", device_id, + device_data->Discovery_State); + break; + } +} + +/** + * @brief Adds a device to the device list + * @param device_id - Device ID from discovered device + * @param src - BACnet address from discovered device + * @return non-zero device index for device if added or existing + */ +static void bacnet_discover_devices_task(void) +{ + unsigned int device_index = 0; + unsigned int device_count = 0; + uint32_t device_id = 0; + BACNET_DEVICE_DATA *device_data; + KEY key; + + device_count = Keylist_Count(Device_List); + for (device_index = 0; device_index < device_count; device_index++) { + device_data = Keylist_Data_Index(Device_List, device_index); + if (!device_data) { + debug_perror("device[%u] is NULL!\n", device_index); + continue; + } + if (Keylist_Index_Key(Device_List, device_index, &key)) { + device_id = key; + bacnet_discover_device_fsm(device_id, device_data); + } + } +} + +/** + * @brief Iterate a specific device object property list + * @param callback - function to call for each device object property + * @param context - pointer to user data + * @return true if the iteration completed, false if it stopped early + */ +bool bacnet_discover_device_object_property_iterate(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + bacnet_discover_device_callback callback, + void *context) +{ + size_t property_count = 0; + size_t device_index = 0, object_index = 0, property_index = 0; + BACNET_DEVICE_DATA *device; + BACNET_OBJECT_DATA *object; + BACNET_PROPERTY_DATA *property; + BACNET_READ_PROPERTY_DATA rp_data = { 0 }; + bool status = true; + KEY key = device_id; + + /* device */ + device = Keylist_Data(Device_List, key); + if (!device) { + return true; + } + device_index = Keylist_Index(Device_List, key); + /* object */ + key = KEY_ENCODE(object_type, object_instance); + object = Keylist_Data(device->Object_List, key); + if (!object) { + return true; + } + object_index = Keylist_Index(device->Object_List, key); + rp_data.object_type = object_type; + rp_data.object_instance = object_instance; + /* property */ + property_count = Keylist_Count(object->Property_List); + for (property_index = 0; property_index < property_count; + property_index++) { + if (Keylist_Index_Key(object->Property_List, property_index, &key)) { + rp_data.object_property = key; + } else { + continue; + } + property = Keylist_Data_Index(object->Property_List, property_index); + if (property) { + rp_data.error_class = ERROR_CLASS_PROPERTY; + rp_data.error_code = ERROR_CODE_SUCCESS; + rp_data.application_data = property->application_data; + rp_data.application_data_len = property->application_data_len; + status = callback(device_id, device_index, object_index, + property_index, &rp_data, context); + /* callback returns true if the iteration + should continue, false if it should stop */ + if (!status) { + return false; + } + } else { + rp_data.application_data = NULL; + rp_data.application_data_len = 0; + rp_data.error_class = ERROR_CLASS_PROPERTY; + rp_data.error_code = ERROR_CODE_UNKNOWN_PROPERTY; + status = callback(device_id, device_index, object_index, + property_index, &rp_data, context); + /* callback returns true if the iteration + should continue, false if it should stop */ + if (!status) { + return false; + } + } + } + + return true; +} + +/** + * @brief Iterate a specific device object list + * @param callback - function to call for each device object property + * @param context - pointer to user data + * @return true if the iteration completed, false if it stopped early + */ +bool bacnet_discover_device_object_iterate( + uint32_t device_id, bacnet_discover_device_callback callback, void *context) +{ + size_t object_count = 0; + size_t object_index = 0; + BACNET_DEVICE_DATA *device; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + KEY key = device_id; + bool status = false; + + device = Keylist_Data(Device_List, key); + if (!device) { + return true; + } + object_count = Keylist_Count(device->Object_List); + for (object_index = 0; object_index < object_count; object_index++) { + if (Keylist_Index_Key(device->Object_List, object_index, &key)) { + object_type = KEY_DECODE_TYPE(key); + object_instance = KEY_DECODE_ID(key); + } else { + continue; + } + status = bacnet_discover_device_object_property_iterate( + device_id, object_type, object_instance, callback, context); + if (!status) { + return false; + } + } + + return true; +} + +/** + * @brief Iterate the device list + * @param callback - function to call for each device object property + * @param context - pointer to user data + * @return true if the iteration completed, false if it stopped early + */ +bool bacnet_discover_device_iterate( + bacnet_discover_device_callback callback, void *context) +{ + size_t device_count = 0; + size_t device_index = 0; + uint32_t device_id = 0; + bool status = true; + KEY key = 0; + + device_count = Keylist_Count(Device_List); + for (device_index = 0; device_index < device_count; device_index++) { + if (Keylist_Index_Key(Device_List, device_index, &key)) { + device_id = key; + status = bacnet_discover_device_object_iterate( + device_id, callback, context); + if (!status) { + return false; + } + } else { + continue; + } + } + + return true; +} + +/** + * @brief Non-blocking task for running BACnet client tasks + * @param dest - BACnet address of the destination discovery + */ +void bacnet_discover_task(void) +{ + BACNET_ADDRESS dest = { 0 }; + + if (mstimer_expired(&WhoIs_Timer)) { + mstimer_restart(&WhoIs_Timer); + dest.net = Target_DNET; + Send_WhoIs_To_Network(&dest, -1, -1); + } + if (mstimer_expired(&Read_Write_Timer)) { + mstimer_restart(&Read_Write_Timer); + bacnet_read_write_task(); + } + if (bacnet_read_write_idle()) { + bacnet_discover_devices_task(); + } +} + +/** + * @brief Set the BACnet time between discovery in seconds + * @param seconds - number of seconds between discovery intervals + */ +void bacnet_discover_dnet_set(uint16_t dnet) +{ + Target_DNET = dnet; +} + +/** + * @brief Get the BACnet time between discovery in seconds + * @return number of seconds between discovery intervals + */ +uint16_t bacnet_discover_dnet(void) +{ + return Target_DNET; +} + +/** + * @brief Sets a Vendor ID filter on I-Am bindings to limit the address + * cache usage when we are only reading/writing to a specific vendor ID + * @param vendor_id - vendor ID to filter, 0=no filter + */ +void bacnet_discover_vendor_id_set(uint16_t vendor_id) +{ + bacnet_read_write_vendor_id_filter_set(vendor_id); +} + +/** + * @brief Gets a Vendor ID filter on I-Am bindings to limit the address + * cache usage when we are only reading/writing to a specific vendor ID + * @return vendor_id - vendor ID to filter, 0=no filter + */ +uint16_t bacnet_discover_vendor_id(void) +{ + return bacnet_read_write_vendor_id_filter(); +} + +/** + * @brief Set the BACnet time between discovery in seconds + * @param seconds - number of seconds between discovery intervals + */ +void bacnet_discover_seconds_set(unsigned int seconds) +{ + Discovery_Milliseconds = seconds * 1000; +} + +/** + * @brief Get the BACnet time between discovery in seconds + * @return number of seconds between discovery intervals + */ +unsigned int bacnet_discover_seconds(void) +{ + return Discovery_Milliseconds = 1000; +} + +/** + * @brief Set the millisecond timer for the read propcess (default=10ms) + * @param milliseconds - read process task time + */ +void bacnet_discover_read_process_milliseconds_set(unsigned long milliseconds) +{ + mstimer_set(&Read_Write_Timer, milliseconds); +} + +/** + * @brief Get the millisecond timer for the read propcess (default=10ms) + * @return read process task time in milliseconds + */ +unsigned long bacnet_discover_read_process_milliseconds(void) +{ + return mstimer_interval(&Read_Write_Timer); +} + +/** + * Save the I-Am service data to a data store + * + * @param device_instance [in] device instance number where data originated + * @param max_apdu [in] maximum APDU size + * @param segmentation [in] segmentation flag + * @param vendor_id [in] vendor identifier + */ +void bacnet_discover_device_add(uint32_t device_instance, + unsigned max_apdu, + int segmentation, + uint16_t vendor_id) +{ + BACNET_DEVICE_DATA *device_data; + + (void)max_apdu; + (void)segmentation; + device_data = bacnet_device_data_add(device_instance); + debug_printf("device[%d] %lu - vendor=%u %s.\n", + Keylist_Index(Device_List, device_instance), device_instance, vendor_id, + device_data ? "success" : "fail"); +} + +/** + * @brief Initializes the ReadProperty module + */ +void bacnet_discover_init(void) +{ + Device_List = Keylist_Create(); + bacnet_read_write_init(); + /* default value in case it is not set */ + if (!mstimer_interval(&WhoIs_Timer)) { + mstimer_set(&WhoIs_Timer, 5UL * 60UL * 1000UL); + } + /* force WhoIs_Timer to be expired to send WhoIs immediately */ + WhoIs_Timer.start = mstimer_now() - WhoIs_Timer.interval; + /* default value in case it is not set */ + if (!mstimer_interval(&Read_Write_Timer)) { + mstimer_set(&Read_Write_Timer, 10); + } + bacnet_read_write_value_callback_set(bacnet_read_property_reply); + bacnet_read_write_device_callback_set(bacnet_discover_device_add); +} diff --git a/src/bacnet/basic/client/bac-discover.h b/src/bacnet/basic/client/bac-discover.h new file mode 100644 index 00000000..eddc44f1 --- /dev/null +++ b/src/bacnet/basic/client/bac-discover.h @@ -0,0 +1,136 @@ +/** + * @file + * @author Steve Karg + * @date 2024 + * + * SPDX-License-Identifier: MIT + */ +#ifndef BAC_DISCOVER_H +#define BAC_DISCOVER_H + +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/rp.h" +#include "bacnet/bacnet_stack_exports.h" + +/** + * @brief Callback function for iterating the results of the device discovery. + * @param device_id [in] The device ID of the data + * @param device_index [in] The index of the device in the list of discovered devices + * @param object_index [in] The index of the object in the list of discovered objects in the device + * @param property_index [in] The index of the property in the list of discovered properties in the object in the device + * @param rp_data [in] The contents of the device object property + * @param context_data [in] The context data passed to the discover function + * @return true if the iteration should continue, false if it should stop +*/ +typedef bool (*bacnet_discover_device_callback) ( + uint32_t device_id, + unsigned device_index, + unsigned object_index, + unsigned property_index, + BACNET_READ_PROPERTY_DATA * rp_data, + void *context_data); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void bacnet_discover_cleanup(void); + +BACNET_STACK_EXPORT +int bacnet_discover_device_count(void); +BACNET_STACK_EXPORT +uint32_t bacnet_discover_device_instance(unsigned index); +BACNET_STACK_EXPORT +int bacnet_discover_device_object_count(uint32_t device_id); +BACNET_STACK_EXPORT +bool bacnet_discover_device_object_identifier(uint32_t device_id, + unsigned index, BACNET_OBJECT_ID *object_id); +BACNET_STACK_EXPORT +unsigned long bacnet_discover_device_elapsed_milliseconds( + uint32_t device_id); +BACNET_STACK_EXPORT +size_t bacnet_discover_device_memory( + uint32_t device_id); +BACNET_STACK_EXPORT +unsigned int bacnet_discover_object_property_count( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); +BACNET_STACK_EXPORT +bool bacnet_discover_object_property_identifier( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + unsigned index, + uint32_t *property_id); +BACNET_STACK_EXPORT +bool bacnet_discover_property_value(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_APPLICATION_DATA_VALUE *value); +BACNET_STACK_EXPORT +bool bacnet_discover_property_name(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + char *buffer, + size_t buffer_len, + const char *default_string); + +BACNET_STACK_EXPORT +bool bacnet_discover_device_object_property_iterate( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + bacnet_discover_device_callback callback, void *context); +BACNET_STACK_EXPORT +bool bacnet_discover_device_object_iterate( + uint32_t device_id, + bacnet_discover_device_callback callback, void *context); +BACNET_STACK_EXPORT +bool bacnet_discover_device_iterate( + bacnet_discover_device_callback callback, void *context); + +BACNET_STACK_EXPORT +void bacnet_discover_task(void); + +BACNET_STACK_EXPORT +void bacnet_discover_dnet_set(uint16_t dnet); +BACNET_STACK_EXPORT +uint16_t bacnet_discover_dnet(void); + +BACNET_STACK_EXPORT +void bacnet_discover_vendor_id_set(uint16_t vendor_id); +BACNET_STACK_EXPORT +uint16_t bacnet_discover_vendor_id(void); + +BACNET_STACK_EXPORT +void bacnet_discover_seconds_set(unsigned int seconds); +BACNET_STACK_EXPORT +unsigned int bacnet_discover_seconds(void); + +BACNET_STACK_EXPORT +void bacnet_discover_read_process_milliseconds_set(unsigned long milliseconds); +BACNET_STACK_EXPORT +unsigned long bacnet_discover_read_process_milliseconds(void); + +BACNET_STACK_EXPORT +void bacnet_discover_device_add( + uint32_t device_instance, + unsigned max_apdu, + int segmentation, + uint16_t vendor_id); + +BACNET_STACK_EXPORT +void bacnet_discover_init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/client/bac-rw.c b/src/bacnet/basic/client/bac-rw.c index b21f8979..167c8fb2 100644 --- a/src/bacnet/basic/client/bac-rw.c +++ b/src/bacnet/basic/client/bac-rw.c @@ -32,6 +32,8 @@ static struct mstimer Cache_Timer; static struct mstimer Read_Write_Timer; /* where the data from the read is stored */ static bacnet_read_write_value_callback_t bacnet_read_write_value_callback; +/* where the data from the I-Am is called */ +static bacnet_read_write_device_callback_t bacnet_read_write_device_callback; /* states for client task */ typedef enum { @@ -170,6 +172,10 @@ static void My_I_Am_Bind( } if (bind) { address_add_binding(device_id, max_apdu, src); + if (bacnet_read_write_device_callback) { + bacnet_read_write_device_callback( + device_id, max_apdu, segmentation, vendor_id); + } } } } @@ -191,6 +197,64 @@ static void MyWritePropertySimpleAckHandler( } } +/** + * @brief Process a ReadProperty-ACK message + * @param device_id [in] The device ID of the source of the message + * @param rp_data [in] The contents of the service request. + */ +static void bacnet_read_property_ack_process( + uint32_t device_id, BACNET_READ_PROPERTY_DATA *rp_data) +{ + BACNET_APPLICATION_DATA_VALUE *value; + uint8_t *apdu; + int apdu_len, len; + BACNET_ARRAY_INDEX array_index = 0; + + if (rp_data) { + apdu = rp_data->application_data; + apdu_len = rp_data->application_data_len; + while (apdu_len) { + value = &Target_Decoded_Property_Value; + len = bacapp_decode_known_property(apdu, (unsigned)apdu_len, value, + rp_data->object_type, rp_data->object_property); + if (len > 0) { + if ((len < apdu_len) && + (rp_data->array_index == BACNET_ARRAY_ALL)) { + /* assume that since there is more data that this + is an array and split full array of elements + into separate RP Acks */ + array_index = 1; + } + rp_data->error_class = ERROR_CLASS_SERVICES; + rp_data->error_code = ERROR_CODE_SUCCESS; + if (array_index) { + rp_data->array_index = array_index; + } + if (bacnet_read_write_value_callback) { + bacnet_read_write_value_callback(device_id, rp_data, value); + } + /* see if there is any more data */ + if (len < apdu_len) { + apdu += len; + apdu_len -= len; + if (array_index) { + array_index++; + } + } else { + break; + } + } else { + rp_data->error_class = ERROR_CLASS_SERVICES; + rp_data->error_code = ERROR_CODE_SUCCESS; + if (bacnet_read_write_value_callback) { + bacnet_read_write_value_callback(device_id, rp_data, NULL); + } + break; + } + } + } +} + /** Handler for a ReadProperty ACK. * Saves the data from a matching read-property request * @@ -207,13 +271,11 @@ static void My_Read_Property_Ack_Handler(uint8_t *service_request, { int len = 0; BACNET_READ_PROPERTY_DATA rp_data; - uint8_t *application_data; - int application_data_len; - BACNET_APPLICATION_DATA_VALUE *value; uint32_t device_id = 0; if (address_match(&Target_Address, src) && (service_data->invoke_id == Request_Invoke_ID)) { + address_get_device_id(src, &device_id); len = rp_ack_decode_service_request( service_request, service_len, &rp_data); if (len < 0) { @@ -222,68 +284,7 @@ static void My_Read_Property_Ack_Handler(uint8_t *service_request, Error_Class = ERROR_CLASS_SERVICES; Error_Code = ERROR_CODE_INTERNAL_ERROR; } else { - address_get_device_id(src, &device_id); - application_data = rp_data.application_data; - application_data_len = rp_data.application_data_len; - /* value? need to loop until all of the len is gone... */ - for (;;) { - value = &Target_Decoded_Property_Value; - len = bacapp_decode_application_data( - application_data, (uint8_t)application_data_len, value); - if (len > 0) { - /* handle the data */ - rp_data.error_class = ERROR_CLASS_SERVICES; - rp_data.error_code = ERROR_CODE_SUCCESS; - if (bacnet_read_write_value_callback) { - bacnet_read_write_value_callback( - device_id, &rp_data, value); - } - /* see if there is any more data */ - if (len < application_data_len) { - application_data += len; - application_data_len -= len; - } else { - break; - } - } else { - break; - } - } - } - } -} - -static void bacnet_rpm_process( - uint32_t device_id, BACNET_READ_ACCESS_DATA *rpm_data) -{ - BACNET_PROPERTY_REFERENCE *listOfProperties; - BACNET_APPLICATION_DATA_VALUE *value; - BACNET_READ_PROPERTY_DATA rp_data; - - if (rpm_data) { - rp_data.error_class = ERROR_CLASS_SERVICES; - rp_data.error_code = ERROR_CODE_SUCCESS; - rp_data.object_type = rpm_data->object_type; - rp_data.object_instance = rpm_data->object_instance; - listOfProperties = rpm_data->listOfProperties; - while (listOfProperties) { - rp_data.object_property = listOfProperties->propertyIdentifier; - rp_data.array_index = listOfProperties->propertyArrayIndex; - if (listOfProperties->propertyArrayIndex == BACNET_ARRAY_ALL) { - rp_data.array_index = 1; - } - value = listOfProperties->value; - while (value) { - if (bacnet_read_write_value_callback) { - bacnet_read_write_value_callback( - device_id, &rp_data, value); - } - value = value->next; - if (listOfProperties->propertyArrayIndex == BACNET_ARRAY_ALL) { - rp_data.array_index++; - } - } - listOfProperties = listOfProperties->next; + bacnet_read_property_ack_process(device_id, &rp_data); } } } @@ -297,67 +298,20 @@ static void bacnet_rpm_process( * @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information * decoded from the APDU header of this message. */ -static void My_Read_Property_Multiple_Ack_Handler(uint8_t *service_request, - uint16_t service_len, +static void My_Read_Property_Multiple_Ack_Handler(uint8_t *apdu, + uint16_t apdu_len, BACNET_ADDRESS *src, BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) { - int len = 0; - BACNET_READ_ACCESS_DATA *rpm_data; - BACNET_READ_ACCESS_DATA *old_rpm_data; - BACNET_PROPERTY_REFERENCE *rpm_property; - BACNET_PROPERTY_REFERENCE *old_rpm_property; - BACNET_APPLICATION_DATA_VALUE *value; - BACNET_APPLICATION_DATA_VALUE *old_value; + BACNET_READ_PROPERTY_DATA rp_data = { 0 }; uint32_t device_id = 0; + address_get_device_id(src, &device_id); if (address_match(&Target_Address, src) && (service_data->invoke_id == Request_Invoke_ID)) { - rpm_data = calloc(1, sizeof(BACNET_READ_ACCESS_DATA)); - if (rpm_data) { - len = rpm_ack_decode_service_request( - service_request, service_len, rpm_data); - } - if (len > 0) { - address_get_device_id(src, &device_id); - while (rpm_data) { - rpm_ack_print_data(rpm_data); - bacnet_rpm_process(device_id, rpm_data); - rpm_property = rpm_data->listOfProperties; - while (rpm_property) { - value = rpm_property->value; - while (value) { - old_value = value; - value = value->next; - free(old_value); - } - old_rpm_property = rpm_property; - rpm_property = rpm_property->next; - free(old_rpm_property); - } - old_rpm_data = rpm_data; - rpm_data = rpm_data->next; - free(old_rpm_data); - } - } else { - while (rpm_data) { - rpm_property = rpm_data->listOfProperties; - while (rpm_property) { - value = rpm_property->value; - while (value) { - old_value = value; - value = value->next; - free(old_value); - } - old_rpm_property = rpm_property; - rpm_property = rpm_property->next; - free(old_rpm_property); - } - old_rpm_data = rpm_data; - rpm_data = rpm_data->next; - free(old_rpm_data); - } - } + rpm_ack_object_property_process(apdu, apdu_len, + device_id, &rp_data, + bacnet_read_property_ack_process); } } @@ -546,6 +500,17 @@ void bacnet_read_write_value_callback_set( bacnet_read_write_value_callback = callback; } +/** + * @brief Sets the callback for when an I-Am returns device data + * + * @param callback - function for callback + */ +void bacnet_read_write_device_callback_set( + bacnet_read_write_device_callback_t callback) +{ + bacnet_read_write_device_callback = callback; +} + /** * @brief Handles the ReadProperty repetitive task */ @@ -879,6 +844,16 @@ void bacnet_read_write_vendor_id_filter_set(uint16_t vendor_id) Target_Vendor_ID = vendor_id; } +/** + * @brief Gets a Vendor ID filter on I-Am bindings to limit the address + * cache usage when we are only reading/writing to a specific vendor ID + * @return vendor_id - vendor ID to filter, 0=no filter + */ +uint16_t bacnet_read_write_vendor_id_filter(void) +{ + return Target_Vendor_ID; +} + /** * @brief Initializes the ReadProperty module */ diff --git a/src/bacnet/basic/client/bac-rw.h b/src/bacnet/basic/client/bac-rw.h index d1be99e8..3ec36f4f 100644 --- a/src/bacnet/basic/client/bac-rw.h +++ b/src/bacnet/basic/client/bac-rw.h @@ -28,6 +28,19 @@ typedef void (*bacnet_read_write_value_callback_t)(uint32_t device_instance, BACNET_READ_PROPERTY_DATA *rp_data, BACNET_APPLICATION_DATA_VALUE *value); +/** + * Save the I-Am service data to a data store + * + * @param device_instance [in] device instance number where data originated + * @param max_apdu [in] maximum APDU size + * @param segmentation [in] segmentation flag + * @param vendor_id [in] vendor identifier + */ +typedef void (*bacnet_read_write_device_callback_t)(uint32_t device_instance, + unsigned max_apdu, + int segmentation, + uint16_t vendor_id); + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -97,7 +110,12 @@ BACNET_STACK_EXPORT void bacnet_read_write_value_callback_set( bacnet_read_write_value_callback_t callback); BACNET_STACK_EXPORT +void bacnet_read_write_device_callback_set( + bacnet_read_write_device_callback_t callback); +BACNET_STACK_EXPORT void bacnet_read_write_vendor_id_filter_set(uint16_t vendor_id); +BACNET_STACK_EXPORT +uint16_t bacnet_read_write_vendor_id_filter(void); #ifdef __cplusplus } diff --git a/src/bacnet/basic/service/h_rpm_a.c b/src/bacnet/basic/service/h_rpm_a.c index 92d7564e..8e252ed6 100644 --- a/src/bacnet/basic/service/h_rpm_a.c +++ b/src/bacnet/basic/service/h_rpm_a.c @@ -62,6 +62,7 @@ int rpm_ack_decode_service_request( int len = 0; /* number of bytes returned from decoding */ uint8_t tag_number = 0; /* decoded tag number */ uint32_t len_value = 0; /* decoded length value */ + int data_len = 0; /* data blob length */ BACNET_READ_ACCESS_DATA *rpm_object; BACNET_READ_ACCESS_DATA *old_rpm_object; BACNET_PROPERTY_REFERENCE *rpm_property; @@ -110,6 +111,8 @@ int rpm_ack_decode_service_request( apdu_len -= len; apdu += len; if (apdu_len && decode_is_opening_tag_number(apdu, 4)) { + data_len = bacapp_data_len(apdu, (unsigned)apdu_len, + rpm_property->propertyIdentifier); /* propertyValue */ decoded_len++; apdu_len--; @@ -121,7 +124,7 @@ int rpm_ack_decode_service_request( /* Special case for an empty array - we decode it as null */ if (apdu_len && decode_is_closing_tag_number(apdu, 4)) { - /* NULL value has tag 0, that was already set by calloc */ + bacapp_value_list_init(value, 1); decoded_len++; apdu_len--; apdu++; @@ -134,13 +137,19 @@ int rpm_ack_decode_service_request( * OK. */ if (len < 0) { /* problem decoding */ - PERROR("RPM Ack: unable to decode! %s:%s\n", - bactext_object_type_name( - rpm_object->object_type), - bactext_property_name( - rpm_property->propertyIdentifier)); - /* note: caller will free the memory */ - return BACNET_STATUS_ERROR; + if (data_len >= 0) { + /* valid data that we'll skip over */ + len = data_len; + bacapp_value_list_init(value, 1); + } else { + PERROR("RPM Ack: unable to decode! %s:%s\n", + bactext_object_type_name( + rpm_object->object_type), + bactext_property_name( + rpm_property->propertyIdentifier)); + /* note: caller will free the memory */ + return BACNET_STATUS_ERROR; + } } decoded_len += len; apdu_len -= len; diff --git a/src/bacnet/property.c b/src/bacnet/property.c index b003d5dc..d1778230 100644 --- a/src/bacnet/property.c +++ b/src/bacnet/property.c @@ -1535,7 +1535,7 @@ BACNET_PROPERTY_ID property_list_special_property( BACNET_PROPERTY_ID special_property, unsigned index) { - int property = -1; /* return value */ + BACNET_PROPERTY_ID property = UINT32_MAX; /* return value */ unsigned required, optional, proprietary; struct special_property_list_t PropertyList = { 0 }; @@ -1573,7 +1573,7 @@ BACNET_PROPERTY_ID property_list_special_property( } } - return (BACNET_PROPERTY_ID)property; + return property; } unsigned property_list_special_count( diff --git a/src/bacnet/rp.h b/src/bacnet/rp.h index effed2b7..3c302e16 100644 --- a/src/bacnet/rp.h +++ b/src/bacnet/rp.h @@ -57,6 +57,15 @@ typedef int ( *read_property_function) ( BACNET_READ_PROPERTY_DATA * rp_data); +/** + * @brief Process a ReadProperty-ACK message + * @param device_id [in] The device ID of the source of the message + * @param rp_data [in] The contents of the ReadProperty-ACK message + */ +typedef void ( + *read_property_ack_process) ( + uint32_t device_id, BACNET_READ_PROPERTY_DATA *rp_data); + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ diff --git a/src/bacnet/rpm.c b/src/bacnet/rpm.c index 49e6817e..ea963803 100644 --- a/src/bacnet/rpm.c +++ b/src/bacnet/rpm.c @@ -587,4 +587,137 @@ int rpm_ack_decode_object_property(uint8_t *apdu, return (int)len; } + +/** + * @brief Decode the RPM Ack and call the ReadProperty-ACK function to + * process each property value of the reply. + * + * ReadAccessResult ::= SEQUENCE { + * object-identifier [0] BACnetObjectIdentifier, + * list-of-results [1] SEQUENCE OF SEQUENCE { + * property-identifier [2] BACnetPropertyIdentifier, + * property-array-index [3] Unsigned OPTIONAL, + * -- used only with array datatype + * -- if omitted with an array the entire + * -- array is referenced + * read-result CHOICE { + * property-value [4] ABSTRACT-SYNTAX.&Type, + * property-access-error [5] Error + * } + * } + * } + * + * @param apdu [in] Buffer of bytes received. + * @param apdu_len [in] Count of valid bytes in the buffer. + * @param device_id [in] The device ID of the device that replied. + * @param rp_data [in] The data structure to be filled. + * @param callback [in] The function to call for each property value. +*/ +void rpm_ack_object_property_process( + uint8_t *apdu, + unsigned apdu_len, + uint32_t device_id, + BACNET_READ_PROPERTY_DATA *rp_data, + read_property_ack_process callback) +{ + int len = 0; + uint16_t application_data_len; + uint32_t error_value = 0; /* decoded error value */ + + if (!apdu) { + return; + } + if (!rp_data) { + return; + } + while (apdu_len) { + /* object-identifier [0] BACnetObjectIdentifier */ + /* list-of-results [1] SEQUENCE OF SEQUENCE */ + len = rpm_ack_decode_object_id( + apdu, apdu_len, &rp_data->object_type, &rp_data->object_instance); + if (len <= 0) { + /* malformed */ + return; + } + apdu_len -= len; + apdu += len; + while (apdu_len) { + len = rpm_ack_decode_object_property(apdu, apdu_len, + &rp_data->object_property, &rp_data->array_index); + if (len <= 0) { + /* malformed */ + return; + } + apdu_len -= len; + apdu += len; + if (bacnet_is_opening_tag_number(apdu, apdu_len, 4, &len)) { + application_data_len = bacapp_data_len( + apdu, apdu_len, rp_data->object_property); + /* propertyValue */ + apdu_len -= len; + apdu += len; + if (application_data_len) { + rp_data->application_data_len = application_data_len; + rp_data->application_data = apdu; + apdu_len -= application_data_len; + apdu += application_data_len; + } + if (bacnet_is_closing_tag_number(apdu, apdu_len, 4, &len)) { + apdu_len -= len; + apdu += len; + } else { + /* malformed */ + return; + } + rp_data->error_class = ERROR_CLASS_PROPERTY; + rp_data->error_code = ERROR_CODE_SUCCESS; + if (callback) { + callback(device_id, rp_data); + } + } else if (bacnet_is_opening_tag_number( + apdu, apdu_len, 5, &len)) { + apdu_len -= len; + apdu += len; + /* property-access-error */ + len = bacnet_enumerated_application_decode( + apdu, apdu_len, &error_value); + if (len > 0) { + rp_data->error_class = (BACNET_ERROR_CLASS)error_value; + apdu_len -= len; + apdu += len; + } else { + /* malformed */ + return; + } + len = bacnet_enumerated_application_decode( + apdu, apdu_len, &error_value); + if (len > 0) { + rp_data->error_code = (BACNET_ERROR_CODE)error_value; + apdu_len -= len; + apdu += len; + } else { + /* malformed */ + return; + } + if (bacnet_is_closing_tag_number(apdu, apdu_len, 5, &len)) { + apdu_len -= len; + apdu += len; + } else { + /* malformed */ + return; + } + if (callback) { + callback(device_id, rp_data); + } + } + } + len = rpm_decode_object_end(apdu, apdu_len); + if (len) { + apdu_len -= len; + apdu += len; + } + } + + return; +} #endif diff --git a/src/bacnet/rpm.h b/src/bacnet/rpm.h index 0dabf97c..524d7eff 100644 --- a/src/bacnet/rpm.h +++ b/src/bacnet/rpm.h @@ -31,6 +31,7 @@ #include "bacnet/bacdef.h" #include "bacnet/bacapp.h" #include "bacnet/proplist.h" +#include "bacnet/rp.h" /* * Bundle together commonly used data items for convenience when calling * rpm helper functions. @@ -184,6 +185,12 @@ extern "C" { unsigned apdu_len, BACNET_PROPERTY_ID * object_property, BACNET_ARRAY_INDEX * array_index); + void rpm_ack_object_property_process( + uint8_t *apdu, + unsigned apdu_len, + uint32_t device_id, + BACNET_READ_PROPERTY_DATA *rp_data, + read_property_ack_process callback); #ifdef __cplusplus }