diff --git a/bacnet-stack/demo/Makefile b/bacnet-stack/demo/Makefile index e31cb966..2fb41e5c 100644 --- a/bacnet-stack/demo/Makefile +++ b/bacnet-stack/demo/Makefile @@ -56,7 +56,7 @@ LFLAGS := -Wl,$(BACNET_LIB),$(SYSTEM_LIB) SUBDIRS = readprop writeprop readfile writefile reinit server dcc \ whohas whois ucov scov timesync epics readpropm readrange \ - uptransfer + uptransfer getevent ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter readbdt diff --git a/bacnet-stack/demo/getevent/Makefile b/bacnet-stack/demo/getevent/Makefile new file mode 100644 index 00000000..3a90a91e --- /dev/null +++ b/bacnet-stack/demo/getevent/Makefile @@ -0,0 +1,44 @@ +#Makefile to build BACnet Application using GCC compiler + +# tools - only if you need them. +# Most platforms have this already defined +# CC = gcc +# AR = ar +# MAKE = make +# SIZE = size +# +# Assumes rm and cp are available + +# Executable file name +TARGET = bacge + +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +SRCS = main.c \ + ../object/device-client.c + +OBJS = ${SRCS:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +lib: ${BACNET_LIB_TARGET} + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +clean: + rm -rf core ${TARGET_BIN} ${OBJS} ${BACNET_LIB_TARGET} + +include: .depend diff --git a/bacnet-stack/demo/getevent/main.c b/bacnet-stack/demo/getevent/main.c new file mode 100755 index 00000000..91e8fcae --- /dev/null +++ b/bacnet-stack/demo/getevent/main.c @@ -0,0 +1,283 @@ +/************************************************************************* +* Copyright (C) 2015 B Weitsch +* +* 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, and displays the reply */ +#include +#include +#include +#include +#include +#include + +/* core stuff needed */ +#include "bacdef.h" +#include "config.h" +#include "bactext.h" +#include "bacerror.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +#include "getevent.h" + +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" +#include "dlenv.h" + +#define MAX_OBJ_IDS_IN_GE_ACK 10 + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = {0}; + +/* converted command line arguments */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +/* the invoke id is needed to filter incoming messages */ +static uint8_t Request_Invoke_ID = 0; +static BACNET_ADDRESS Target_Address; +static bool Error_Detected = false; +static bool Recieved_Ack = false; +static bool More_Events = false; +static BACNET_OBJECT_ID LastReceivedObjectIdentifier; + +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\r\n", + bactext_error_class_name((int) error_class), + bactext_error_code_name((int) error_code)); + Error_Detected = true; + } +} + +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\r\n", + bactext_abort_reason_name((int) abort_reason)); + Error_Detected = true; + } +} + +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\r\n", + bactext_reject_reason_name((int) reject_reason)); + Error_Detected = true; + } +} + +/** Handler for a GetEvent ACK. + * Print out the ACK data of a matching request and check if we need + * to repeat or GetEventInformation 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. + */ +void My_Get_Event_Ack_Handler( + uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data) +{ + int len = 0; + int i; + BACNET_GET_EVENT_INFORMATION_DATA data[MAX_OBJ_IDS_IN_GE_ACK]; + for (i = 0; i < MAX_OBJ_IDS_IN_GE_ACK - 1; i++) + data[i].next = &data[i+1]; + + printf("Recieved Ack. Saved invoke ID was %i, service returned %i\n", Request_Invoke_ID, service_data->invoke_id); + + if (service_data->invoke_id == Request_Invoke_ID) { + len = getevent_ack_decode_service_request(service_request, + service_len, + &data[0], + &More_Events); + printf("Decode of Ack returned length %i. MoreEvents flag was %i \n", len, More_Events); + if (len > 0) { + ge_ack_print_data(&(data[0]), Target_Device_Object_Instance); + if (More_Events) { + BACNET_GET_EVENT_INFORMATION_DATA * lastData = &(data[0]); + while (lastData->next) lastData = lastData->next; + LastReceivedObjectIdentifier = lastData->objectIdentifier; + } + } + } + Recieved_Ack = true; +} + +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 getevent - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_GET_EVENT_INFORMATION, + handler_get_event_information); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_GET_EVENT_INFORMATION, + My_Get_Event_Ack_Handler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static int print_help(char *exe_name){ + printf("Usage:\n" "\n" "%s device-instance [--help]\n" "\n" + " Send BACnet GetEventInformation service retruequest to given device, and wait\n" + " for responses.\n\n", exe_name); + return 1; +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = {0}; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + unsigned max_apdu = 0; + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + LastReceivedObjectIdentifier.instance = 0; + LastReceivedObjectIdentifier.type = 0; + + bool found = false; + if (argc <= 1) { + printf("Usage: %s device-instance\r\n", filename_remove_path(argv[0])); + return 0; + } else if(strcmp(argv[1], "--help") == 0) { + print_help(filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameter */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + 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) { + printf("\nSending first GetEventInformation request ...\n"); + Request_Invoke_ID = Send_GetEvent(&Target_Address, NULL); + } else if (More_Events) { + printf("\nSending another GetEventInformation request ...\n"); + Request_Invoke_ID = Send_GetEvent(&Target_Address, + &LastReceivedObjectIdentifier); + More_Events = false; + } else if (tsm_invoke_id_free(Request_Invoke_ID)) + if (Recieved_Ack) break; + else if (tsm_invoke_id_failed(Request_Invoke_ID)) { + fprintf(stderr, "\rError: TSM Timeout!\r\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!\r\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; +} diff --git a/bacnet-stack/demo/handler/h_getevent.c b/bacnet-stack/demo/handler/h_getevent.c index 37840a03..af1b1df0 100644 --- a/bacnet-stack/demo/handler/h_getevent.c +++ b/bacnet-stack/demo/handler/h_getevent.c @@ -43,6 +43,31 @@ static get_event_info_function Get_Event_Info[MAX_BACNET_OBJECT_TYPE]; + +/** print eventState + */ +void ge_ack_print_data( + BACNET_GET_EVENT_INFORMATION_DATA * data, + uint32_t device_id) +{ + BACNET_GET_EVENT_INFORMATION_DATA *act_data = data; + const char* state_strs[] = {"NO", "FA", "ON", "HL", "LL"}; + printf("DeviceID\tType\tInstance\teventState\n"); + printf("--------------- ------- --------------- ---------------\n"); + int count = 0; + while (act_data) { + printf("%u\t\t%u\t%u\t\t%s\n", + device_id, + act_data->objectIdentifier.type, + act_data->objectIdentifier.instance, + state_strs[data->eventState] + ); + act_data = act_data->next; + count++; + } + printf("\n%u\t Total\n",count); +} + void handler_get_event_information_set( BACNET_OBJECT_TYPE object_type, get_event_info_function pFunction) @@ -172,7 +197,7 @@ void handler_get_event_information( goto GET_EVENT_ERROR; } #if PRINT_ENABLED - fprintf(stderr, "GetEventInformation: Sending Ack!\n"); + fprintf(stderr, "Got a GetEventInformation request: Sending Ack!\n"); #endif GET_EVENT_ERROR: if (error) { diff --git a/bacnet-stack/demo/handler/s_getevent.c b/bacnet-stack/demo/handler/s_getevent.c new file mode 100644 index 00000000..5bfcad3f --- /dev/null +++ b/bacnet-stack/demo/handler/s_getevent.c @@ -0,0 +1,111 @@ +/************************************************************************** +* +* Copyright (C) 2015 bowe +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "getevent.h" +#include "bacenum.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" +#include "client.h" +#define PRINT_ENABLED 1 +/** @file s_getevent.c Send a GetEventInformation request. */ + +/** Send a GetEventInformation request to a remote network for a specific device, a range, + * or any device. + * @param target_address [in] BACnet address of target or broadcast + */ +uint8_t Send_GetEvent( + BACNET_ADDRESS * target_address, + BACNET_OBJECT_ID * lastReceivedObjectIdentifier) +{ + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + uint8_t invoke_id = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + + datalink_get_my_address(&my_address); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + + pdu_len = + npdu_encode_pdu(&Handler_Transmit_Buffer[0], target_address, + &my_address, &npdu_data); + + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* encode the APDU portion of the packet */ + len = + getevent_encode_apdu(&Handler_Transmit_Buffer[pdu_len], invoke_id, lastReceivedObjectIdentifier); + pdu_len += len; + bytes_sent = + datalink_send_pdu(target_address, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); + #if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to Send GetEventInformation Request (%s)!\n", + strerror(errno)); + #endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, + "Failed to Send GetEventInformation Request " + "(exceeds destination maximum APDU)!\n"); +#endif + } + return invoke_id; +} + +/** Send a global GetEventInformation request. + */ +uint8_t Send_GetEvent_Global( void ) +{ + BACNET_ADDRESS dest; + + if (!dcc_communication_enabled()) + return -1; + + datalink_get_broadcast_address(&dest); + + return Send_GetEvent(&dest, NULL); +} + diff --git a/bacnet-stack/include/client.h b/bacnet-stack/include/client.h index 27a37579..6facc224 100644 --- a/bacnet-stack/include/client.h +++ b/bacnet-stack/include/client.h @@ -112,7 +112,11 @@ extern "C" { uint32_t device_id, BACNET_SUBSCRIBE_COV_DATA * cov_data); - +/* returns the invoke ID for confirmed request, or 0 if failed */ + uint8_t Send_GetEvent( + BACNET_ADDRESS * target_address, + BACNET_OBJECT_ID *lastReceivedObjectIdentifier); + uint8_t Send_GetEvent_Global(void); /* returns the invoke ID for confirmed request, or 0 if failed */ uint8_t Send_Read_Property_Request_Address( diff --git a/bacnet-stack/include/handlers.h b/bacnet-stack/include/handlers.h index 1accd9a8..1e02d051 100644 --- a/bacnet-stack/include/handlers.h +++ b/bacnet-stack/include/handlers.h @@ -222,6 +222,8 @@ extern "C" { /* print the RP Ack data to stdout */ void rp_ack_print_data( BACNET_READ_PROPERTY_DATA * data); + /* print the GE Ack data to stdout */ + void ge_ack_print_data(BACNET_GET_EVENT_INFORMATION_DATA * data, uint32_t device_id); /* print the RPM Ack data to stdout */ void rpm_ack_print_data( BACNET_READ_ACCESS_DATA * rpm_data); diff --git a/bacnet-stack/lib/Makefile b/bacnet-stack/lib/Makefile index d51536ca..dabddd9f 100644 --- a/bacnet-stack/lib/Makefile +++ b/bacnet-stack/lib/Makefile @@ -130,7 +130,8 @@ HANDLER_SRC = \ $(BACNET_HANDLER)/s_whois.c \ $(BACNET_HANDLER)/s_wpm.c \ $(BACNET_HANDLER)/s_upt.c \ - $(BACNET_HANDLER)/s_wp.c + $(BACNET_HANDLER)/s_wp.c \ + $(BACNET_HANDLER)/s_getevent.c PORT_ARCNET_SRC = \ $(BACNET_PORT_DIR)/arcnet.c