diff --git a/CMakeLists.txt b/CMakeLists.txt index 61a2420d..b88940ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,11 @@ option( "compile the piface app" OFF) +option( + BACNET_BUILD_BACPOLL_APP + "compile the bacpoll app" + ON) + option( BACDL_ETHERNET "compile with ethernet support" @@ -558,16 +563,14 @@ if(BACNET_STACK_BUILD_APPS) target_link_libraries(piface PRIVATE ${PROJECT_NAME}) endif(BACNET_BUILD_PIFACE_APP) - # add_executable( - # ptransfer - # apps/ptransfer/main.c - # apps/ptransfer/h_pt_a.h - # apps/ptransfer/h_pt_a.c - # apps/ptransfer/h_pt.h - # apps/ptransfer/h_pt.c - # apps/ptransfer/s_ptransfer.h - # apps/ptransfer/s_ptransfer.c) - # target_link_libraries(ptransfer PRIVATE ${PROJECT_NAME}) + if(BACNET_BUILD_BACPOLL_APP) + add_executable(bacpoll + apps/server-client/main.c + src/bacnet/basic/client/bac-task.c + src/bacnet/basic/client/bac-data.c + src/bacnet/basic/client/bac-rw.c) + target_link_libraries(bacpoll PRIVATE ${PROJECT_NAME}) + endif(BACNET_BUILD_BACPOLL_APP) if(NOT BACDL_ETHERNET) add_executable(readbdt apps/readbdt/main.c) diff --git a/apps/Makefile b/apps/Makefile index 4dc92fe4..137a4346 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -135,7 +135,8 @@ endif 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 + writepropm uptransfer getevent uevent abort error event ack-alarm \ + server-client ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter @@ -260,6 +261,10 @@ scov: $(BACNET_LIB_TARGET) server: $(BACNET_LIB_TARGET) $(MAKE) -s -b -C $@ +.PHONY: server-client +server-client: $(BACNET_LIB_TARGET) + $(MAKE) -s -b -C $@ + .PHONY: timesync timesync: $(BACNET_LIB_TARGET) $(MAKE) -b -C $@ diff --git a/apps/server-client/Makefile b/apps/server-client/Makefile new file mode 100644 index 00000000..53d3f9ff --- /dev/null +++ b/apps/server-client/Makefile @@ -0,0 +1,45 @@ +#Makefile to build BACnet Application using GCC compiler + +# 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 \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_CLIENT_DIR)/bac-data.c \ + $(BACNET_CLIENT_DIR)/bac-rw.c \ + $(BACNET_CLIENT_DIR)/bac-task.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-client/main.c b/apps/server-client/main.c new file mode 100644 index 00000000..3d2d8eb4 --- /dev/null +++ b/apps/server-client/main.c @@ -0,0 +1,227 @@ +/** + * @file + * @author Steve Karg + * @date 2022 + * @brief Application to acquire data from a target client + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bactext.h" +#include "bacnet/version.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/client/bac-task.h" +#include "bacnet/basic/client/bac-data.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" + +/* print with flush by default */ +#define PRINTF(...) \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr) + +/* current version of the BACnet stack */ +static const char *BACnet_Version = BACNET_VERSION_TEXT; + +static void print_usage(const char *filename) +{ + PRINTF("Usage: %s [device-instance]\n", filename); + PRINTF(" [object-type] [object-instance]\n"); + PRINTF(" [--device][--print-seconds]\n"); + PRINTF(" [--version][--help]\n"); +} + +static void print_help(const char *filename) +{ + PRINTF("Simulate a BACnet server-client device.\n"); + PRINTF("device-instance:\n" + "BACnet Device Object Instance number that you are\n" + "trying to communicate to. This number will be used\n" + "to try and bind with the device using Who-Is and\n" + "I-Am services. For example, if you were reading\n" + "Device Object 123, the device-instance would be 123.\n" + "\nobject-type:\n" + "The object type is object that you are reading. It\n" + "can be defined either as the object-type name string\n" + "as defined in the BACnet specification, or as the\n" + "integer value of the enumeration BACNET_OBJECT_TYPE\n" + "in bacenum.h. For example if you were reading Analog\n" + "Output 2, the object-type would be analog-output or 1.\n" + "\nobject-instance:\n" + "This is the object instance number of the object that\n" + "you are reading. For example, if you were reading\n" + "Analog Output 2, the object-instance would be 2.\n"); + PRINTF("\n" + "Example:\n" + "If you want read the Present-Value of Analog Output 101\n" + "in Device 123, you could send either of the following\n" + "commands:\n" + "%s 123 analog-output 101\n" + "%s 123 1 101\n" + "If you want read the Present-Value of Binary Input 1\n" + "in Device 123, you could send either of the following\n" + "commands:\n" + "%s 123 binary-input 1\n" + "%s 123 3 1\n", + filename, filename, filename, 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; + struct mstimer print_value_timer = { 0 }; + float float_value = 0.0; + bool bool_value = false; + uint32_t unsigned_value = 0; + /* data from the command line */ + unsigned long print_seconds = 10; + uint32_t target_device_object_instance = BACNET_MAX_INSTANCE; + uint32_t target_object_instance = BACNET_MAX_INSTANCE; + BACNET_OBJECT_TYPE target_object_type = OBJECT_ANALOG_INPUT; + + 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) 2022 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], "--device") == 0) { + if (++argi < argc) { + device_id = strtol(argv[argi], NULL, 0); + if (device_id > BACNET_MAX_INSTANCE) { + fprintf(stderr, "device=%u - it must be less than %u\n", + device_id, BACNET_MAX_INSTANCE); + return 1; + } + } + } else if (strcmp(argv[argi], "--print-seconds") == 0) { + if (++argi < argc) { + print_seconds = strtol(argv[argi], NULL, 0); + } + } else { + if (target_args == 0) { + target_device_object_instance = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 1) { + if (bactext_object_type_strtol( + argv[argi], &target_object_type) == false) { + fprintf(stderr, "object-type=%s invalid\n", argv[argi]); + return 1; + } + target_args++; + } else if (target_args == 2) { + target_object_instance = strtol(argv[argi], NULL, 0); + target_args++; + } else { + print_usage(filename); + return 1; + } + } + } + if (target_args < 2) { + print_usage(filename); + return 0; + } + Device_Set_Object_Instance_Number(device_id); + if (target_device_object_instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\n", + target_device_object_instance, BACNET_MAX_INSTANCE); + return 1; + } + PRINTF("BACnet Server-Client Demo\n" + "BACnet Stack Version %s\n" + "BACnet Device ID: %u\n" + "Max APDU: %d\n", + BACnet_Version, Device_Object_Instance_Number(), MAX_APDU); + fflush(stdout); + dlenv_init(); + atexit(datalink_cleanup); + bacnet_task_init(); + bacnet_data_poll_seconds_set(print_seconds); + if (!bacnet_data_object_add(target_device_object_instance, + target_object_type, target_object_instance)) { + return 1; + } + mstimer_set(&print_value_timer, print_seconds * 1000); + /* loop forever */ + for (;;) { + bacnet_task(); + if (mstimer_expired(&print_value_timer)) { + mstimer_reset(&print_value_timer); + switch (target_object_type) { + case OBJECT_ANALOG_INPUT: + case OBJECT_ANALOG_OUTPUT: + case OBJECT_ANALOG_VALUE: + if (bacnet_data_analog_present_value( + target_device_object_instance, target_object_type, + target_object_instance, &float_value)) { + PRINTF("Device %u %s-%u=%f\n", + (unsigned)target_device_object_instance, + bactext_object_type_name(target_object_type), + (unsigned)target_object_instance, float_value); + } + break; + case OBJECT_BINARY_INPUT: + case OBJECT_BINARY_OUTPUT: + case OBJECT_BINARY_VALUE: + if (bacnet_data_binary_present_value( + target_device_object_instance, target_object_type, + target_object_instance, &bool_value)) { + PRINTF("Device %u %s-%u=%s\n", + (unsigned)target_device_object_instance, + bactext_object_type_name(target_object_type), + (unsigned)target_object_instance, + bool_value ? "active" : "inactive"); + } + break; + case OBJECT_MULTI_STATE_INPUT: + case OBJECT_MULTI_STATE_OUTPUT: + case OBJECT_MULTI_STATE_VALUE: + if (bacnet_data_multistate_present_value( + target_device_object_instance, target_object_type, + target_object_instance, &unsigned_value)) { + PRINTF("Device %u %s-%u=%u\n", + (unsigned)target_device_object_instance, + bactext_object_type_name(target_object_type), + (unsigned)target_object_instance, + (unsigned)unsigned_value); + } + break; + default: + return 1; + break; + } + } + } + + return 0; +} diff --git a/src/bacnet/basic/client/bac-data.c b/src/bacnet/basic/client/bac-data.c new file mode 100644 index 00000000..29671e76 --- /dev/null +++ b/src/bacnet/basic/client/bac-data.c @@ -0,0 +1,420 @@ +/** + * @file + * @author Steve Karg + * @date 2013 + * @brief Store properties from other BACnet devices + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/basic/sys/mstimer.h" +/* us */ +#include "bacnet/basic/client/bac-rw.h" +#include "bacnet/basic/client/bac-data.h" + +/* number of objects data stored */ +#ifndef BACNET_DATA_OBJECT_MAX +#define BACNET_DATA_OBJECT_MAX 16 +#endif +/* Polling interval timer */ +static struct mstimer Object_Poll_Timer; +/* property R/W process interval timer */ +static struct mstimer Read_Write_Timer; + +/* variables for remote BACnet Object Data */ +typedef struct bacnet_object_data { + uint32_t Device_ID; + uint16_t Object_Type; + uint32_t Object_ID; + struct bacnet_present_value { + /* application tag data type for writing */ + uint8_t tag; + union { + bool Boolean; + float Real; + uint32_t Unsigned_Int; + int32_t Signed_Int; + uint32_t Enumerated; + } type; + } Present_Value; + bool refresh; +} BACNET_DATA_OBJECT; +static BACNET_DATA_OBJECT Object_Table[BACNET_DATA_OBJECT_MAX]; + +/** + * @brief Find the index of a BACnet object type of a given instance. + * @param device_instance - object-instance number of the device object + * @param object_instance - object-instance number of the object + * @return The index of the object sought, or BACNET_STATUS_ERROR if + * not found. + */ +static int bacnet_data_object_index_find( + uint32_t device_instance, uint16_t object_type, uint32_t object_instance) +{ + BACNET_DATA_OBJECT *object = NULL; + unsigned i = 0; + + for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) { + object = &Object_Table[i]; + if ((object->Device_ID == device_instance) && + (object->Object_Type == object_type) && + (object->Object_ID == object_instance)) { + return i; + } + } + + return BACNET_STATUS_ERROR; +} + +/** + * @brief Find a free index of an BACnet value object type + * @return The index of a free element, or BACNET_STATUS_ERROR if + * no free elements exist + */ +static int bacnet_data_object_index_find_free(void) +{ + BACNET_DATA_OBJECT *object = NULL; + unsigned i = 0; + + for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) { + object = &Object_Table[i]; + if ((object->Device_ID >= BACNET_MAX_INSTANCE) && + (object->Object_Type == MAX_BACNET_OBJECT_TYPE) && + (object->Object_ID >= BACNET_MAX_INSTANCE)) { + return i; + } + } + + return BACNET_STATUS_ERROR; +} + +/** + * @brief Initializes the BACnet object data + */ +static void bacnet_data_object_init(void) +{ + unsigned i = 0; + BACNET_DATA_OBJECT *object = NULL; + + for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) { + object = &Object_Table[i]; + object->Device_ID = BACNET_MAX_INSTANCE; + object->Object_Type = MAX_BACNET_OBJECT_TYPE; + object->Object_ID = BACNET_MAX_INSTANCE; + } +} + +static void bacnet_data_object_store(int index, + BACNET_READ_PROPERTY_DATA *rp_data, + BACNET_APPLICATION_DATA_VALUE *value) +{ + BACNET_DATA_OBJECT *object = NULL; + + assert(rp_data); + assert(value); + if ((index < BACNET_DATA_OBJECT_MAX) && (!value->context_specific)) { + object = &Object_Table[index]; + switch (rp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value->tag == BACNET_APPLICATION_TAG_REAL) { + object->Present_Value.tag = value->tag; + object->Present_Value.type.Real = value->type.Real; + } + if (value->tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + object->Present_Value.tag = value->tag; + object->Present_Value.type.Unsigned_Int = + value->type.Unsigned_Int; + } + if (value->tag == BACNET_APPLICATION_TAG_ENUMERATED) { + object->Present_Value.tag = value->tag; + object->Present_Value.type.Enumerated = + value->type.Enumerated; + } + break; + default: + break; + } + object->refresh = false; + } +} + +/** + * @brief Find the index of an analog value object type of a given instance. + * @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. + */ +void bacnet_data_value_save(uint32_t device_instance, + BACNET_READ_PROPERTY_DATA *rp_data, + BACNET_APPLICATION_DATA_VALUE *value) +{ + int index = 0; + + if (!rp_data) { + return; + } + if (rp_data->error_code != ERROR_CODE_SUCCESS) { + return; + } + if (value) { + switch (rp_data->object_type) { + case OBJECT_ANALOG_INPUT: + case OBJECT_ANALOG_OUTPUT: + case OBJECT_ANALOG_VALUE: + case OBJECT_BINARY_INPUT: + case OBJECT_BINARY_OUTPUT: + case OBJECT_BINARY_VALUE: + case OBJECT_MULTI_STATE_INPUT: + case OBJECT_MULTI_STATE_OUTPUT: + case OBJECT_MULTI_STATE_VALUE: + index = bacnet_data_object_index_find(device_instance, + rp_data->object_type, rp_data->object_instance); + if (index != BACNET_STATUS_ERROR) { + bacnet_data_object_store(index, rp_data, value); + } + break; + case OBJECT_DEVICE: + default: + break; + } + } +} + +/** + * @brief Handles the BACnet Data Analog Value processing + * @param object - BACnet object structure data pointer + */ +static void bacnet_data_object_process(BACNET_DATA_OBJECT *object) +{ + if (object && (object->Device_ID < BACNET_MAX_INSTANCE) && + (object->Object_ID < BACNET_MAX_INSTANCE)) { + bacnet_read_property_queue(object->Device_ID, + (BACNET_OBJECT_TYPE)object->Object_Type, object->Object_ID, + PROP_PRESENT_VALUE, BACNET_ARRAY_ALL); + } +} + +/** + * @brief Adds a BACnet Data remote value point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @return true if added or existing, false if not added or existing + */ +bool bacnet_data_object_add(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + BACNET_DATA_OBJECT *object = NULL; + bool status = false; + int index = 0; + + switch (object_type) { + case OBJECT_ANALOG_INPUT: + case OBJECT_ANALOG_OUTPUT: + case OBJECT_ANALOG_VALUE: + case OBJECT_BINARY_INPUT: + case OBJECT_BINARY_OUTPUT: + case OBJECT_BINARY_VALUE: + case OBJECT_MULTI_STATE_INPUT: + case OBJECT_MULTI_STATE_OUTPUT: + case OBJECT_MULTI_STATE_VALUE: + index = bacnet_data_object_index_find( + device_id, object_type, object_instance); + if (index == BACNET_STATUS_ERROR) { + index = bacnet_data_object_index_find_free(); + if (index != BACNET_STATUS_ERROR) { + object = &Object_Table[index]; + object->Device_ID = device_id; + object->Object_Type = object_type; + object->Object_ID = object_instance; + object->refresh = true; + status = true; + } + } else { + object = &Object_Table[index]; + object->refresh = true; + status = true; + } + break; + case OBJECT_DEVICE: + default: + break; + } + + return status; +} + +/** + * @brief Reads a Property value that has been stored + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance # of the object to be read. + * @param float_value [out] property value stored if available + * @return true if found and value loaded + */ +bool bacnet_data_analog_present_value(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + float *float_value) +{ + bool status = false; + int index = 0; + BACNET_DATA_OBJECT *object = NULL; + + index = + bacnet_data_object_index_find(device_id, object_type, object_instance); + if (index == BACNET_STATUS_ERROR) { + /* add to our object table if not found */ + bacnet_data_object_add(device_id, object_type, object_instance); + } else { + status = true; + if (float_value) { + object = &Object_Table[index]; + *float_value = object->Present_Value.type.Real; + } + } + + return status; +} + +/** + * @brief Reads a Property value that has been stored + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance # of the object to be read. + * @param bool_value [out] property value stored if available + * @return true if found and value loaded + */ +bool bacnet_data_binary_present_value(uint32_t device_id, + uint16_t object_type, + uint32_t object_instance, + bool *bool_value) +{ + bool status = false; + int index = 0; + BACNET_DATA_OBJECT *object = NULL; + + index = + bacnet_data_object_index_find(device_id, object_type, object_instance); + if (index == BACNET_STATUS_ERROR) { + /* add to our object table if not found */ + bacnet_data_object_add( + device_id, (BACNET_OBJECT_TYPE)object_type, object_instance); + } else { + status = true; + if (bool_value) { + object = &Object_Table[index]; + if (object->Present_Value.type.Enumerated == BINARY_INACTIVE) { + *bool_value = false; + } else { + *bool_value = true; + } + } + } + + return status; +} + +/** + * @brief Reads a Property value that has been stored + * @param device_id - ID of the destination device + * @param object_type - BACnet object type + * @param object_instance - Instance # of the object to be read. + * @param bool_value [out] property value stored if available + * @return true if found and value loaded + */ +bool bacnet_data_multistate_present_value(uint32_t device_id, + uint16_t object_type, + uint32_t object_instance, + uint32_t *unsigned_value) +{ + bool status = false; + int index = 0; + BACNET_DATA_OBJECT *object = NULL; + + index = + bacnet_data_object_index_find(device_id, object_type, object_instance); + if (index == BACNET_STATUS_ERROR) { + /* add to our object table if not found */ + bacnet_data_object_add( + device_id, (BACNET_OBJECT_TYPE)object_type, object_instance); + } else { + status = true; + if (unsigned_value) { + object = &Object_Table[index]; + *unsigned_value = object->Present_Value.type.Unsigned_Int; + } + } + + return status; +} + +/** + * @brief Handles the BACnet Data repetitive task + */ +void bacnet_data_task(void) +{ + static unsigned object_index = 0; + BACNET_DATA_OBJECT *object = NULL; + unsigned i = 0; + + if (mstimer_expired(&Object_Poll_Timer)) { + mstimer_reset(&Object_Poll_Timer); + for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) { + object = &Object_Table[i]; + object->refresh = true; + } + } + if (mstimer_expired(&Read_Write_Timer)) { + mstimer_reset(&Read_Write_Timer); + bacnet_read_write_task(); + } + if (bacnet_read_write_idle()) { + object = &Object_Table[object_index]; + if (object->refresh) { + object->refresh = false; + bacnet_data_object_process(object); + } + object_index++; + if (object_index >= BACNET_DATA_OBJECT_MAX) { + object_index = 0; + } + } +} + +/** + * @brief Set the BACnet Data Poll seconds + * @param seconds - number of seconds between polling intervals + */ +void bacnet_data_poll_seconds_set(unsigned int seconds) +{ + mstimer_set(&Object_Poll_Timer, seconds * 1000); +} + +/** + * @brief Get the BACnet Data Poll seconds + * @return number of seconds between polling intervals + */ +unsigned int bacnet_data_poll_seconds(void) +{ + return mstimer_interval(&Object_Poll_Timer) * 1000; +} + +/** + * @brief Initializes the ReadProperty module + */ +void bacnet_data_init(void) +{ + bacnet_data_object_init(); + bacnet_read_write_init(); + /* start the cyclic poll timer */ + mstimer_set(&Object_Poll_Timer, 1 * 60 * 1000); + mstimer_set(&Read_Write_Timer, 10); + bacnet_read_write_value_callback_set(bacnet_data_value_save); +} diff --git a/src/bacnet/basic/client/bac-data.h b/src/bacnet/basic/client/bac-data.h new file mode 100644 index 00000000..12ff8c60 --- /dev/null +++ b/src/bacnet/basic/client/bac-data.h @@ -0,0 +1,64 @@ +/** + * @file + * @author Steve Karg + * @date 2013 + * + * SPDX-License-Identifier: MIT + */ +#ifndef BAC_DATA_H +#define BAC_DATA_H + +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/rp.h" +#include "bacnet/bacnet_stack_exports.h" + +struct bacnet_status_flags_t { + bool in_alarm : 1; + bool fault : 1; + bool overridden : 1; + bool out_of_service : 1; +}; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void bacnet_data_init(void); +BACNET_STACK_EXPORT +void bacnet_data_task(void); +BACNET_STACK_EXPORT +void bacnet_data_poll_seconds_set(unsigned int seconds); +BACNET_STACK_EXPORT +unsigned int bacnet_data_poll_seconds(void); +BACNET_STACK_EXPORT +void bacnet_data_value_save(uint32_t device_instance, + BACNET_READ_PROPERTY_DATA *rp_data, + BACNET_APPLICATION_DATA_VALUE *value); +BACNET_STACK_EXPORT +bool bacnet_data_object_add(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); +BACNET_STACK_EXPORT +bool bacnet_data_analog_present_value(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + float *float_value); +BACNET_STACK_EXPORT +bool bacnet_data_multistate_present_value(uint32_t device_id, + uint16_t object_type, + uint32_t object_instance, + uint32_t *unsigned_value); +BACNET_STACK_EXPORT +bool bacnet_data_binary_present_value(uint32_t device_id, + uint16_t object_type, + uint32_t object_instance, + bool *bool_value); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/client/bac-rw.c b/src/bacnet/basic/client/bac-rw.c new file mode 100644 index 00000000..b21f8979 --- /dev/null +++ b/src/bacnet/basic/client/bac-rw.c @@ -0,0 +1,908 @@ +/** + * @file + * @author Steve Karg + * @date 2013 + * @brief Read properties from other BACnet devices, and store their values + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include "bacnet/abort.h" +#include "bacnet/apdu.h" +#include "bacnet/iam.h" +#include "bacnet/reject.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/sys/ringbuf.h" +#include "bacnet/basic/tsm/tsm.h" +/* me */ +#include "bacnet/basic/client/bac-rw.h" + +/* timer for address cache */ +static struct mstimer Cache_Timer; +#define CACHE_CYCLE_SECONDS 60 +/* timeout timer for read-write task */ +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; + +/* states for client task */ +typedef enum { + BACNET_CLIENT_IDLE, + BACNET_CLIENT_BIND, + BACNET_CLIENT_BINDING, + BACNET_CLIENT_SEND, + BACNET_CLIENT_WAITING, + BACNET_CLIENT_FINISHED +} BACNET_CLIENT_STATE; +/* data queue */ +typedef struct target_data_t { + bool write_property; + uint32_t device_id; + uint32_t object_instance; + BACNET_OBJECT_TYPE object_type; + BACNET_PROPERTY_ID object_property; + int32_t array_index; + uint8_t priority; + /* application tag data type for writing */ + uint8_t tag; + union { + /* only supporting limited values for writing */ + bool Boolean; + float Real; + uint32_t Enumerated; + uint32_t Unsigned_Int; + int32_t Signed_Int; + } type; +} TARGET_DATA; +#define TARGET_DATA_QUEUE_SIZE (sizeof(struct target_data_t)) +/* count must be a power of 2 for ringbuf library */ +#ifndef TARGET_DATA_QUEUE_COUNT +#define TARGET_DATA_QUEUE_COUNT 8 +#endif +static TARGET_DATA Target_Data_Buffer[TARGET_DATA_QUEUE_COUNT]; +static RING_BUFFER Target_Data_Queue; +/* local storage - keeps it off the c-stack */ +static BACNET_APPLICATION_DATA_VALUE Target_Decoded_Property_Value; +/* the invoke id is needed to filter incoming messages */ +static uint8_t Request_Invoke_ID; +static BACNET_ADDRESS Target_Address; +static uint32_t Target_Device_ID; +static uint16_t Target_Vendor_ID; +static bool Error_Detected = false; +static BACNET_ERROR_CLASS Error_Class; +static BACNET_ERROR_CODE Error_Code; +static BACNET_CLIENT_STATE RW_State = BACNET_CLIENT_IDLE; + +/** + * @brief Handler for an Error PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param error_class [in] the error class + * @param error_code [in] the error code + */ +static void MyErrorHandler(BACNET_ADDRESS *src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code) +{ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + Error_Detected = true; + Error_Class = error_class; + Error_Code = error_code; + } +} + +/** + * @brief Handler for an Abort PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param abort_reason [in] the reason for the message abort + * @param server + */ +static void MyAbortHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + (void)server; + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + Error_Detected = true; + Error_Class = ERROR_CLASS_SERVICES; + Error_Code = abort_convert_to_error_code(abort_reason); + } +} + +/** + * @brief Handler for a Reject PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param reject_reason [in] the reason for the rejection + */ +static void MyRejectHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t reject_reason) +{ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + Error_Detected = true; + Error_Class = ERROR_CLASS_SERVICES; + Error_Code = reject_convert_to_error_code(reject_reason); + } +} + +/** + * @brief Handler for I-Am responses + * @param service_request [in] The received message to be handled. + * @param service_len [in] Length of the service_request message. + * @param src [in] The BACNET_ADDRESS of the message's source. + */ +static void My_I_Am_Bind( + uint8_t *service_request, uint16_t service_len, BACNET_ADDRESS *src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + bool found = false; + bool bind = false; + + (void)service_len; + len = iam_decode_service_request( + service_request, &device_id, &max_apdu, &segmentation, &vendor_id); + if (len > 0) { + found = address_bind_request(device_id, NULL, NULL); + if (!found) { + if (Target_Vendor_ID != 0) { + if (Target_Vendor_ID == vendor_id) { + /* limit binding to specific vendor ID */ + bind = true; + } + } else { + bind = true; + } + if (bind) { + address_add_binding(device_id, max_apdu, src); + } + } + } + + return; +} + +/** Handler for a Simple ACK PDU. + * + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + */ +static void MyWritePropertySimpleAckHandler( + BACNET_ADDRESS *src, uint8_t invoke_id) +{ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + /* nothing to do */ + } +} + +/** Handler for a ReadProperty ACK. + * Saves the data from a matching read-property request + * + * @param service_request [in] The contents of the service request. + * @param service_len [in] The length of the service_request. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information + * decoded from the APDU header of this message. + */ +static void My_Read_Property_Ack_Handler(uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) +{ + 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)) { + len = rp_ack_decode_service_request( + service_request, service_len, &rp_data); + if (len < 0) { + /* unable to decode value */ + Error_Detected = true; + 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; + } + } +} + +/** Handler for a ReadPropertyMultiple ACK. + * Saves the data from a matching read-property-multiple request + * + * @param service_request [in] The contents of the service request. + * @param service_len [in] The length of the service_request. + * @param src [in] BACNET_ADDRESS of the source of the message + * @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, + 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; + uint32_t device_id = 0; + + 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); + } + } + } +} + +/** + * @brief Sends a ReadPropertyMultiple service request + * @param device_id [in] The contents of the service request. + * @param object_type [in] The contents of the service request. + * @param object_instance [in] The contents of the service request. + * @return invoke_id of request + */ +static uint8_t Send_RPM_All_Request(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + BACNET_READ_ACCESS_DATA read_access_data = { 0 }; + BACNET_PROPERTY_REFERENCE property_list = { 0 }; + uint8_t pdu[MAX_PDU] = { 0 }; + + /* configure the property list */ + property_list.error.error_class = ERROR_CLASS_DEVICE; + property_list.error.error_code = ERROR_CODE_OTHER; + property_list.value = NULL; + property_list.propertyArrayIndex = BACNET_ARRAY_ALL; + property_list.propertyIdentifier = PROP_ALL; + property_list.next = NULL; + /* configure the read access data */ + read_access_data.listOfProperties = &property_list; + read_access_data.object_instance = object_instance; + read_access_data.object_type = object_type; + read_access_data.next = NULL; + + return Send_Read_Property_Multiple_Request( + pdu, sizeof(pdu), device_id, &read_access_data); +} + +/** + * @brief Handles the ReadProperty process + * @param service_request [in] The contents of the service request. + * @return true if the process is finished + */ +static bool bacnet_read_write_process(TARGET_DATA *target) +{ + bool found = false; + unsigned max_apdu = 0; + uint8_t application_data[16] = { 0 }; + int application_data_len = 0; + bool valid_tag = false; + + switch (RW_State) { + case BACNET_CLIENT_IDLE: + mstimer_set(&Read_Write_Timer, apdu_timeout()); + if (target->device_id < BACNET_MAX_INSTANCE) { + Error_Detected = false; + RW_State = BACNET_CLIENT_BIND; + } else { + RW_State = BACNET_CLIENT_FINISHED; + } + break; + case BACNET_CLIENT_BIND: + /* exclude our device - in case our ID changed */ + address_own_device_id_set(Device_Object_Instance_Number()); + /* try to bind with the device */ + found = address_bind_request( + target->device_id, &max_apdu, &Target_Address); + if (found) { + Target_Device_ID = target->device_id; + RW_State = BACNET_CLIENT_SEND; + } else { + Send_WhoIs(target->device_id, target->device_id); + RW_State = BACNET_CLIENT_BINDING; + } + break; + case BACNET_CLIENT_BINDING: + found = address_bind_request( + target->device_id, &max_apdu, &Target_Address); + if (found) { + Target_Device_ID = target->device_id; + mstimer_set(&Read_Write_Timer, apdu_timeout()); + RW_State = BACNET_CLIENT_SEND; + } else if (mstimer_expired(&Read_Write_Timer)) { + /* unable to bind within APDU timeout */ + Error_Detected = true; + Error_Class = ERROR_CLASS_SERVICES; + Error_Code = ERROR_CODE_TIMEOUT; + RW_State = BACNET_CLIENT_FINISHED; + } + break; + case BACNET_CLIENT_SEND: + if (target->write_property) { + switch (target->tag) { + case BACNET_APPLICATION_TAG_NULL: + application_data_len = + encode_application_null(&application_data[0]); + valid_tag = true; + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + application_data_len = encode_application_boolean( + &application_data[0], target->type.Boolean); + valid_tag = true; + break; + case BACNET_APPLICATION_TAG_REAL: + application_data_len = encode_application_real( + &application_data[0], target->type.Real); + valid_tag = true; + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + application_data_len = encode_application_unsigned( + &application_data[0], target->type.Unsigned_Int); + valid_tag = true; + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + application_data_len = encode_application_signed( + &application_data[0], target->type.Signed_Int); + valid_tag = true; + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + application_data_len = encode_application_enumerated( + &application_data[0], target->type.Enumerated); + valid_tag = true; + break; + default: + break; + } + if (valid_tag) { + Request_Invoke_ID = Send_Write_Property_Request_Data( + target->device_id, target->object_type, + target->object_instance, target->object_property, + &application_data[0], application_data_len, + target->priority, target->array_index); + } + } else { + if (target->object_property == PROP_ALL) { + Request_Invoke_ID = Send_RPM_All_Request(target->device_id, + target->object_type, target->object_instance); + } else { + Request_Invoke_ID = + Send_Read_Property_Request(target->device_id, + target->object_type, target->object_instance, + target->object_property, target->array_index); + } + } + if (Request_Invoke_ID == 0) { + if (mstimer_expired(&Read_Write_Timer)) { + /* TSM Timeout - no invokeIDs available */ + Error_Detected = true; + Error_Class = ERROR_CLASS_SERVICES; + Error_Code = ERROR_CODE_TIMEOUT; + RW_State = BACNET_CLIENT_FINISHED; + } + } else { + RW_State = BACNET_CLIENT_WAITING; + } + break; + case BACNET_CLIENT_WAITING: + if (tsm_invoke_id_free(Request_Invoke_ID)) { + Error_Detected = false; + RW_State = BACNET_CLIENT_FINISHED; + } else if (tsm_invoke_id_failed(Request_Invoke_ID)) { + Error_Detected = true; + Error_Class = ERROR_CLASS_SERVICES; + Error_Code = ERROR_CODE_ABORT_TSM_TIMEOUT; + RW_State = BACNET_CLIENT_FINISHED; + tsm_free_invoke_id(Request_Invoke_ID); + } else if (Error_Detected) { + RW_State = BACNET_CLIENT_FINISHED; + } + break; + case BACNET_CLIENT_FINISHED: + RW_State = BACNET_CLIENT_IDLE; + break; + default: + break; + } + + return (RW_State == BACNET_CLIENT_FINISHED); +} + +/** + * @brief Sets the callback for when a read-property returns data + * + * @param callback - function for callback + */ +void bacnet_read_write_value_callback_set( + bacnet_read_write_value_callback_t callback) +{ + bacnet_read_write_value_callback = callback; +} + +/** + * @brief Handles the ReadProperty repetitive task + */ +void bacnet_read_write_task(void) +{ + TARGET_DATA *target; + bool status = false; + BACNET_READ_PROPERTY_DATA rp_data; + + if (!Ringbuf_Empty(&Target_Data_Queue)) { + target = (TARGET_DATA *)Ringbuf_Peek(&Target_Data_Queue); + status = bacnet_read_write_process(target); + if (status) { + if (Error_Detected) { + if (bacnet_read_write_value_callback) { + rp_data.error_class = Error_Class; + rp_data.error_code = Error_Code; + rp_data.object_type = target->object_type; + rp_data.object_instance = target->object_instance; + rp_data.object_property = target->object_property; + rp_data.array_index = target->array_index; + bacnet_read_write_value_callback( + target->device_id, &rp_data, NULL); + } + } + Ringbuf_Pop(&Target_Data_Queue, NULL); + } + } + if (mstimer_expired(&Cache_Timer)) { + mstimer_reset(&Cache_Timer); + address_cache_timer(CACHE_CYCLE_SECONDS); + } +} + +/** + * @brief Adds a Read Property request remote data point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_read_property_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target; + + target.write_property = false; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Adds a WriteProperty request to a remote data point - REAL + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param value - property value of type REAL (float) + * @param priority - BACnet priority for writing 1..16, or 0 if not set + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_write_property_real_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + float value, + uint8_t priority, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target = { 0 }; + + target.write_property = true; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.tag = BACNET_APPLICATION_TAG_REAL; + target.type.Real = value; + target.priority = priority; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Adds a Write Property request to a remote data point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param value - property value of type NULL + * @param priority - BACnet priority for writing 1..16, or 0 if not set + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_write_property_null_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + uint8_t priority, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target = { 0 }; + + target.write_property = true; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.tag = BACNET_APPLICATION_TAG_NULL; + target.priority = priority; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Adds a Write Property request to a remote data point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param value - property value of type ENUMERATED + * @param priority - BACnet priority for writing 1..16, or 0 if not set + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_write_property_enumerated_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + unsigned int value, + uint8_t priority, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target; + + target.write_property = true; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.tag = BACNET_APPLICATION_TAG_ENUMERATED; + target.type.Enumerated = value; + target.priority = priority; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Adds a Write Property request to a remote data point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param value - property value of type UNSIGNED INT + * @param priority - BACnet priority for writing 1..16, or 0 if not set + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_write_property_unsigned_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + unsigned int value, + uint8_t priority, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target; + + target.write_property = true; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + target.type.Unsigned_Int = value; + target.priority = priority; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Adds a Write Property request to a remote data point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param value - property value of type SIGNED INT + * @param priority - BACnet priority for writing 1..16, or 0 if not set + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_write_property_signed_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + signed int value, + uint8_t priority, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target; + + target.write_property = true; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.tag = BACNET_APPLICATION_TAG_SIGNED_INT; + target.type.Signed_Int = value; + target.priority = priority; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Adds a Write Property request to a remote data point + * @param device_id - ID of the destination device + * @param object_type - Type of the object whose property is to be read. + * @param object_instance - Instance # of the object to be read. + * @param object_property - Property to be read, but not ALL, REQUIRED, or + * OPTIONAL. + * @param value - property value of type SIGNED INT + * @param priority - BACnet priority for writing 1..16, or 0 if not set + * @param array_index [in] Optional: if the Property is an array, + * - 0 for the array size + * - 1 to n for individual array members + * - BACNET_ARRAY_ALL (~0) for the full array to be read. + * @return true if added, false if not added + */ +bool bacnet_write_property_boolean_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + bool value, + uint8_t priority, + uint32_t array_index) +{ + bool status = false; + TARGET_DATA target; + + target.write_property = true; + target.device_id = device_id; + target.object_type = object_type; + target.object_instance = object_instance; + target.object_property = object_property; + target.tag = BACNET_APPLICATION_TAG_BOOLEAN; + target.type.Boolean = value; + target.priority = priority; + target.array_index = array_index; + status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target); + + return status; +} + +/** + * @brief Determines if the BACnet ReadProperty queue is empty + * @return true if the parameter queue is empty, and thus, idle + */ +bool bacnet_read_write_idle(void) +{ + return Ringbuf_Empty(&Target_Data_Queue); +} + +/** + * @brief Determines if the BACnet ReadProperty queue is full + * @return true if the parameter queue is full, and thus, busy + */ +bool bacnet_read_write_busy(void) +{ + return Ringbuf_Full(&Target_Data_Queue); +} + +/** + * @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_read_write_vendor_id_filter_set(uint16_t vendor_id) +{ + Target_Vendor_ID = vendor_id; +} + +/** + * @brief Initializes the ReadProperty module + */ +void bacnet_read_write_init(void) +{ + Ringbuf_Init(&Target_Data_Queue, (uint8_t *)&Target_Data_Buffer, + TARGET_DATA_QUEUE_SIZE, TARGET_DATA_QUEUE_COUNT); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, My_I_Am_Bind); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler( + SERVICE_CONFIRMED_READ_PROPERTY, My_Read_Property_Ack_Handler); + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, + My_Read_Property_Multiple_Ack_Handler); + /* handle the Simple ACK coming back */ + apdu_set_confirmed_simple_ack_handler( + SERVICE_CONFIRMED_WRITE_PROPERTY, MyWritePropertySimpleAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); + apdu_set_error_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); + /* configure the address cache */ + address_init(); + /* start the cyclic 1 second timer for Address Cache */ + mstimer_set(&Cache_Timer, CACHE_CYCLE_SECONDS * 1000); +} diff --git a/src/bacnet/basic/client/bac-rw.h b/src/bacnet/basic/client/bac-rw.h new file mode 100644 index 00000000..d1be99e8 --- /dev/null +++ b/src/bacnet/basic/client/bac-rw.h @@ -0,0 +1,105 @@ +/** + * @file + * @author Steve Karg + * @date 2013 + * + * SPDX-License-Identifier: MIT + */ +#ifndef BAC_RW_H +#define BAC_RW_H + +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/rp.h" +#include "bacnet/bacnet_stack_exports.h" + +/** + * Save the requested ReadProperty data to a data store + * + * @param device_instance [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. + */ +typedef void (*bacnet_read_write_value_callback_t)(uint32_t device_instance, + BACNET_READ_PROPERTY_DATA *rp_data, + BACNET_APPLICATION_DATA_VALUE *value); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void bacnet_read_write_init(void); +BACNET_STACK_EXPORT +void bacnet_read_write_task(void); +BACNET_STACK_EXPORT +bool bacnet_read_write_idle(void); +BACNET_STACK_EXPORT +bool bacnet_read_write_busy(void); +BACNET_STACK_EXPORT +bool bacnet_read_property_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + uint32_t array_index); +BACNET_STACK_EXPORT +bool bacnet_write_property_real_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + float value, + uint8_t priority, + uint32_t array_index); +BACNET_STACK_EXPORT +bool bacnet_write_property_null_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + uint8_t priority, + uint32_t array_index); +BACNET_STACK_EXPORT +bool bacnet_write_property_enumerated_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + unsigned int value, + uint8_t priority, + uint32_t array_index); +BACNET_STACK_EXPORT +bool bacnet_write_property_unsigned_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + unsigned int value, + uint8_t priority, + uint32_t array_index); +BACNET_STACK_EXPORT +bool bacnet_write_property_signed_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + signed int value, + uint8_t priority, + uint32_t array_index); +BACNET_STACK_EXPORT +bool bacnet_write_property_boolean_queue(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + bool value, + uint8_t priority, + uint32_t array_index); +BACNET_STACK_EXPORT +void bacnet_read_write_value_callback_set( + bacnet_read_write_value_callback_t callback); +BACNET_STACK_EXPORT +void bacnet_read_write_vendor_id_filter_set(uint16_t vendor_id); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/client/bac-task.c b/src/bacnet/basic/client/bac-task.c new file mode 100644 index 00000000..f10f1423 --- /dev/null +++ b/src/bacnet/basic/client/bac-task.c @@ -0,0 +1,108 @@ +/** + * @file + * @author Steve Karg + * @date 2013 + * @brief High level BACnet Task handling + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +/* core library */ +#include "bacnet/apdu.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/config.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/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" +#include "bacnet/basic/object/device.h" +/* us */ +#include "bacnet/basic/client/bac-rw.h" +#include "bacnet/basic/client/bac-data.h" +#include "bacnet/basic/client/bac-task.h" + +/** 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; + +/** + * @brief Non-blocking task for running BACnet service + */ +void bacnet_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)); + } + bacnet_data_task(); +} + +/** + * @brief Initialize the handlers we will utilize. + */ +void bacnet_task_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); + bacnet_data_init(); + mstimer_set(&BACnet_Task_Timer, 1000); + mstimer_set(&BACnet_TSM_Timer, 50); +} diff --git a/src/bacnet/basic/client/bac-task.h b/src/bacnet/basic/client/bac-task.h new file mode 100644 index 00000000..3821d15b --- /dev/null +++ b/src/bacnet/basic/client/bac-task.h @@ -0,0 +1,27 @@ +/** + * @file + * @author Steve Karg + * @date 2013 + * @brief High level BACnet task handling + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_TASK_H +#define BACNET_TASK_H + +#include +#include "bacnet/bacnet_stack_exports.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void bacnet_task_init(void); +BACNET_STACK_EXPORT +void bacnet_task(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif