From a7bc145c43535a91729b238940da40eb23140a67 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 22 Apr 2021 08:33:04 -0500 Subject: [PATCH] Feature/alarm ack application (#164) * Added alarm-ack application * fix error and simple ack handling for event notification * Added ack-alarm application * Update CMake for ack-alarm * update example objects for alarm and events * Allow repeated ack-alarm for same transition * add event state API. Fix COV event state. * add event state API to AV. Fix COV event state. * Use event time for ack notification * Enable notifications for all transitions by default. For testing. * Use unconfirmed device notification to 4194303 for testing. * initialize local vars Co-authored-by: Steve Karg --- CMakeLists.txt | 3 + Makefile | 4 + apps/Makefile | 6 +- apps/ack-alarm/Makefile | 56 ++++ apps/ack-alarm/main.c | 443 +++++++++++++++++++++++++ apps/event/main.c | 5 +- src/bacnet/bactext.c | 7 + src/bacnet/bactext.h | 3 + src/bacnet/basic/object/ai.c | 164 ++++++--- src/bacnet/basic/object/ai.h | 5 + src/bacnet/basic/object/av.c | 50 ++- src/bacnet/basic/object/av.h | 8 + src/bacnet/basic/object/nc.c | 41 ++- src/bacnet/basic/service/s_ack_alarm.c | 97 ++++-- src/bacnet/basic/service/s_ack_alarm.h | 4 + src/bacnet/datetime.c | 71 ++++ src/bacnet/datetime.h | 9 + src/bacnet/timestamp.c | 84 +++++ src/bacnet/timestamp.h | 5 + 19 files changed, 966 insertions(+), 99 deletions(-) create mode 100644 apps/ack-alarm/Makefile create mode 100644 apps/ack-alarm/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 161bf187..2508ac42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -621,6 +621,9 @@ if(BACNET_STACK_BUILD_APPS) add_executable(abort apps/abort/main.c) target_link_libraries(abort PRIVATE ${PROJECT_NAME}) + add_executable(ack-alarm apps/ack-alarm/main.c) + target_link_libraries(ack-alarm PRIVATE ${PROJECT_NAME}) + add_executable(dcc apps/dcc/main.c) target_link_libraries(dcc PRIVATE ${PROJECT_NAME}) diff --git a/Makefile b/Makefile index 242ca7c4..3edbafe9 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,10 @@ apps: abort: $(MAKE) -s -C apps $@ +.PHONY: ack-alarm +ack-alarm: + $(MAKE) -s -C apps $@ + .PHONY: dcc dcc: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 9cc72732..ac4ab0e9 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 event + writepropm uptransfer getevent uevent abort error event ack-alarm ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter readbdt readfdt @@ -229,6 +229,10 @@ mstpcrc: abort: $(MAKE) -b -C $@ +.PHONY: ack-alarm +ack-alarm: + $(MAKE) -b -C $@ + .PHONY: dcc dcc: $(MAKE) -b -C $@ diff --git a/apps/ack-alarm/Makefile b/apps/ack-alarm/Makefile new file mode 100644 index 00000000..846dbe55 --- /dev/null +++ b/apps/ack-alarm/Makefile @@ -0,0 +1,56 @@ +#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 = bacackalarm +# 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_ack_alarm.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/ack-alarm/main.c b/apps/ack-alarm/main.c new file mode 100644 index 00000000..6b7c8533 --- /dev/null +++ b/apps/ack-alarm/main.c @@ -0,0 +1,443 @@ +/************************************************************************** + * + * 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/alarm_ack.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("\nAcknowledgeAlarm 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_ACKNOWLEDGE_ALARM, MyWritePropertySimpleAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler( + SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static void print_usage(char *filename) +{ + printf("Usage: %s device-id process-id\n" + " event-object-type event-object-instance event-state-acked\n" + " event-time-stamp ack-source-name ack-time-stamp\n", + filename); + printf(" [--dnet][--dadr][--mac]\n"); + printf(" [--version][--help]\n"); +} + +static void print_help(char *filename) +{ + printf("Send BACnet AcknowledgedAlarm, 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" + "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" + "event-state-acked:\n" + "The event-state that for this alarm acknowledge.\n" + "\n" + "event-time-stamp:\n" + "The time-stamp of the event.\n" + "\n" + "ack-source-name\n" + "The source name of the alarm acknowledge.\n" + "\n" + "ack-time-stamp\n" + "The time-stamp of the alarm acknowledge.\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_ALARM_ACK_DATA data = { 0 }; + 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; + + 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) 2021 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 */ + data.ackProcessIdentifier = strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 2) { + /* event-object-type */ + if (bactext_object_type_strtol(argv[argi], + &data.eventObjectIdentifier.type)) { + target_args++; + } else { + fprintf(stderr, "event-object-type=%s invalid\n", + argv[argi]); + return 1; + } + } else if (target_args == 3) { + /* event-object-instance */ + data.eventObjectIdentifier.instance = + strtol(argv[argi], NULL, 0); + target_args++; + } else if (target_args == 4) { + /* event-state-acked */ + if (bactext_event_state_strtol(argv[argi], + &data.eventStateAcked)) { + target_args++; + } else { + fprintf(stderr, "event-state=%s invalid\n", + argv[argi]); + return 1; + } + } else if (target_args == 5) { + /* event-time-stamp */ + bacapp_timestamp_init_ascii(&data.eventTimeStamp, argv[argi]); + target_args++; + } else if (target_args == 6) { + /* ack-source */ + characterstring_init_ansi(&data.ackSource, argv[argi]); + target_args++; + } else if (target_args == 7) { + /* ack-time-stamp */ + bacapp_timestamp_init_ascii(&data.ackTimeStamp, argv[argi]); + target_args++; + } else { + print_usage(filename); + return 1; + } + } + } + if (target_args < 7) { + 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_Alarm_Acknowledgement_Address( + Handler_Transmit_Buffer, + sizeof(Handler_Transmit_Buffer), + &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/apps/event/main.c b/apps/event/main.c index 36c1b2ab..203258ef 100644 --- a/apps/event/main.c +++ b/apps/event/main.c @@ -151,9 +151,10 @@ static void Init_Service_Handlers(void) SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); /* handle the ack coming back */ apdu_set_confirmed_simple_ack_handler( - SERVICE_CONFIRMED_WRITE_PROPERTY, MyWritePropertySimpleAckHandler); + SERVICE_CONFIRMED_EVENT_NOTIFICATION, MyWritePropertySimpleAckHandler); /* handle any errors coming back */ - apdu_set_error_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, MyErrorHandler); + apdu_set_error_handler( + SERVICE_CONFIRMED_EVENT_NOTIFICATION, MyErrorHandler); apdu_set_abort_handler(MyAbortHandler); apdu_set_reject_handler(MyRejectHandler); } diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index 09f00015..409f8bfe 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -1283,12 +1283,19 @@ bool bactext_event_state_index( bacnet_event_state_names, search_name, found_index); } +bool bactext_event_state_strtol(const char *search_name, unsigned *found_index) +{ + return bactext_strtol_index( + 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_OUT_OF_RANGE, "out-of-range" }, { EVENT_CHANGE_OF_LIFE_SAFETY, "change-of-life-safety" }, { EVENT_EXTENDED, "extended" }, { EVENT_BUFFER_READY, "buffer-ready" }, diff --git a/src/bacnet/bactext.h b/src/bacnet/bactext.h index 22537bc5..f33882df 100644 --- a/src/bacnet/bactext.h +++ b/src/bacnet/bactext.h @@ -130,6 +130,9 @@ extern "C" { bool bactext_event_state_index( const char *search_name, unsigned *found_index); BACNET_STACK_EXPORT + bool bactext_event_state_strtol( + 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( diff --git a/src/bacnet/basic/object/ai.c b/src/bacnet/basic/object/ai.c index 0c7d114d..9fa9ac57 100644 --- a/src/bacnet/basic/object/ai.c +++ b/src/bacnet/basic/object/ai.c @@ -41,6 +41,13 @@ #include "bacnet/timestamp.h" #include "bacnet/basic/object/ai.h" +#if PRINT_ENABLED +#include +#define PRINTF(...) fprintf(stderr,__VA_ARGS__) +#else +#define PRINTF(...) +#endif + #ifndef MAX_ANALOG_INPUTS #define MAX_ANALOG_INPUTS 4 #endif @@ -222,6 +229,28 @@ bool Analog_Input_Object_Name( return status; } +/** + * For a given object instance-number, gets the event-state property value + * + * @param object_instance - object-instance number of the object + * + * @return event-state property value + */ +unsigned Analog_Input_Event_State(uint32_t object_instance) +{ + unsigned index = 0; + unsigned state = EVENT_STATE_NORMAL; + +#if defined(INTRINSIC_REPORTING) + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + state = AI_Descr[index].Event_State; + } +#endif + + return state; +} + bool Analog_Input_Change_Of_Value(uint32_t object_instance) { unsigned index = 0; @@ -275,8 +304,13 @@ bool Analog_Input_Encode_Value_List( value_list->value.context_specific = false; value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING; bitstring_init(&value_list->value.type.Bit_String); - bitstring_set_bit( - &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, false); + if (Analog_Input_Event_State(object_instance) == EVENT_STATE_NORMAL) { + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_IN_ALARM, false); + } else { + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_IN_ALARM, true); + } bitstring_set_bit( &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, false); bitstring_set_bit( @@ -408,12 +442,9 @@ int Analog_Input_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) case PROP_STATUS_FLAGS: bitstring_init(&bit_string); -#if defined(INTRINSIC_REPORTING) bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, - CurrentAI->Event_State ? true : false); -#else - bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); -#endif + Analog_Input_Event_State(rpdata->object_instance) != + EVENT_STATE_NORMAL); bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, @@ -423,13 +454,9 @@ int Analog_Input_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) break; case PROP_EVENT_STATE: -#if defined(INTRINSIC_REPORTING) apdu_len = - encode_application_enumerated(&apdu[0], CurrentAI->Event_State); -#else - apdu_len = - encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); -#endif + encode_application_enumerated(&apdu[0], + Analog_Input_Event_State(rpdata->object_instance)); break; case PROP_RELIABILITY: @@ -810,37 +837,33 @@ bool Analog_Input_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) void Analog_Input_Intrinsic_Reporting(uint32_t object_instance) { #if defined(INTRINSIC_REPORTING) - BACNET_EVENT_NOTIFICATION_DATA event_data; - BACNET_CHARACTER_STRING msgText; - ANALOG_INPUT_DESCR *CurrentAI; - unsigned int object_index; + BACNET_EVENT_NOTIFICATION_DATA event_data = { 0 }; + BACNET_CHARACTER_STRING msgText = { 0 }; + ANALOG_INPUT_DESCR *CurrentAI = NULL; + unsigned int object_index = 0; uint8_t FromState = 0; - uint8_t ToState; + uint8_t ToState = 0; float ExceededLimit = 0.0f; float PresentVal = 0.0f; bool SendNotify = false; object_index = Analog_Input_Instance_To_Index(object_instance); - if (object_index < MAX_ANALOG_INPUTS) + if (object_index < MAX_ANALOG_INPUTS) { CurrentAI = &AI_Descr[object_index]; - else + } else { return; - + } /* check limits */ - if (!CurrentAI->Limit_Enable) + if (!CurrentAI->Limit_Enable) { return; /* limits are not configured */ + } if (CurrentAI->Ack_notify_data.bSendAckNotify) { /* clean bSendAckNotify flag */ CurrentAI->Ack_notify_data.bSendAckNotify = false; /* copy toState */ ToState = CurrentAI->Ack_notify_data.EventState; - -#if PRINT_ENABLED - fprintf(stderr, "Send Acknotification for (%s,%d).\n", - bactext_object_type_name(OBJECT_ANALOG_INPUT), object_instance); -#endif /* PRINT_ENABLED */ - + PRINTF("Analog-Input[%d]: Send AckNotification.\n", object_instance); characterstring_init_ansi(&msgText, "AckNotification"); /* Notify Type */ @@ -983,14 +1006,10 @@ void Analog_Input_Intrinsic_Reporting(uint32_t object_instance) ExceededLimit = 0; break; } /* switch (ToState) */ - -#if PRINT_ENABLED - fprintf(stderr, "Event_State for (%s,%d) goes from %s to %s.\n", - bactext_object_type_name(OBJECT_ANALOG_INPUT), object_instance, + PRINTF("Analog-Input[%d]: Event_State goes from %s to %s.\n", + object_instance, bactext_event_state_name(FromState), bactext_event_state_name(ToState)); -#endif /* PRINT_ENABLED */ - /* Notify Type */ event_data.notifyType = CurrentAI->Notify_Type; @@ -1006,25 +1025,49 @@ void Analog_Input_Intrinsic_Reporting(uint32_t object_instance) /* Time Stamp */ event_data.timeStamp.tag = TIME_STAMP_DATETIME; - Device_getCurrentDateTime(&event_data.timeStamp.value.dateTime); - if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) { + Device_getCurrentDateTime(&event_data.timeStamp.value.dateTime); /* fill Event_Time_Stamps */ switch (ToState) { case EVENT_STATE_HIGH_LIMIT: case EVENT_STATE_LOW_LIMIT: - CurrentAI->Event_Time_Stamps[TRANSITION_TO_OFFNORMAL] = - event_data.timeStamp.value.dateTime; + datetime_copy( + &CurrentAI->Event_Time_Stamps[TRANSITION_TO_OFFNORMAL], + &event_data.timeStamp.value.dateTime); break; - case EVENT_STATE_FAULT: - CurrentAI->Event_Time_Stamps[TRANSITION_TO_FAULT] = - event_data.timeStamp.value.dateTime; + datetime_copy( + &CurrentAI->Event_Time_Stamps[TRANSITION_TO_FAULT], + &event_data.timeStamp.value.dateTime); break; - case EVENT_STATE_NORMAL: - CurrentAI->Event_Time_Stamps[TRANSITION_TO_NORMAL] = - event_data.timeStamp.value.dateTime; + datetime_copy( + &CurrentAI->Event_Time_Stamps[TRANSITION_TO_NORMAL], + &event_data.timeStamp.value.dateTime); + break; + default: + break; + } + } else { + /* fill event_data timeStamp */ + switch (ToState) { + case EVENT_STATE_HIGH_LIMIT: + case EVENT_STATE_LOW_LIMIT: + datetime_copy( + &event_data.timeStamp.value.dateTime, + &CurrentAI->Event_Time_Stamps[TRANSITION_TO_OFFNORMAL]); + break; + case EVENT_STATE_FAULT: + datetime_copy( + &event_data.timeStamp.value.dateTime, + &CurrentAI->Event_Time_Stamps[TRANSITION_TO_FAULT]); + break; + case EVENT_STATE_NORMAL: + datetime_copy( + &event_data.timeStamp.value.dateTime, + &CurrentAI->Event_Time_Stamps[TRANSITION_TO_NORMAL]); + break; + default: break; } } @@ -1058,7 +1101,8 @@ void Analog_Input_Intrinsic_Reporting(uint32_t object_instance) &event_data.notificationParams.outOfRange.statusFlags); bitstring_set_bit( &event_data.notificationParams.outOfRange.statusFlags, - STATUS_FLAG_IN_ALARM, CurrentAI->Event_State ? true : false); + STATUS_FLAG_IN_ALARM, + CurrentAI->Event_State != EVENT_STATE_NORMAL); bitstring_set_bit( &event_data.notificationParams.outOfRange.statusFlags, STATUS_FLAG_FAULT, false); @@ -1077,11 +1121,23 @@ void Analog_Input_Intrinsic_Reporting(uint32_t object_instance) } /* add data from notification class */ + PRINTF("Analog-Input[%d]: Notification Class[%d]-%s " + "%u/%u/%u-%u:%u:%u.%u!\n", + object_instance, event_data.notificationClass, + bactext_event_type_name(event_data.eventType), + (unsigned)event_data.timeStamp.value.dateTime.date.year, + (unsigned)event_data.timeStamp.value.dateTime.date.month, + (unsigned)event_data.timeStamp.value.dateTime.date.day, + (unsigned)event_data.timeStamp.value.dateTime.time.hour, + (unsigned)event_data.timeStamp.value.dateTime.time.min, + (unsigned)event_data.timeStamp.value.dateTime.time.sec, + (unsigned)event_data.timeStamp.value.dateTime.time.hundredths); Notification_Class_common_reporting_function(&event_data); /* Ack required */ if ((event_data.notifyType != NOTIFY_ACK_NOTIFICATION) && (event_data.ackRequired == true)) { + PRINTF("Analog-Input[%d]: Ack Required!\n", object_instance); switch (event_data.toState) { case EVENT_STATE_OFFNORMAL: case EVENT_STATE_HIGH_LIMIT: @@ -1221,10 +1277,12 @@ int Analog_Input_Alarm_Ack( *error_code = ERROR_CODE_INVALID_TIME_STAMP; return -1; } - - /* FIXME: Send ack notification */ + /* Send ack notification */ CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL].bIsAcked = true; + } else if (alarmack_data->eventStateAcked == + CurrentAI->Event_State) { + /* Send ack notification */ } else { *error_code = ERROR_CODE_INVALID_EVENT_STATE; return -1; @@ -1245,10 +1303,12 @@ int Analog_Input_Alarm_Ack( *error_code = ERROR_CODE_INVALID_TIME_STAMP; return -1; } - - /* FIXME: Send ack notification */ + /* Send ack notification */ CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked = true; + } else if (alarmack_data->eventStateAcked == + CurrentAI->Event_State) { + /* Send ack notification */ } else { *error_code = ERROR_CODE_INVALID_EVENT_STATE; return -1; @@ -1269,10 +1329,12 @@ int Analog_Input_Alarm_Ack( *error_code = ERROR_CODE_INVALID_TIME_STAMP; return -1; } - - /* FIXME: Send ack notification */ + /* Send ack notification */ CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked = true; + } else if (alarmack_data->eventStateAcked == + CurrentAI->Event_State) { + /* Send ack notification */ } else { *error_code = ERROR_CODE_INVALID_EVENT_STATE; return -1; diff --git a/src/bacnet/basic/object/ai.h b/src/bacnet/basic/object/ai.h index 91867785..f585355d 100644 --- a/src/bacnet/basic/object/ai.h +++ b/src/bacnet/basic/object/ai.h @@ -140,6 +140,11 @@ extern "C" { uint32_t object_instance, bool oos_flag); + BACNET_STACK_EXPORT + unsigned Analog_Input_Event_State(uint32_t object_instance); + BACNET_STACK_EXPORT + bool Analog_Input_Event_State_Set(uint32_t object_instance, unsigned state); + BACNET_STACK_EXPORT bool Analog_Input_Change_Of_Value( uint32_t instance); diff --git a/src/bacnet/basic/object/av.c b/src/bacnet/basic/object/av.c index 4c84476d..45790956 100644 --- a/src/bacnet/basic/object/av.c +++ b/src/bacnet/basic/object/av.c @@ -290,6 +290,28 @@ bool Analog_Value_Object_Name( return status; } +/** + * For a given object instance-number, gets the event-state property value + * + * @param object_instance - object-instance number of the object + * + * @return event-state property value + */ +unsigned Analog_Value_Event_State(uint32_t object_instance) +{ + unsigned index = 0; + unsigned state = EVENT_STATE_NORMAL; + +#if defined(INTRINSIC_REPORTING) + index = Analog_Value_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_VALUES) { + state = AV_Descr[index].Event_State; + } +#endif + + return state; +} + /** * For a given object instance-number, determines if the COV flag * has been triggered. @@ -356,8 +378,13 @@ bool Analog_Value_Encode_Value_List( value_list->value.context_specific = false; value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING; bitstring_init(&value_list->value.type.Bit_String); - bitstring_set_bit( - &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, false); + if (Analog_Value_Event_State(object_instance) == EVENT_STATE_NORMAL) { + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_IN_ALARM, false); + } else { + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_IN_ALARM, true); + } bitstring_set_bit( &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, false); bitstring_set_bit( @@ -497,12 +524,9 @@ int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) case PROP_STATUS_FLAGS: bitstring_init(&bit_string); -#if defined(INTRINSIC_REPORTING) bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, - CurrentAV->Event_State ? true : false); -#else - bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); -#endif + Analog_Value_Event_State(rpdata->object_instance) != + EVENT_STATE_NORMAL); bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, @@ -1155,7 +1179,8 @@ void Analog_Value_Intrinsic_Reporting(uint32_t object_instance) &event_data.notificationParams.outOfRange.statusFlags); bitstring_set_bit( &event_data.notificationParams.outOfRange.statusFlags, - STATUS_FLAG_IN_ALARM, CurrentAV->Event_State ? true : false); + STATUS_FLAG_IN_ALARM, + CurrentAV->Event_State != EVENT_STATE_NORMAL); bitstring_set_bit( &event_data.notificationParams.outOfRange.statusFlags, STATUS_FLAG_FAULT, false); @@ -1322,6 +1347,9 @@ int Analog_Value_Alarm_Ack( /* Clean transitions flag. */ CurrentAV->Acked_Transitions[TRANSITION_TO_OFFNORMAL].bIsAcked = true; + } else if (alarmack_data->eventStateAcked == + CurrentAV->Event_State) { + /* Send ack notification */ } else { *error_code = ERROR_CODE_INVALID_EVENT_STATE; return -1; @@ -1346,6 +1374,9 @@ int Analog_Value_Alarm_Ack( /* Clean transitions flag. */ CurrentAV->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked = true; + } else if (alarmack_data->eventStateAcked == + CurrentAV->Event_State) { + /* Send ack notification */ } else { *error_code = ERROR_CODE_INVALID_EVENT_STATE; return -1; @@ -1370,6 +1401,9 @@ int Analog_Value_Alarm_Ack( /* Clean transitions flag. */ CurrentAV->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked = true; + } else if (alarmack_data->eventStateAcked == + CurrentAV->Event_State) { + /* Send ack notification */ } else { *error_code = ERROR_CODE_INVALID_EVENT_STATE; return -1; diff --git a/src/bacnet/basic/object/av.h b/src/bacnet/basic/object/av.h index 09ec5316..97fc3ddd 100644 --- a/src/bacnet/basic/object/av.h +++ b/src/bacnet/basic/object/av.h @@ -117,6 +117,14 @@ extern "C" { float Analog_Value_Present_Value( uint32_t object_instance); + BACNET_STACK_EXPORT + unsigned Analog_Value_Event_State( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Analog_Value_Event_State_Set( + uint32_t object_instance, + unsigned state); + BACNET_STACK_EXPORT bool Analog_Value_Change_Of_Value( uint32_t instance); diff --git a/src/bacnet/basic/object/nc.c b/src/bacnet/basic/object/nc.c index 59c5b2a8..bd8a812a 100644 --- a/src/bacnet/basic/object/nc.c +++ b/src/bacnet/basic/object/nc.c @@ -46,6 +46,14 @@ #include "bacnet/basic/tsm/tsm.h" #include "bacnet/wp.h" #include "bacnet/basic/object/nc.h" +#include "bacnet/datalink/datalink.h" + +#if PRINT_ENABLED +#include +#define PRINTF(...) fprintf(stderr,__VA_ARGS__) +#else +#define PRINTF(...) +#endif #ifndef MAX_NOTIFICATION_CLASSES #define MAX_NOTIFICATION_CLASSES 2 @@ -89,7 +97,29 @@ void Notification_Class_Init(void) NC_Info[NotifyIdx].Priority[TRANSITION_TO_FAULT] = 255; /* The lowest priority for Normal message. */ NC_Info[NotifyIdx].Priority[TRANSITION_TO_NORMAL] = - 255; /* The lowest priority for Normal message. */ + 255; /* PRINTF lowest priority for Normal message. */ + /* configure for every day, all day long */ + for (unsigned i = 0; i < MAX_BACNET_DAYS_OF_WEEK; i++) { + NC_Info[NotifyIdx].Recipient_List->ValidDays |= (1<FromTime.hour = 0; + NC_Info[NotifyIdx].Recipient_List->FromTime.min = 0; + NC_Info[NotifyIdx].Recipient_List->FromTime.sec = 0; + NC_Info[NotifyIdx].Recipient_List->FromTime.hundredths = 0; + NC_Info[NotifyIdx].Recipient_List->ToTime.hour = 23; + NC_Info[NotifyIdx].Recipient_List->ToTime.min = 59; + NC_Info[NotifyIdx].Recipient_List->ToTime.sec = 59; + NC_Info[NotifyIdx].Recipient_List->ToTime.hundredths = 0; + NC_Info[NotifyIdx].Recipient_List->Transitions = + TRANSITION_TO_OFFNORMAL_MASKED | + TRANSITION_TO_FAULT_MASKED | + TRANSITION_TO_NORMAL_MASKED; + NC_Info[NotifyIdx].Recipient_List->ConfirmedNotify = false; + NC_Info[NotifyIdx].Recipient_List->ConfirmedNotify = false; + NC_Info[NotifyIdx].Recipient_List->Recipient.RecipientType = + RECIPIENT_TYPE_DEVICE; + NC_Info[NotifyIdx].Recipient_List->Recipient._.DeviceIdentifier = + 4194303; } return; @@ -862,6 +892,8 @@ void Notification_Class_common_reporting_function( } /* send notifications for active recipients */ + PRINTF("Notification Class[%u]: send notifications\n", + event_data->notificationClass); /* pointer to first recipient */ pBacDest = &CurrentNotify->Recipient_List[0]; for (index = 0; index < NC_MAX_RECIPIENTS; index++, pBacDest++) { @@ -869,7 +901,7 @@ void Notification_Class_common_reporting_function( if (pBacDest->Recipient.RecipientType == RECIPIENT_TYPE_NOTINITIALIZED) break; /* recipient doesn't defined - end of list */ - if (IsRecipientActive(pBacDest, event_data->toState) == true) { + if (IsRecipientActive(pBacDest, event_data->toState)) { BACNET_ADDRESS dest; uint32_t device_id; unsigned max_apdu; @@ -881,7 +913,8 @@ void Notification_Class_common_reporting_function( if (pBacDest->Recipient.RecipientType == RECIPIENT_TYPE_DEVICE) { /* send notification to the specified device */ device_id = pBacDest->Recipient._.DeviceIdentifier; - + PRINTF("Notification Class[%u]: send notification to %u\n", + event_data->notificationClass, (unsigned)device_id); if (pBacDest->ConfirmedNotify == true) Send_CEvent_Notify(device_id, event_data); else if (address_get_by_device(device_id, &max_apdu, &dest)) @@ -889,6 +922,8 @@ void Notification_Class_common_reporting_function( Handler_Transmit_Buffer, event_data, &dest); } else if (pBacDest->Recipient.RecipientType == RECIPIENT_TYPE_ADDRESS) { + PRINTF("Notification Class[%u]: send notification to ADDR\n", + event_data->notificationClass); /* send notification to the address indicated */ if (pBacDest->ConfirmedNotify == true) { if (address_get_device_id(&dest, &device_id)) diff --git a/src/bacnet/basic/service/s_ack_alarm.c b/src/bacnet/basic/service/s_ack_alarm.c index 32725281..5028bb02 100644 --- a/src/bacnet/basic/service/s_ack_alarm.c +++ b/src/bacnet/basic/service/s_ack_alarm.c @@ -42,40 +42,46 @@ #include "bacnet/datalink/datalink.h" /** @file s_ack_alarm.c Send an Alarm Acknowledgment. */ +#if PRINT_ENABLED +#include +#define PRINTF(...) fprintf(stderr,__VA_ARGS__) +#else +#define PRINTF(...) +#endif -/* returns the invoke ID for confirmed request, or zero on failure */ - -uint8_t Send_Alarm_Acknowledgement( - uint32_t device_id, BACNET_ALARM_ACK_DATA *data) +/** Sends an Confirmed Alarm Acknowledgment. + * + * @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_Alarm_Acknowledgement_Address(uint8_t *pdu, uint16_t pdu_size, + BACNET_ALARM_ACK_DATA *data, BACNET_ADDRESS *dest) { - BACNET_ADDRESS dest; - BACNET_ADDRESS my_address; - unsigned max_apdu = 0; - uint8_t invoke_id = 0; - bool status = false; int len = 0; int pdu_len = 0; -#if PRINT_ENABLED int bytes_sent = 0; -#endif BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + 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 = alarm_ack_encode_apdu( &Handler_Transmit_Buffer[pdu_len], invoke_id, data); pdu_len += len; @@ -84,29 +90,52 @@ uint8_t Send_Alarm_Acknowledgement( 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 PRINT_ENABLED + if ((uint16_t)pdu_len < pdu_size) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, dest, + &npdu_data, pdu, (uint16_t)pdu_len); bytes_sent = -#endif - datalink_send_pdu( - &dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); -#if PRINT_ENABLED - if (bytes_sent <= 0) - fprintf(stderr, "Failed to Send Alarm Ack Request (%s)!\n", + datalink_send_pdu(dest, &npdu_data, pdu, pdu_len); + if (bytes_sent <= 0) { + PRINTF("Failed to Send Alarm Ack Request (%s)!\n", strerror(errno)); -#endif + } } else { tsm_free_invoke_id(invoke_id); invoke_id = 0; -#if PRINT_ENABLED - fprintf(stderr, - "Failed to Send Alarm Ack Request " + PRINTF("Failed to Send Alarm Ack Request " "(exceeds destination maximum APDU)!\n"); -#endif } } return invoke_id; } + +/** Sends an Confirmed Alarm Acknowledgment + * @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_Alarm_Acknowledgement( + uint32_t device_id, BACNET_ALARM_ACK_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_Alarm_Acknowledgement_Address( + Handler_Transmit_Buffer, max_apdu, + data, &dest); + } + + return invoke_id; +} diff --git a/src/bacnet/basic/service/s_ack_alarm.h b/src/bacnet/basic/service/s_ack_alarm.h index 9a1f6412..584631b8 100644 --- a/src/bacnet/basic/service/s_ack_alarm.h +++ b/src/bacnet/basic/service/s_ack_alarm.h @@ -43,6 +43,10 @@ extern "C" { #endif /* __cplusplus */ +BACNET_STACK_EXPORT +uint8_t Send_Alarm_Acknowledgement_Address(uint8_t *pdu, uint16_t pdu_size, + BACNET_ALARM_ACK_DATA *data, BACNET_ADDRESS *dest); + BACNET_STACK_EXPORT uint8_t Send_Alarm_Acknowledgement(uint32_t device_id, BACNET_ALARM_ACK_DATA* data); diff --git a/src/bacnet/datetime.c b/src/bacnet/datetime.c index 3f35319b..5f97330b 100644 --- a/src/bacnet/datetime.c +++ b/src/bacnet/datetime.c @@ -1087,6 +1087,77 @@ int bacapp_decode_context_datetime( return apdu_len; } +#if PRINT_ENABLED +/** + * @brief Parse an ascii string for the date 2021/12/31 or 2021/12/31:1 + * @param bdate - #BACNET_DATE structure + * @param argv - C string with date formatted 2021/12/31 or 2021/12/31:1 + * or year/month/day or year/month/day:weekday + * @return true if parsed successfully + */ +bool datetime_date_init_ascii(BACNET_DATE *bdate, const char *ascii) +{ + bool status = false; + int year, month, day, wday; + int count = 0; + + count = + sscanf(ascii, "%4d/%3d/%3d:%3d", &year, &month, &day, &wday); + if (count == 3) { + datetime_set_date(bdate, (uint16_t)year, (uint8_t)month, (uint8_t)day); + status = true; + } else if (count == 4) { + bdate->year = (uint16_t)year; + bdate->month = (uint8_t)month; + bdate->day = (uint8_t)day; + bdate->wday = (uint8_t)wday; + status = true; + } + + return status; +} +#endif + +#if PRINT_ENABLED +/** + * @brief Parse an ascii string for the time formatted 23:59:59.99 + * @param btime - #BACNET_TIME structure + * @param ascii - C string with time formatted 23:59:59.99 or 23:59:59 or + * or 23:59 or hours:minutes:seconds.hundredths + * @return true if parsed successfully + */ +bool datetime_time_init_ascii(BACNET_TIME *btime, const char *ascii) +{ + bool status = false; + int hour, min, sec, hundredths; + int count = 0; + + count = sscanf( + ascii, "%3d:%3d:%3d.%3d", &hour, &min, &sec, &hundredths); + if (count == 4) { + btime->hour = (uint8_t)hour; + btime->min = (uint8_t)min; + btime->sec = (uint8_t)sec; + btime->hundredths = (uint8_t)hundredths; + status = true; + } else if (count == 3) { + btime->hour = (uint8_t)hour; + btime->min = (uint8_t)min; + btime->sec = (uint8_t)sec; + btime->hundredths = 0; + status = true; + } else if (count == 2) { + btime->hour = (uint8_t)hour; + btime->min = (uint8_t)min; + btime->sec = 0; + btime->hundredths = 0; + status = true; + } + + return status; +} +#endif + #ifdef BAC_TEST #include #include diff --git a/src/bacnet/datetime.h b/src/bacnet/datetime.h index 1761159b..7d67ccc1 100644 --- a/src/bacnet/datetime.h +++ b/src/bacnet/datetime.h @@ -299,6 +299,15 @@ extern "C" { int16_t utc_offset_minutes, int8_t dst_adjust_minutes); + BACNET_STACK_EXPORT + bool datetime_date_init_ascii( + BACNET_DATE *bdate, + const char *ascii); + BACNET_STACK_EXPORT + bool datetime_time_init_ascii( + BACNET_TIME *btime, + const char *ascii); + BACNET_STACK_EXPORT int bacapp_encode_datetime( uint8_t * apdu, diff --git a/src/bacnet/timestamp.c b/src/bacnet/timestamp.c index 67f8e312..2283bfe5 100644 --- a/src/bacnet/timestamp.c +++ b/src/bacnet/timestamp.c @@ -33,6 +33,7 @@ ####COPYRIGHTEND####*/ #include #include +#include "bacnet/bacapp.h" #include "bacnet/timestamp.h" /** @file timestamp.c Encode/Decode BACnet Timestamps */ @@ -265,6 +266,89 @@ int bacapp_decode_context_timestamp( return len; } +#if PRINT_ENABLED +/** + * @brief Parse an ascii string for the timestamp + * @param btime - #BACNET_TIME structure + * @param ascii - C string with timestamp formatted as one of the following: + * time - 23:59:59.99 or 23:59:59 or 23:59 or + * datetime - 2021/12/31 or 2021/12/31-23:59:59.99 or 2021/12/31-23:59:59 or + * 2021/12/31-23:59 2021/12/31-23 or + * sequence number: 1234 + * @return true if parsed successfully + */ +bool bacapp_timestamp_init_ascii(BACNET_TIMESTAMP *timestamp, const char *ascii) +{ + bool status = false; + int hour, min, sec, hundredths; + int year, month, day; + int sequence; + int count = 0; + + count = sscanf( + ascii, "%3d:%3d:%3d.%3d", &hour, &min, &sec, &hundredths); + if (count == 4) { + timestamp->tag = TIME_STAMP_TIME; + timestamp->value.time.hour = (uint8_t)hour; + timestamp->value.time.min = (uint8_t)min; + timestamp->value.time.sec = (uint8_t)sec; + timestamp->value.time.hundredths = (uint8_t)hundredths; + status = true; + } else if (count == 3) { + timestamp->tag = TIME_STAMP_TIME; + timestamp->value.time.hour = (uint8_t)hour; + timestamp->value.time.min = (uint8_t)min; + timestamp->value.time.sec = (uint8_t)sec; + timestamp->value.time.hundredths = 0; + status = true; + } else if (count == 2) { + timestamp->tag = TIME_STAMP_TIME; + timestamp->value.time.hour = (uint8_t)hour; + timestamp->value.time.min = (uint8_t)min; + timestamp->value.time.sec = 0; + timestamp->value.time.hundredths = 0; + status = true; + } + if (!status) { + count = + sscanf(ascii, "%4d/%3d/%3d-%3d:%3d:%3d.%3d", + &year, &month, &day, &hour, &min, &sec, &hundredths); + if (count >= 3) { + timestamp->tag = TIME_STAMP_DATETIME; + datetime_set_date(×tamp->value.dateTime.date, + (uint16_t)year, (uint8_t)month, (uint8_t)day); + if (count >= 7) { + datetime_set_time(×tamp->value.dateTime.time, + (uint8_t)hour, (uint8_t)min, (uint8_t)sec, + (uint8_t)hundredths); + } else if (count >= 6) { + datetime_set_time(×tamp->value.dateTime.time, + (uint8_t)hour, (uint8_t)min, (uint8_t)sec, 0); + } else if (count >= 5) { + datetime_set_time(×tamp->value.dateTime.time, + (uint8_t)hour, (uint8_t)min, 0, 0); + } else if (count >= 4) { + datetime_set_time(×tamp->value.dateTime.time, + (uint8_t)hour, 0, 0, 0); + } else { + datetime_set_time(×tamp->value.dateTime.time, 0, 0, 0, 0); + } + status = true; + } + } + if (!status) { + count = sscanf(ascii, "%4d", &sequence); + if (count == 1) { + timestamp->tag = TIME_STAMP_DATETIME; + timestamp->value.sequenceNum = (uint16_t)sequence; + status = true; + } + } + + return status; +} +#endif + #ifdef BAC_TEST #include diff --git a/src/bacnet/timestamp.h b/src/bacnet/timestamp.h index bcde4393..6e36bee1 100644 --- a/src/bacnet/timestamp.h +++ b/src/bacnet/timestamp.h @@ -90,6 +90,11 @@ extern "C" { uint8_t tag_number, BACNET_TIMESTAMP * value); + BACNET_STACK_EXPORT + bool bacapp_timestamp_init_ascii( + BACNET_TIMESTAMP *timestamp, + const char *ascii); + #ifdef __cplusplus } #endif /* __cplusplus */