diff --git a/CMakeLists.txt b/CMakeLists.txt index c33d2f9c..161bf187 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -718,7 +718,7 @@ if(BACNET_STACK_BUILD_APPS) add_executable(router-ipv6 apps/router-ipv6/main.c) target_link_libraries(router-ipv6 PRIVATE ${PROJECT_NAME}) endif() - + add_executable(scov apps/scov/main.c) target_link_libraries(scov PRIVATE ${PROJECT_NAME}) @@ -731,6 +731,9 @@ if(BACNET_STACK_BUILD_APPS) add_executable(ucov apps/ucov/main.c) target_link_libraries(ucov PRIVATE ${PROJECT_NAME}) + add_executable(event apps/event/main.c) + target_link_libraries(event PRIVATE ${PROJECT_NAME}) + add_executable(uevent apps/uevent/main.c) target_link_libraries(uevent PRIVATE ${PROJECT_NAME}) diff --git a/Makefile b/Makefile index ebb38ad4..242ca7c4 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ epics: error: $(MAKE) -s -C apps $@ +.PHONY: event +event: + $(MAKE) -s -C apps $@ + .PHONY: iam iam: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 75cee372..9cc72732 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -185,7 +185,7 @@ BACNET_BASIC_SRC ?= \ SUBDIRS = readprop writeprop readfile writefile reinit server dcc \ whohas whois iam ucov scov timesync epics readpropm readrange \ - writepropm uptransfer getevent uevent abort error + writepropm uptransfer getevent uevent abort error event ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter readbdt readfdt @@ -241,6 +241,10 @@ epics: error: $(MAKE) -b -C $@ +.PHONY: event +event: + $(MAKE) -b -C $@ + .PHONY: getevent getevent: $(MAKE) -b -C $@ diff --git a/apps/event/Makefile b/apps/event/Makefile new file mode 100644 index 00000000..469b9381 --- /dev/null +++ b/apps/event/Makefile @@ -0,0 +1,54 @@ +#Makefile to build BACnet Application with GNU Make + +# tools - only if you need them. +# Most platforms have this already defined +# CC = gcc + +# Executable file name +TARGET = bacevent +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c +BACNET_BASIC_SRC += \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_apdu.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_iam.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_noserv.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_rp.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_rr_a.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_whois.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/s_iam.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/s_cevent.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/s_whois.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +SRCS = $(SRC) $(BACNET_SRC) $(BACNET_BASIC_SRC) $(BACNET_PORT_SRC) + +OBJS += ${SRCS:.c=.o} + +.PHONY: all +all: Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +.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 + +.PHONY: include +include: .depend + diff --git a/apps/event/main.c b/apps/event/main.c new file mode 100644 index 00000000..36c1b2ab --- /dev/null +++ b/apps/event/main.c @@ -0,0 +1,634 @@ +/************************************************************************** + * + * Copyright (C) 2021 Steve Karg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + *********************************************************************/ + +/* command line tool that sends a BACnet service */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bacnet/bactext.h" +#include "bacnet/iam.h" +#include "bacnet/cov.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/basic/object/device.h" +#include "bacport.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/event.h" +#include "bacnet/whois.h" +#include "bacnet/version.h" +/* some demo stuff needed */ +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/dlenv.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; +/* global variables used in this file */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +/* needed to filter incoming messages */ +static uint8_t Request_Invoke_ID = 0; +static BACNET_ADDRESS Target_Address; +/* needed for return value of main application */ +static bool Error_Detected = false; + +/** 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)) { + printf("BACnet Error: %s: %s\n", + bactext_error_class_name((int)error_class), + bactext_error_code_name((int)error_code)); + Error_Detected = true; + } +} + +/** 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)) { + printf( + "BACnet Abort: %s\n", bactext_abort_reason_name((int)abort_reason)); + Error_Detected = true; + } +} + +/** 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)) { + printf("BACnet Reject: %s\n", + bactext_reject_reason_name((int)reject_reason)); + Error_Detected = true; + } +} + +/** 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)) { + printf("\nEventNotification Acknowledged!\n"); + } +} + +/** + * Initializes the BACnet objects and services supported + */ +static void Init_Service_Handlers(void) +{ + Device_Init(NULL); + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + /* 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); + /* we must implement read property - it's required! */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); + /* handle the 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_WRITE_PROPERTY, MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static void print_usage(char *filename) +{ + printf("Usage: %s device-id process-id initiating-device-id\n" + " event-object-type event-object-instance\n" + " sequence-number notification-class priority message-text\n" + " notify-type ack-required from-state to-state event-type\n" + " [change-of-bitstring reference-bit-string status-flags]\n" + " [change-of-state new-state-tag new-state-value status-flags]\n", + filename); + printf(" [--dnet][--dadr][--mac]\n"); + printf(" [--version][--help]\n"); +} + +static void print_help(char *filename) +{ + printf("Send BACnet ConfirmedEventNotification message to a device.\n"); + printf( + "device-id:\n" + "BACnet Device Object Instance number that you are trying to\n" + "communicate to. This number will be used to try and bind with\n" + "the device using Who-Is and I-Am services. For example, if you were\n" + "notifying Device Object 123, the device-instance would be 123.\n" + "\n" + "process-id:\n" + "Process Identifier in the receiving device for which the\n" + "notification is intended.\n" + "\n" + "initiating-device-id: the BACnet Device Object Instance number\n" + "that initiated the ConfirmedEventNotification service request.\n" + "\n" + "event-object-type:\n" + "The object type is defined either as the object-type name string\n" + "as defined in the BACnet specification, or as the integer value.\n" + "\n" + "event-object-instance:\n" + "The object instance number of the event object.\n" + "\n" + "sequence-number:\n" + "The sequence number of the event.\n" + "\n" + "notification-class:\n" + "The notification-class of the event.\n" + "\n" + "priority:\n" + "The priority of the event.\n" + "\n" + "message-text:\n" + "The message text of the event.\n" + "\n" + "notify-type:\n" + "The notify type of the event.\n" + "\n" + "ack-required:\n" + "The ack-required of the event (0=FALSE,1=TRUE).\n" + "\n" + "from-state:\n" + "The from-state of the event.\n" + "\n" + "to-state:\n" + "The to-state of the event.\n" + "\n" + "event-type\n" + "The event-type of the event.\n" + "\n"); + printf("--mac A\n" + "Optional BACnet mac address." + "Valid ranges are from 00 to FF (hex) for MS/TP or ARCNET,\n" + "or an IP string with optional port number like 10.1.2.3:47808\n" + "or an Ethernet MAC in hex like 00:21:70:7e:32:bb\n" + "\n" + "--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" + "\n" + "--dadr A\n" + "Optional BACnet mac address on the destination BACnet network " + "number.\n" + "Valid ranges are from 00 to FF (hex) for MS/TP or ARCNET,\n" + "or an IP string with optional port number like 10.1.2.3:47808\n" + "or an Ethernet MAC in hex like 00:21:70:7e:32:bb\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + BACNET_EVENT_NOTIFICATION_DATA event_data = { 0 }; + BACNET_BIT_STRING *pBitString; + BACNET_CHARACTER_STRING bcstring; + BACNET_PROPERTY_STATE_TYPE tag = BOOLEAN_VALUE; + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + unsigned timeout = 100; /* milliseconds */ + uint16_t pdu_len = 0; + unsigned max_apdu = 0; + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + bool found = false; + long dnet = -1; + BACNET_MAC_ADDRESS mac = { 0 }; + BACNET_MAC_ADDRESS adr = { 0 }; + BACNET_ADDRESS dest = { 0 }; + bool specific_address = false; + int argi = 0; + unsigned int target_args = 0; + char *filename = NULL; + unsigned found_index = 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) 2016 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], "--mac") == 0) { + if (++argi < argc) { + if (address_mac_from_ascii(&mac, argv[argi])) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--dnet") == 0) { + if (++argi < argc) { + dnet = strtol(argv[argi], NULL, 0); + if ((dnet >= 0) && (dnet <= BACNET_BROADCAST_NETWORK)) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--dadr") == 0) { + if (++argi < argc) { + if (address_mac_from_ascii(&adr, argv[argi])) { + specific_address = true; + } + } + } else { + if (target_args == 0) { + /* device-id */ + Target_Device_Object_Instance = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 1) { + /* process-id */ + event_data.processIdentifier = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 2) { + /* initiating-device-id */ + event_data.initiatingObjectIdentifier.type = OBJECT_DEVICE; + event_data.initiatingObjectIdentifier.instance = + strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 3) { + /* event-object-type */ + event_data.eventObjectIdentifier.type = + strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 4) { + /* event-object-instance */ + event_data.eventObjectIdentifier.instance = + strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 5) { + /* sequence-number */ + event_data.timeStamp.tag = TIME_STAMP_SEQUENCE; + event_data.timeStamp.value.sequenceNum = + strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 6) { + /* notification-class */ + event_data.notificationClass = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 7) { + /* priority */ + event_data.priority = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 8) { + /* message-text */ + characterstring_init_ansi(&bcstring, argv[argi]); + event_data.messageText = &bcstring; + target_args++; + } else if (target_args == 9) { + /* notify-type */ + if (bactext_notify_type_index(argv[argi], &found_index)) { + event_data.notifyType = found_index; + } else { + event_data.notifyType = strtol(argv[argi], NULL, 0); + } + target_args++; + } else if (target_args == 10) { + /* ack-required */ + event_data.ackRequired = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 11) { + /* from-state */ + if (bactext_event_state_index(argv[argi], &found_index)) { + event_data.fromState = found_index; + } else { + event_data.fromState = strtol(argv[argi], NULL, 0); + } + target_args++; + } else if (target_args == 12) { + /* to-state */ + if (bactext_event_state_index(argv[argi], &found_index)) { + event_data.toState = found_index; + } else { + event_data.toState = strtol(argv[argi], NULL, 0); + } + target_args++; + } else if (target_args == 13) { + /* event-type - see BACNET_EVENT_TYPE */ + if (bactext_event_type_index(argv[argi], &found_index)) { + event_data.eventType = found_index; + } else { + event_data.eventType = strtol(argv[argi], NULL, 0); + } + target_args++; + } else { + if (event_data.eventType == EVENT_CHANGE_OF_BITSTRING) { + if (target_args == 14) { + pBitString = + &event_data.notificationParams.changeOfBitstring + .referencedBitString; + bitstring_init_ascii(pBitString, argv[argi]); + target_args++; + } else if (target_args == 15) { + pBitString = &event_data.notificationParams + .changeOfBitstring.statusFlags; + bitstring_init_ascii(pBitString, argv[argi]); + target_args++; + } else { + print_usage(filename); + return 1; + } + } else if (event_data.eventType == EVENT_CHANGE_OF_STATE) { + if (target_args == 14) { + tag = strtol(argv[argi], NULL, 0); + event_data.notificationParams.changeOfState.newState + .tag = tag; + target_args++; + } else if (target_args == 15) { + if (tag == BOOLEAN_VALUE) { + event_data.notificationParams.changeOfState.newState + .state.booleanValue = + strtol(argv[argi], NULL, 0); + } else if (tag == BINARY_VALUE) { + event_data.notificationParams.changeOfState.newState + .state.binaryValue = + strtol(argv[argi], NULL, 0); + } else if (tag == EVENT_TYPE) { + event_data.notificationParams.changeOfState.newState + .state.eventType = strtol(argv[argi], NULL, 0); + } else if (tag == POLARITY) { + event_data.notificationParams.changeOfState.newState + .state.polarity = strtol(argv[argi], NULL, 0); + } else if (tag == PROGRAM_CHANGE) { + event_data.notificationParams.changeOfState.newState + .state.programChange = + strtol(argv[argi], NULL, 0); + } else if (tag == PROGRAM_STATE) { + event_data.notificationParams.changeOfState.newState + .state.programState = + strtol(argv[argi], NULL, 0); + } else if (tag == REASON_FOR_HALT) { + event_data.notificationParams.changeOfState.newState + .state.programError = + strtol(argv[argi], NULL, 0); + } else if (tag == RELIABILITY) { + event_data.notificationParams.changeOfState.newState + .state.reliability = + strtol(argv[argi], NULL, 0); + } else if (tag == STATE) { + event_data.notificationParams.changeOfState.newState + .state.state = strtol(argv[argi], NULL, 0); + } else if (tag == SYSTEM_STATUS) { + event_data.notificationParams.changeOfState.newState + .state.systemStatus = + strtol(argv[argi], NULL, 0); + } else if (tag == UNITS) { + event_data.notificationParams.changeOfState.newState + .state.units = strtol(argv[argi], NULL, 0); + } else if (tag == UNSIGNED_VALUE) { + event_data.notificationParams.changeOfState.newState + .state.unsignedValue = + strtol(argv[argi], NULL, 0); + } else if (tag == LIFE_SAFETY_MODE) { + event_data.notificationParams.changeOfState.newState + .state.lifeSafetyMode = + strtol(argv[argi], NULL, 0); + } else if (tag == LIFE_SAFETY_STATE) { + event_data.notificationParams.changeOfState.newState + .state.lifeSafetyState = + strtol(argv[argi], NULL, 0); + } else { + printf("Invalid Change-Of-State Tag\n"); + return 1; + } + target_args++; + } else if (target_args == 16) { + pBitString = &event_data.notificationParams + .changeOfBitstring.statusFlags; + bitstring_init_ascii(pBitString, argv[argi]); + target_args++; + } else { + print_usage(filename); + return 1; + } + } else if (event_data.eventType == EVENT_CHANGE_OF_VALUE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_COMMAND_FAILURE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_FLOATING_LIMIT) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_OUT_OF_RANGE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_CHANGE_OF_LIFE_SAFETY) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_EXTENDED) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_BUFFER_READY) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_UNSIGNED_RANGE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_ACCESS_EVENT) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_DOUBLE_OUT_OF_RANGE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_SIGNED_OUT_OF_RANGE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_UNSIGNED_OUT_OF_RANGE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_CHANGE_OF_CHARACTERSTRING) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_CHANGE_OF_STATUS_FLAGS) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_CHANGE_OF_RELIABILITY) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_NONE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_CHANGE_OF_DISCRETE_VALUE) { + /* FIXME: add event type parameters */ + } else if (event_data.eventType == EVENT_CHANGE_OF_TIMER) { + /* FIXME: add event type parameters */ + } else if ((event_data.eventType >= EVENT_PROPRIETARY_MIN) && + (event_data.eventType <= EVENT_PROPRIETARY_MAX)) { + /* Enumerated values 64-65535 may + be used by others subject to + the procedures and constraints + described in Clause 23. */ + } else { + print_usage(filename); + return 1; + } + } + } + } + if (target_args < 14) { + print_usage(filename); + return 0; + } + address_init(); + if (specific_address) { + if (adr.len && mac.len) { + memcpy(&dest.mac[0], &mac.adr[0], mac.len); + dest.mac_len = mac.len; + memcpy(&dest.adr[0], &adr.adr[0], adr.len); + dest.len = adr.len; + if ((dnet >= 0) && (dnet <= BACNET_BROADCAST_NETWORK)) { + dest.net = dnet; + } else { + dest.net = BACNET_BROADCAST_NETWORK; + } + } else if (mac.len) { + memcpy(&dest.mac[0], &mac.adr[0], mac.len); + dest.mac_len = mac.len; + dest.len = 0; + if ((dnet >= 0) && (dnet <= BACNET_BROADCAST_NETWORK)) { + dest.net = dnet; + } else { + dest.net = 0; + } + } else { + if ((dnet >= 0) && (dnet <= BACNET_BROADCAST_NETWORK)) { + dest.net = dnet; + } else { + dest.net = BACNET_BROADCAST_NETWORK; + } + dest.mac_len = 0; + dest.len = 0; + } + address_add(Target_Device_Object_Instance, MAX_APDU, &dest); + printf("Added Device %u to address cache\n", + Target_Device_Object_Instance); + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + dlenv_init(); + atexit(datalink_cleanup); + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (apdu_timeout() / 1000) * apdu_retries(); + /* try to bind with the device */ + found = address_bind_request( + Target_Device_Object_Instance, &max_apdu, &Target_Address); + if (!found) { + Send_WhoIs( + Target_Device_Object_Instance, Target_Device_Object_Instance); + } + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* at least one second has passed */ + if (current_seconds != last_seconds) { + tsm_timer_milliseconds( + (uint16_t)((current_seconds - last_seconds) * 1000)); + } + if (Error_Detected) + break; + /* wait until the device is bound, or timeout and quit */ + if (!found) { + found = address_bind_request( + Target_Device_Object_Instance, &max_apdu, &Target_Address); + } + if (found) { + if (Request_Invoke_ID == 0) { + Request_Invoke_ID = + Send_CEvent_Notify_Address( + Handler_Transmit_Buffer, + sizeof(Handler_Transmit_Buffer), + &event_data, + &Target_Address); + } else if (tsm_invoke_id_free(Request_Invoke_ID)) { + break; + } else if (tsm_invoke_id_failed(Request_Invoke_ID)) { + fprintf(stderr, "\rError: TSM Timeout!\n"); + tsm_free_invoke_id(Request_Invoke_ID); + Error_Detected = true; + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + printf("\rError: APDU Timeout!\n"); + Error_Detected = true; + break; + } + } + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + if (Error_Detected) { + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index 634594e1..09f00015 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -1228,6 +1228,25 @@ bool bactext_days_of_week_index(const char *search_name, unsigned *found_index) bacnet_days_of_week_names, search_name, found_index); } +INDTEXT_DATA bacnet_notify_type_names[] = { + { NOTIFY_ALARM, "alarm" }, + { NOTIFY_EVENT, "event" }, + { NOTIFY_ACK_NOTIFICATION, "ack-notification" }, + { 0, NULL } }; + +const char *bactext_notify_type_name(unsigned index) +{ + return indtext_by_index_default( + bacnet_notify_type_names, index, ASHRAE_Reserved_String); +} + +bool bactext_notify_type_index( + const char *search_name, unsigned *found_index) +{ + return indtext_by_istring( + bacnet_notify_type_names, search_name, found_index); +} + INDTEXT_DATA bacnet_event_transition_names[] = { { TRANSITION_TO_OFFNORMAL, "offnormal" }, { TRANSITION_TO_NORMAL, "normal" }, { TRANSITION_TO_FAULT, "fault" }, @@ -1257,6 +1276,50 @@ const char *bactext_event_state_name(unsigned index) bacnet_event_state_names, index, ASHRAE_Reserved_String); } +bool bactext_event_state_index( + const char *search_name, unsigned *found_index) +{ + return indtext_by_istring( + bacnet_event_state_names, search_name, found_index); +} + +INDTEXT_DATA bacnet_event_type_names[] = { + { EVENT_CHANGE_OF_BITSTRING, "change-of-bitstring" }, + { EVENT_CHANGE_OF_STATE, "change-of-state" }, + { EVENT_CHANGE_OF_VALUE, "change-of-value" }, + { EVENT_COMMAND_FAILURE, "command-failure" }, + { EVENT_FLOATING_LIMIT, "floating-limit" }, + { EVENT_CHANGE_OF_LIFE_SAFETY, "change-of-life-safety" }, + { EVENT_EXTENDED, "extended" }, + { EVENT_BUFFER_READY, "buffer-ready" }, + { EVENT_UNSIGNED_RANGE, "unsigned-range" }, + { EVENT_ACCESS_EVENT, "access-event" }, + { EVENT_DOUBLE_OUT_OF_RANGE, "double-out-of-range" }, + { EVENT_SIGNED_OUT_OF_RANGE, "signed-out-of-range" }, + { EVENT_UNSIGNED_OUT_OF_RANGE, "unsigned-out-of-range" }, + { EVENT_CHANGE_OF_CHARACTERSTRING, "change-of-characterstring" }, + { EVENT_CHANGE_OF_STATUS_FLAGS, "change-of-status-flags" }, + { EVENT_CHANGE_OF_RELIABILITY, "change-of-reliability" }, + { EVENT_NONE, "none" }, + { EVENT_CHANGE_OF_DISCRETE_VALUE, "change-of-discrete-value" }, + { EVENT_CHANGE_OF_TIMER, "change-of-timer" }, + { 0, NULL } }; + +const char *bactext_event_type_name(unsigned index) +{ + return indtext_by_index_split_default(bacnet_event_type_names, index, + EVENT_PROPRIETARY_MIN, ASHRAE_Reserved_String, + Vendor_Proprietary_String); +} + +bool bactext_event_type_index( + const char *search_name, unsigned *found_index) +{ + return indtext_by_istring( + bacnet_event_type_names, search_name, found_index); +} + + INDTEXT_DATA bacnet_binary_present_value_names[] = { { BINARY_INACTIVE, "inactive" }, { BINARY_ACTIVE, "active" }, { 0, NULL } }; diff --git a/src/bacnet/bactext.h b/src/bacnet/bactext.h index fd1991fb..22537bc5 100644 --- a/src/bacnet/bactext.h +++ b/src/bacnet/bactext.h @@ -119,9 +119,22 @@ extern "C" { const char *bactext_day_of_week_name( unsigned index); BACNET_STACK_EXPORT + const char *bactext_notify_type_name(unsigned index); + BACNET_STACK_EXPORT + bool bactext_notify_type_index( + const char *search_name, unsigned *found_index); + BACNET_STACK_EXPORT const char *bactext_event_state_name( unsigned index); BACNET_STACK_EXPORT + bool bactext_event_state_index( + const char *search_name, unsigned *found_index); + BACNET_STACK_EXPORT + const char *bactext_event_type_name(unsigned index); + BACNET_STACK_EXPORT + bool bactext_event_type_index( + const char *search_name, unsigned *found_index); + BACNET_STACK_EXPORT const char *bactext_binary_present_value_name( unsigned index); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/service/s_cevent.c b/src/bacnet/basic/service/s_cevent.c index f5f4fe73..d030a359 100644 --- a/src/bacnet/basic/service/s_cevent.c +++ b/src/bacnet/basic/service/s_cevent.c @@ -40,13 +40,15 @@ /** Sends an Confirmed Alarm/Event Notification. * @ingroup EVNOTFCN * - * @param device_id [in] ID of the destination device + * @param pdu [in] the PDU buffer used for sending the message + * @param pdu_size [in] Size of the PDU buffer * @param data [in] The information about the Event to be sent. + * @param dest [in] BACNET_ADDRESS of the destination device * @return invoke id of outgoing message, or 0 if communication is disabled, * or no tsm slot is available. */ -uint8_t Send_CEvent_Notify( - uint32_t device_id, BACNET_EVENT_NOTIFICATION_DATA *data) +uint8_t Send_CEvent_Notify_Address(uint8_t *pdu, uint16_t pdu_size, + BACNET_EVENT_NOTIFICATION_DATA *data, BACNET_ADDRESS *dest) { int len = 0; int pdu_len = 0; @@ -54,45 +56,37 @@ uint8_t Send_CEvent_Notify( int bytes_sent = 0; #endif BACNET_NPDU_DATA npdu_data; - BACNET_ADDRESS dest; BACNET_ADDRESS my_address; - unsigned max_apdu = 0; - bool status = false; uint8_t invoke_id = 0; if (!dcc_communication_enabled()) { return 0; } - - /* is the device bound? */ - status = address_get_by_device(device_id, &max_apdu, &dest); - /* is there a tsm available? */ - if (status) { - invoke_id = tsm_next_free_invokeID(); + if (!dest) { + return 0; } + /* is there a tsm available? */ + invoke_id = tsm_next_free_invokeID(); if (invoke_id) { /* encode the NPDU portion of the packet */ datalink_get_my_address(&my_address); npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); - pdu_len = npdu_encode_pdu( - &Handler_Transmit_Buffer[0], &dest, &my_address, &npdu_data); + pdu_len = npdu_encode_pdu(pdu, dest, &my_address, &npdu_data); /* encode the APDU portion of the packet */ - len = cevent_notify_encode_apdu( - &Handler_Transmit_Buffer[pdu_len], invoke_id, data); + len = cevent_notify_encode_apdu(&pdu[pdu_len], invoke_id, data); pdu_len += len; /* will it fit in the sender? note: if there is a bottleneck router in between us and the destination, we won't know unless we have a way to check for that and update the max_apdu in the address binding table. */ - if ((unsigned)pdu_len < max_apdu) { - tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, - &npdu_data, &Handler_Transmit_Buffer[0], (uint16_t)pdu_len); + if ((uint16_t)pdu_len < pdu_size) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, dest, + &npdu_data, pdu, (uint16_t)pdu_len); #if PRINT_ENABLED bytes_sent = #endif - datalink_send_pdu( - &dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + datalink_send_pdu(dest, &npdu_data, pdu, pdu_len); #if PRINT_ENABLED if (bytes_sent <= 0) { fprintf(stderr, @@ -113,3 +107,33 @@ uint8_t Send_CEvent_Notify( return invoke_id; } + +/** Sends an Confirmed Alarm/Event Notification. + * @ingroup EVNOTFCN + * + * @param device_id [in] ID of the destination device + * @param data [in] The information about the Event to be sent. + * @return invoke id of outgoing message, or 0 if communication is disabled, + * or no tsm slot is available. + */ +uint8_t Send_CEvent_Notify( + uint32_t device_id, BACNET_EVENT_NOTIFICATION_DATA *data) +{ + BACNET_ADDRESS dest = { 0 }; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + if (status) { + if (sizeof(Handler_Transmit_Buffer) < max_apdu) { + max_apdu = sizeof(Handler_Transmit_Buffer); + } + invoke_id = Send_CEvent_Notify_Address( + Handler_Transmit_Buffer, max_apdu, + data, &dest); + } + + return invoke_id; +} diff --git a/src/bacnet/basic/service/s_cevent.h b/src/bacnet/basic/service/s_cevent.h index ab4fd8db..6650f28b 100644 --- a/src/bacnet/basic/service/s_cevent.h +++ b/src/bacnet/basic/service/s_cevent.h @@ -46,6 +46,9 @@ extern "C" { BACNET_STACK_EXPORT uint8_t Send_CEvent_Notify(uint32_t device_id, BACNET_EVENT_NOTIFICATION_DATA* data); +BACNET_STACK_EXPORT +uint8_t Send_CEvent_Notify_Address(uint8_t *pdu, uint16_t pdu_size, + BACNET_EVENT_NOTIFICATION_DATA *data, BACNET_ADDRESS *dest); #ifdef __cplusplus }