From 857a7ab73612adc951d34f85b4abc4c0a3b77e4e Mon Sep 17 00:00:00 2001 From: skarg Date: Fri, 13 Jan 2006 21:53:46 +0000 Subject: [PATCH] Adding an AtomicReadFille command line demo, and splitting the handlers out of handlers.c. --- bacnet-stack/demo/handler/h_iam.c | 63 ++++ bacnet-stack/demo/handler/h_rp.c | 248 +++++++++++++ bacnet-stack/demo/handler/h_whois.c | 67 ++++ bacnet-stack/demo/handler/handlers.h | 63 ++++ bacnet-stack/demo/handler/noserv.c | 73 ++++ bacnet-stack/demo/handler/txbuf.c | 31 ++ bacnet-stack/demo/handler/txbuf.h | 35 ++ bacnet-stack/demo/readfile/makefile.mak | 137 +++++++ bacnet-stack/demo/readfile/readfile.c | 451 ++++++++++++++++++++++++ bacnet-stack/demo/readfile/readfile.ide | Bin 0 -> 56378 bytes bacnet-stack/handlers.c | 2 - bacnet-stack/tsm.c | 92 +++-- bacnet-stack/tsm.h | 2 + 13 files changed, 1240 insertions(+), 24 deletions(-) create mode 100644 bacnet-stack/demo/handler/h_iam.c create mode 100644 bacnet-stack/demo/handler/h_rp.c create mode 100644 bacnet-stack/demo/handler/h_whois.c create mode 100644 bacnet-stack/demo/handler/handlers.h create mode 100644 bacnet-stack/demo/handler/noserv.c create mode 100644 bacnet-stack/demo/handler/txbuf.c create mode 100644 bacnet-stack/demo/handler/txbuf.h create mode 100644 bacnet-stack/demo/readfile/makefile.mak create mode 100644 bacnet-stack/demo/readfile/readfile.c create mode 100644 bacnet-stack/demo/readfile/readfile.ide diff --git a/bacnet-stack/demo/handler/h_iam.c b/bacnet-stack/demo/handler/h_iam.c new file mode 100644 index 00000000..0556bb7f --- /dev/null +++ b/bacnet-stack/demo/handler/h_iam.c @@ -0,0 +1,63 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" + +void handler_i_am( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void)src; + (void)service_len; + len = iam_decode_service_request( + service_request, + &device_id, + &max_apdu, + &segmentation, + &vendor_id); + fprintf(stderr,"Received I-Am Request"); + if (len != -1) + { + fprintf(stderr," from %u!\n",device_id); + address_add(device_id, + max_apdu, + src); + } + else + fprintf(stderr,"!\n"); + + return; +} diff --git a/bacnet-stack/demo/handler/h_rp.c b/bacnet-stack/demo/handler/h_rp.c new file mode 100644 index 00000000..96dedf41 --- /dev/null +++ b/bacnet-stack/demo/handler/h_rp.c @@ -0,0 +1,248 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "rp.h" +/* demo objects */ +#include "device.h" +#include "ai.h" +#include "ao.h" +#if BACFILE +#include "bacfile.h" +#endif + +static uint8_t Temp_Buf[MAX_APDU] = {0}; + +void handler_read_property( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data) +{ + BACNET_READ_PROPERTY_DATA data; + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS my_address; + bool send = false; + bool error = false; + int bytes_sent = 0; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + + len = rp_decode_service_request( + service_request, + service_len, + &data); + if (len <= 0) + fprintf(stderr,"Unable to decode Read-Property Request!\n"); + // prepare a reply + datalink_get_my_address(&my_address); + // encode the NPDU portion of the packet + pdu_len = npdu_encode_apdu( + &Handler_Transmit_Buffer[0], + src, + &my_address, + false, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + // bad decoding - send an abort + if (len == -1) + { + pdu_len += abort_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_OTHER); + fprintf(stderr,"Sending Abort!\n"); + send = true; + } + else if (service_data->segmented_message) + { + pdu_len += abort_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED); + fprintf(stderr,"Sending Abort!\n"); + send = true; + } + else + { + switch (data.object_type) + { + case OBJECT_DEVICE: + // FIXME: probably need a length limitation sent with encode + if (data.object_instance == Device_Object_Instance_Number()) + { + len = Device_Encode_Property_APDU( + &Temp_Buf[0], + data.object_property, + data.array_index, + &error_class, + &error_code); + if (len > 0) + { + // encode the APDU portion of the packet + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + // FIXME: probably need a length limitation sent with encode + pdu_len += rp_ack_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + &data); + fprintf(stderr,"Sending Read Property Ack!\n"); + send = true; + } + else + error = true; + } + else + error = true; + break; + case OBJECT_ANALOG_INPUT: + if (Analog_Input_Valid_Instance(data.object_instance)) + { + len = Analog_Input_Encode_Property_APDU( + &Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, + &error_class, + &error_code); + if (len > 0) + { + // encode the APDU portion of the packet + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + // FIXME: probably need a length limitation sent with encode + pdu_len += rp_ack_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + &data); + fprintf(stderr,"Sending Read Property Ack!\n"); + send = true; + } + else + error = true; + } + else + error = true; + break; + case OBJECT_ANALOG_OUTPUT: + if (Analog_Output_Valid_Instance(data.object_instance)) + { + len = Analog_Output_Encode_Property_APDU( + &Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, + &error_class, + &error_code); + if (len > 0) + { + // encode the APDU portion of the packet + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + // FIXME: probably need a length limitation sent with encode + pdu_len += rp_ack_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + &data); + fprintf(stderr,"Sending Read Property Ack!\n"); + send = true; + } + else + error = true; + } + else + error = true; + break; + #if BACFILE + case OBJECT_FILE: + if (bacfile_valid_instance(data.object_instance)) + { + len = bacfile_encode_property_apdu( + &Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, + &error_class, + &error_code); + if (len > 0) + { + // encode the APDU portion of the packet + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + // FIXME: probably need a length limitation sent with encode + pdu_len += rp_ack_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + &data); + fprintf(stderr,"Sending Read Property Ack!\n"); + send = true; + } + else + error = true; + } + else + error = true; + break; + #endif + default: + error = true; + break; + } + } + if (error) + { + pdu_len += bacerror_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_READ_PROPERTY, + error_class, + error_code); + fprintf(stderr,"Sending Read Property Error!\n"); + send = true; + } + if (send) + { + bytes_sent = datalink_send_pdu( + src, // destination address + &Handler_Transmit_Buffer[0], + pdu_len); // number of bytes of data + if (bytes_sent <= 0) + fprintf(stderr,"Failed to send PDU (%s)!\n", strerror(errno)); + } + + return; +} + diff --git a/bacnet-stack/demo/handler/h_whois.c b/bacnet-stack/demo/handler/h_whois.c new file mode 100644 index 00000000..02357d18 --- /dev/null +++ b/bacnet-stack/demo/handler/h_whois.c @@ -0,0 +1,67 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "whois.h" +#include "device.h" + +/* global flag to send an I-Am */ +bool I_Am_Request = true; + +void handler_who_is( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src) +{ + int len = 0; + int32_t low_limit = 0; + int32_t high_limit = 0; + + (void)src; + len = whois_decode_service_request( + service_request, + service_len, + &low_limit, + &high_limit); + /* in our simple system, we just set a flag and let the main loop + send the I-Am request. */ + if (len == 0) + I_Am_Request = true; + else if (len != -1) + { + if ((Device_Object_Instance_Number() >= (uint32_t)low_limit) && + (Device_Object_Instance_Number() <= (uint32_t)high_limit)) + I_Am_Request = true; + } + + return; +} diff --git a/bacnet-stack/demo/handler/handlers.h b/bacnet-stack/demo/handler/handlers.h new file mode 100644 index 00000000..3f60e9a6 --- /dev/null +++ b/bacnet-stack/demo/handler/handlers.h @@ -0,0 +1,63 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#ifndef HANDLERS_H +#define HANDLERS_H + +#include +#include +#include +#include "bacdef.h" +#include "apdu.h" +#include "bacapp.h" + +/* flag requesting main loop to send an I-Am */ +extern bool I_Am_Request; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void handler_unrecognized_service( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *dest, + BACNET_CONFIRMED_SERVICE_DATA *service_data); + +void handler_who_is( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src); + +void handler_read_property( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/bacnet-stack/demo/handler/noserv.c b/bacnet-stack/demo/handler/noserv.c new file mode 100644 index 00000000..2fdc8427 --- /dev/null +++ b/bacnet-stack/demo/handler/noserv.c @@ -0,0 +1,73 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "reject.h" + +void handler_unrecognized_service( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *dest, + BACNET_CONFIRMED_SERVICE_DATA *service_data) +{ + BACNET_ADDRESS src; + int pdu_len = 0; + int bytes_sent = 0; + + (void)service_request; + (void)service_len; + datalink_get_my_address(&src); + + // encode the NPDU portion of the packet + pdu_len = npdu_encode_apdu( + &Handler_Transmit_Buffer[0], + dest, + &src, + false, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + + // encode the APDU portion of the packet + pdu_len += reject_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + REJECT_REASON_UNRECOGNIZED_SERVICE); + + bytes_sent = datalink_send_pdu( + dest, // destination address + &Handler_Transmit_Buffer[0], + pdu_len); // number of bytes of data + if (bytes_sent > 0) + fprintf(stderr,"Sent Reject!\n"); + else + fprintf(stderr,"Failed to Send Reject (%s)!\n", strerror(errno)); +} diff --git a/bacnet-stack/demo/handler/txbuf.c b/bacnet-stack/demo/handler/txbuf.c new file mode 100644 index 00000000..1b60d630 --- /dev/null +++ b/bacnet-stack/demo/handler/txbuf.c @@ -0,0 +1,31 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#include +#include +#include "config.h" +#include "datalink.h" + +uint8_t Handler_Transmit_Buffer[MAX_MPDU] = {0}; + diff --git a/bacnet-stack/demo/handler/txbuf.h b/bacnet-stack/demo/handler/txbuf.h new file mode 100644 index 00000000..cdfe942a --- /dev/null +++ b/bacnet-stack/demo/handler/txbuf.h @@ -0,0 +1,35 @@ +/************************************************************************** +* +* Copyright (C) 2005 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. +* +*********************************************************************/ +#ifndef TXBUF_H +#define TXBUF_H + +#include +#include +#include "config.h" +#include "datalink.h" + +extern uint8_t Handler_Transmit_Buffer[MAX_MPDU]; + +#endif \ No newline at end of file diff --git a/bacnet-stack/demo/readfile/makefile.mak b/bacnet-stack/demo/readfile/makefile.mak new file mode 100644 index 00000000..9d65f9e8 --- /dev/null +++ b/bacnet-stack/demo/readfile/makefile.mak @@ -0,0 +1,137 @@ +# +# Simple makefile to build an executable for Win32 +# +# This makefile assumes Borland bcc32 development environment +# on Windows NT/9x/2000/XP +# + +!ifndef BORLAND_DIR +BORLAND_DIR_Not_Defined: + @echo . + @echo You must define environment variable BORLAND_DIR to compile. +!endif + +PRODUCT = readfile +PRODUCT_EXE = $(PRODUCT).exe + +# Choose the Data Link Layer to Enable +DEFINES = -DBACDL_BIP=1 + +SRCS = readfile.c \ + ..\..\ports\win32\bip-init.c \ + ..\..\bip.c \ + ..\..\demo\handler\txbuf.c \ + ..\..\demo\handler\noserv.c \ + ..\..\demo\handler\h_whois.c \ + ..\..\demo\handler\h_rp.c \ + ..\..\bacdcode.c \ + ..\..\bacapp.c \ + ..\..\bacstr.c \ + ..\..\bactext.c \ + ..\..\indtext.c \ + ..\..\bigend.c \ + ..\..\whois.c \ + ..\..\iam.c \ + ..\..\rp.c \ + ..\..\wp.c \ + ..\..\arf.c \ + ..\..\awf.c \ + ..\..\demo\object\bacfile.c \ + ..\..\demo\object\device.c \ + ..\..\demo\object\ai.c \ + ..\..\demo\object\ao.c \ + ..\..\datalink.c \ + ..\..\tsm.c \ + ..\..\address.c \ + ..\..\abort.c \ + ..\..\reject.c \ + ..\..\bacerror.c \ + ..\..\apdu.c \ + ..\..\npdu.c + +OBJS = $(SRCS:.c=.obj) + +# Compiler definitions +# +CC = $(BORLAND_DIR)\bin\bcc32 +bcc32.cfg +#LINK = $(BORLAND_DIR)\bin\tlink32 +LINK = $(BORLAND_DIR)\bin\ilink32 +TLIB = $(BORLAND_DIR)\bin\tlib + +# +# Include directories +# +CC_DIR = $(BORLAND_DIR)\BIN +INCL_DIRS = -I$(BORLAND_DIR)\include;..\..\;..\..\demo\object\;..\..\demo\handler\;..\..\ports\win32\;. + +CFLAGS = $(INCL_DIRS) $(CS_FLAGS) $(DEFINES) + +# Libraries +# +C_LIB_DIR = $(BORLAND_DIR)\lib + +LIBS = $(C_LIB_DIR)\IMPORT32.lib \ +$(C_LIB_DIR)\CW32MT.lib + +# +# Main target +# +# This should be the first one in the makefile + +all : bcc32.cfg $(PRODUCT_EXE) + +# Linker specific: the link below is for BCC linker/compiler. If you link +# with a different linker - please change accordingly. +# + +# need a temp response file (@&&) because command line is too long +$(PRODUCT_EXE) : $(OBJS) + @echo Running Linker for $(PRODUCT_EXE) + $(LINK) -L$(C_LIB_DIR) -m -c -s -v @&&| # temp response file, starts with | + $(BORLAND_DIR)\lib\c0x32.obj $** # $** lists each dependency + $< + $*.map + $(LIBS) +| # end of temp response file + +# +# Utilities + +clean : + @echo Deleting obj files, $(PRODUCT_EXE) and map files. + del *.obj + del ..\..\*.obj + del $(PRODUCT_EXE) + del *.map + del bcc32.cfg + +# +# Generic rules +# +.SUFFIXES: .cpp .c .sbr .obj + +# +# cc generic rule +# +.c.obj: + $(CC) -o$@ $< + +# Compiler configuration file +bcc32.cfg : + Copy &&| +$(CFLAGS) +-c +-y #include line numbers in OBJ's +-v #include debug info +-w+ #turn on all warnings +-Od #disable all optimizations +#-a4 #32 bit data alignment +#-M # generate link map +#-ls # linker options +#-WM- #not multithread +-WM #multithread +-w-aus # ignore warning assigned a value that is never used +-w-sig # ignore warning conversion may lose sig digits +| $@ + +# EOF: makefile diff --git a/bacnet-stack/demo/readfile/readfile.c b/bacnet-stack/demo/readfile/readfile.c new file mode 100644 index 00000000..52991f10 --- /dev/null +++ b/bacnet-stack/demo/readfile/readfile.c @@ -0,0 +1,451 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* READFILE: command line tool that reads a file from a BACnet device. */ +#include +#include +#include +#include +#include /* for kbhit and getch */ +#include /* for time */ +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "whois.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +// buffer used for receive +static uint8_t Rx_Buf[MAX_MPDU] = {0}; + +/* global variables used in this file */ +static uint32_t Target_File_Object_Instance = 4194303; +static uint32_t Target_Device_Object_Instance = 4194303; +static BACNET_ADDRESS Target_Address; +static char *Local_File_Name = NULL; +static bool End_Of_File_Detected = false; +static bool Error_Detected = false; +static uint8_t Current_Invoke_ID = 0; + +static void Atomic_Read_File_Error_Handler( + BACNET_ADDRESS *src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void)src; + (void)invoke_id; + printf("\r\nBACnet Error!\r\n"); + printf("Error Class: %s\r\n", + bactext_error_class_name(error_class)); + printf("Error Code: %s\r\n", + bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler( + BACNET_ADDRESS *src, + uint8_t invoke_id, + uint8_t abort_reason) +{ + /* FIXME: verify src and invoke id */ + (void)src; + (void)invoke_id; + printf("\r\nBACnet Abort!\r\n"); + printf("Abort Reason: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler( + BACNET_ADDRESS *src, + uint8_t invoke_id, + uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void)src; + (void)invoke_id; + printf("\r\nBACnet Reject!\r\n"); + printf("Reject Reason: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static uint8_t Send_Atomic_Read_File_Stream( + uint32_t device_id, + uint32_t file_instance, + int fileStartPosition, + unsigned requestedOctetCount) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_ATOMIC_READ_FILE_DATA data; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + status = tsm_transaction_available(); + if (status) + { + datalink_get_my_address(&my_address); + pdu_len = npdu_encode_apdu( + &Handler_Transmit_Buffer[0], + &dest, + &my_address, + true, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + + invoke_id = tsm_next_free_invokeID(); + // load the data for the encoding + data.object_type = OBJECT_FILE; + data.object_instance = file_instance; + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = fileStartPosition; + data.type.stream.requestedOctetCount = requestedOctetCount; + pdu_len += arf_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + invoke_id, + &data); + /* will the APDU fit the target device? + 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, + &Handler_Transmit_Buffer[0], + pdu_len); + bytes_sent = datalink_send_pdu( + &dest, // destination address + &Handler_Transmit_Buffer[0], + pdu_len); // number of bytes of data + if (bytes_sent <= 0) + fprintf(stderr,"Failed to Send AtomicReadFile Request (%s)!\n", + strerror(errno)); + } + else + fprintf(stderr,"Failed to Send AtomicReadFile Request " + "(payload exceeds destination maximum APDU)!\n"); + } + + return invoke_id; +} + +static void Send_WhoIs(uint32_t device_id) +{ + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + + /* Who-Is is a global broadcast */ + datalink_get_broadcast_address(&dest); + + /* encode the NPDU portion of the packet */ + pdu_len = npdu_encode_apdu( + &Handler_Transmit_Buffer[0], + &dest, + NULL, + false, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + + /* encode the APDU portion of the packet */ + pdu_len += whois_encode_apdu( + &Handler_Transmit_Buffer[pdu_len], + device_id, + device_id); + + bytes_sent = datalink_send_pdu( + &dest, /* destination address */ + &Handler_Transmit_Buffer[0], + pdu_len); /* number of bytes of data */ + if (bytes_sent <= 0) + fprintf(stderr,"Failed to Send Who-Is Request (%s)!\n", strerror(errno)); +} + +static void AtomicReadFileAckHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) +{ + int len = 0; + BACNET_ATOMIC_READ_FILE_DATA data; + FILE *pFile = NULL; /* stream pointer */ + size_t octets_written = 0; + + (void)src; /* FIXME: validate the source address matches */ + len = arf_ack_decode_service_request( + service_request, + service_len, + &data); + if (len > 0) + { + /* validate the parameters before storing data */ + if ((data.access == FILE_STREAM_ACCESS) && + (service_data->invoke_id == Current_Invoke_ID)) + { + if (data.type.stream.fileStartPosition == 0) + pFile = fopen(Local_File_Name, "wb"); + else + pFile = fopen(Local_File_Name, "rb+"); + if (pFile) + { + /* is there anything to do with this? data.stream.requestedOctetCount */ + (void)fseek(pFile, data.type.stream.fileStartPosition, SEEK_SET); + octets_written = fwrite( + octetstring_value(&data.fileData), + 1, /* unit to write in bytes - in our case, an octet is one byte */ + octetstring_length(&data.fileData), + pFile); + if (octets_written != octetstring_length(&data.fileData)) + fprintf(stderr,"Unable to write data to file \"%s\".\n", + Local_File_Name); + else + printf("\r%u bytes", + (data.type.stream.fileStartPosition + octets_written)); + fclose(pFile); + } + if (data.endOfFile) + { + End_Of_File_Detected = true; + printf("\r\n"); + } + } + } +} + +static void LocalIAmHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void)src; + (void)service_len; + len = iam_decode_service_request( + service_request, + &device_id, + &max_apdu, + &segmentation, + &vendor_id); + if (len != -1) + { + address_add(device_id, + max_apdu, + src); + } + else + fprintf(stderr,"!\n"); + + return; +} + +static void Init_Service_Handlers(void) +{ + /* 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, + LocalIAmHandler); + /* 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 data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler( + SERVICE_CONFIRMED_ATOMIC_READ_FILE, + AtomicReadFileAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler( + SERVICE_CONFIRMED_ATOMIC_READ_FILE, + Atomic_Read_File_Error_Handler); + apdu_set_abort_handler( + MyAbortHandler); + apdu_set_reject_handler( + MyRejectHandler); +} + +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; + bool status = false; + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + int fileStartPosition = 0; + unsigned requestedOctetCount = 0; + uint8_t invoke_id = 0; + bool found = false; + + if (argc < 4) + { + /* FIXME: what about access method - record or stream? */ + printf("%s device-instance file-instance local-name\r\n",argv[0]); + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1],NULL,0); + Target_File_Object_Instance = strtol(argv[2],NULL,0); + Local_File_Name = argv[3]; + if (Target_Device_Object_Instance >= BACNET_MAX_INSTANCE) + { + fprintf(stderr,"device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance,BACNET_MAX_INSTANCE); + return 1; + } + if (Target_File_Object_Instance >= BACNET_MAX_INSTANCE) + { + fprintf(stderr,"file-instance=%u - it must be less than %u\r\n", + Target_File_Object_Instance,BACNET_MAX_INSTANCE+1); + return 1; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + /* configure standard BACnet/IP port */ + bip_set_port(0xBAC0); + if (!bip_init()) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance); + /* loop forever */ + for (;;) + { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = bip_receive( + &src, + &Rx_Buf[0], + MAX_MPDU, + timeout); + + /* process */ + if (pdu_len) + { + npdu_handler( + &src, + &Rx_Buf[0], + pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - last_seconds) * 1000)); + if (End_Of_File_Detected || Error_Detected) + break; + if (I_Am_Request) + { + I_Am_Request = false; + iam_send(&Handler_Transmit_Buffer[0]); + } + else + { + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request( + Target_Device_Object_Instance, + &max_apdu, + &Target_Address); + if (found) + { + /* calculate the smaller of our APDU size or theirs + and remove the overhead of the APDU (about 14 octets max). + note: we could fail if there is a bottle neck (router) + and smaller MPDU in betweeen. */ + requestedOctetCount = min(max_apdu,MAX_APDU) - 14; + /* has the previous invoke id expired or returned? + note: invoke ID = 0 is invalid, so it will be idle */ + if ((invoke_id == 0) || tsm_invoke_id_free(invoke_id)) + { + if (invoke_id != 0) + fileStartPosition += requestedOctetCount; + /* we'll read the file in chunks + less than max_apdu to keep unsegmented */ + invoke_id = Send_Atomic_Read_File_Stream( + Target_Device_Object_Instance, + Target_File_Object_Instance, + fileStartPosition, + requestedOctetCount); + Current_Invoke_ID = invoke_id; + } + } + else + { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) + break; + } + } + /* wait for ESC from keyboard before quitting */ + if (kbhit() && (getch() == 0x1B)) + break; + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/readfile/readfile.ide b/bacnet-stack/demo/readfile/readfile.ide new file mode 100644 index 0000000000000000000000000000000000000000..3d7b8ff417ae20c2c1a3ae66d5acca8491d9e2f0 GIT binary patch literal 56378 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%0DLovCe&+CS)N%|7p6^_m3&q2c>t8)QJv=;% zoj%^r!;$Wc!(-AU4lWh5F?Z)$!vv*=BV#-VlW!Ch4@c7E$UMO-yU0;I9C>?1;pr5+ z(Y85XH9_%kq)v;%voZ<~NA^BZcvg!n%)2;0H9_g&OkhA1o@>N5@NjNvg5u%KVQ>_l z>kO%b6%S_`XE{8al{f^!>zzw;p?Ekm33+(>d3e?tQU@y@&SZvoc#=IlH#oHJMYi}A zzC$}RuqNvnG9wYED1@9fPRFb<9>0~CjF};RBM{I7&UGz(~MoKp|iZ zU_9V#Ko~FqFbQxjU@Bl5U^-w1pbStBm<^Z%I1g|>paL)#Fb{A6U;$tupbBsi;9>y3 zS9KYn8o=*KEdubIiVFGSaIHFVkSMBC4kz!Fw^@$)cvc+1?+*wu3G0Xi(YP-W=bU** z-TeZqQWD2V8qT>$E1c4h5&7p_@F&Za5l^?IY7QljT#A$ODq<#M9;P%TXn8yFC0UlC z8~WfB%}pZi4k1s=q+)hN%}QQ8t_k=yG~|`ylqS!08rzbP@*(80KI{+&_f;~eS!t(? z%30ySg&rBBka}`XIN+9{E6J6S2r7cIV^Zv7JP$WH<@3)(aj0iFK%cNf0CuQyB8Rbw zmJ^h`Qz)lo#7w?YaD4{(qJP=Jl}8REj~oOgZztrXB4*@KMhIV~q_5h+l}8REuLrIP zO5R$$wZZp1j69aal9X_Oes2f2%vdS}C8Kj(IKcNVjEr=ogpfzJ8#}l%NJUUG_TcnA z&+8i*8Av&Ue6iix!IeQOf|9WTr@wh#+{mDJeFzzBw{~!4^Z^w?$(V#Uaro|zk-?H$ zhc8sJ-P^&H(GOGvB_kDYfbpF%BZJaEgADc?c5r3%2NgldsKA?Xe3#D1p!9smV83Gr zSH?h45tNKD-k^NaBZJb{rG*3Rx9s4`7z8SUl5u8mIKX#HjUAL;4H@kB?BL2c3seLp zV;|l&<-4^;#$2R?A%p#<9b6ehKt)h8Iz!FC*|f1tu?)UI7^#x9s4`7!4|d zl97ooaPWH!Mg~16A2K-Z*};`D22=zkqYPgl+3Jx&>2Z+3anla2jIp31C>g2v;>xWa z8I*o9JBOu8awwA}AThA>(?F3`(zr49+|3;L11$R0JiX9$(B_ z>ybg}1(3mciyd4UlR-sLGFFcc2d?(Wp!6VQaNc7FSH?6@5tNL*_&QyOM+T)I9vBXA z-ed8HC!x4JGhVxuB4t;nKPM^SjRnOnNyMq*&_2IrN0-nPxt8G_7hd+dEn}W z&$Gl!{oI%3{wMd`dA8U{G5Jpu^N^3Oo>B-YY4}PR*A9`Ni11m2k!H#y9Va#yMUci- zbUM~=Tye!hVl1Aq;8`Ic6~MEyJpnAITtIha)w`Rr>MhHP8f?2Vy5Y)*%9YuTZ)8f} zpe|Z%>+92vzCKa)_3efWzJPy`^46~#EBi&X($swx{AWsp+G13cTUfNt~-kb22QZ{?|3wjf)=z;5z9Q0AEu%>F;yyYbNMZY;(xI^v(#VpCFs zy3sdCm$7=5Xj?kC8-0VNKDJDo>nzmEL{H7EJN1$55x{|GX|ec6EpiG_@g90MBsoJXIvW$rTG=Y1e+T z7?SaH2LFs?KAoY@QgU|^hgoI2Q`a&3%3;2;jnr30DqmB!#t-tkX=iz|ozV}>(`)wg zMuKTA!@KeO;nMF-Uv4ir1bcV~vF1=r$< z-L#O2vW2kzIDd~+O6OQQGxeH798PpP6KF(I!($lOz|PM;l8`TbkU>C^uP- za(p&zmlDs6^W3SCW_)RiWy5UGnh>oM|0vCH8OzVL+X)D0Iorv(vYnXGz;GIU{3^M3 z4Z8hRw|1sVeH@Jpm(e%P(pQG(Uq;_FsgFI7`wM@r|4z3!kN@mFhjY5*G^4kzbB4va z*Q0YrM4iUkGJKh-*3;Xi29&ikQ95P#YE%z->Wd={#|f@|*c#|ttZ%k>p2@LKdl~zT zACy}q$#mmA<+3E{12)&pZn$6{{zc-N)eYCIs9dwH5;PXlOJ>Uwq%NK!Fjg8bnPcfY z`LXLIbGqm=<<2#i&;W*Bl?&rp?MZ(o7P9pd6Lu2M2)3J=X{Ivs7L4gh&l~trPcEk ze5%Tq>-$RC^O#o8@}*DHmyKT=J4_BPuzK87UB{eFT_Ahh5h!PSpTpR~8MJq=VVlR6 z1>e{fBWsyk`yOuM$ zF16;IluBKkb8;rBPTfYDbDHwJ%+hg9~kSZnlFccZuZ8|l5=(wq8;+Y?+a z^>STk>s@5E%0t>`*q#^3R>?E5JTqlD%{Z{w(#LdDD~qK*uI&w%DV-%2SCz-gC0)2o z>C{+UUR^bki>JqIU9}e1?xSxz7S>8G?-*co)mdC#U3HQxQY)&rxIXdds+U~eS&-4y zU~zqT+HJ87l8YzLOukIXFSY8-SjakCD(j4I+_)#w+*!#oOJC7jZiy_D`p9YY87mtt zu1SA%t!(UKrK#yAi)*zH#!{nmxuvsugj(;h z2V5?7M%rs?w#qY{{%h$cEYD_VWMZ4Q^{ueO0?43np(XyWv{dh0EmaDvN8aCvR7E z;WBw!WpNe#hueZyNiLqQwDYyv;_~Kewd8V7T)J)iYK!ZL$HJ?-a2X5NSX|pZ7Os(8 zBO+LMjm71)@EXbGp6_%mTx)Tqj&l8DZ5J+M;kDLW=}T=PXMxwsxe`y)o7r`w_fNcQ z{_89oyp~@lZHTmkdVM!s*GJ`A*A3UYs9ZO6!*xR!F5{o;Ev_n0*{<)xWuDO9Xtli! zc;0Gyq#JdQ#8W@!8FaQVoEt38#^-+FaBh&C@p!iTLl38UzI&5p>4r%^m7MHbZjzR| zCsAE}8!fIz57$P?mFRhvY^=Q5;@UIW)pfJvN{Ya>$>OTKU+vn`!cCGZB?8wiR*8I; z^np_%x5yId0j`%kC1R}HY;jgSp!Bhy-YhwJKJZWk&RcEHOjqZvl9Q(bzZZdXi^ZA$ zQ&;B}$;q>TuSej#&EhP;&&?a3xlMBNjNX9=oLeo<-4D7tw@OZ)%zGgM=j|5f{tsQw z+a)K@gMwf$;tCsPetIo)8cHN;d0(7 zIeFsli3pr`S)6fMF6Uj6a|k&3+ZlE~w_BXg?s7S|OU|L-d^iH<-4H;lW370x6)&;krB-~L6(4WK&$i-WD?Y)BPqgCaSn)|#e6kgvV#Uw3;!~~o zG%G&ciqEj(Wmdf0iqEv-v#j`RD?Z1HpQqzF+CR?MaeNX%h6*cvt`(oB<3lw4d>uF6 z_^hA1Qp#0^$_y^iCkunafqxIP{%Ht0Bx1j}%fj+^$hQOAvc z^SjjyIBG1zCLK5WZ_#lx_uZ`Hrajzh(Qnalv%hki#lO{x->&0E-Zm@!4jni4+-cF@ zWzlc9;&)s0_vpB>?_P_3hmIS4_gV4#t@s00e5VzE(2DP};tyHz-B$czEB=TTf7FWa zvEq+e@yD(B6IOh$6@Su-KV`+Aw&MG&__wY2GgkaNR{U8j{+tzm-ip6q#rIqB7p?dK zEB;+8e$a})WW`^$;;&foLstA%EB=}l|DF{;Y{kEC#b3ALKd|CQtoRSD_#0OIO)GxX zioa#Wk6G~_S@Gjm{B0}#jun5`ioa*Y-?!p;JHgovd)k`y1|RsnvvPV`&U*P~?J>`+ zH+&P`LdUEZ?`gyziFaqc+4%L0H$k`UP`ua^A3*vGx<6FE8MYVSPCo(IH&uvrS0f3Z zKUud`;JZQi&6XVkZ-v9I`_ymvehyrX>A<|l1a`FWRtWG!;Ma8gH1NxaKl8Y{{|WGU zh!;Ph=o^7&G97px@EPD*;6-~?`gVI7 zfN9?e9q;+HikAUT!Tt5X=K&uFrvAcx%AU2rmm>ZFFu#_W|814N8JPNx15Up?d3rzVtfT{1tIv(7w;?p%=tMLKgrJ&1xQSmL%c$3Dj0aMPG zI$n4{+0zM3Jx^(T5}0%Y@Z);KRT^&vrd=Na)9wKWRlHH-1HhDXO5>cDRQwW+w`zPu z<39tld?&rE=-Po9->>6;0G^0=?kg(&QjNE1{D#J7G%h}*=o&TN1x)@Ab-d@RD!okO zHNecrK^^}TnDqItDY`|#jPKC#cXT}XJ(WI1<4$1GJ)`3vYdqkvqN~(+qsE6c{#@hy z?`wS;ZwF>M|3=5NURUY!G~S@`%Nl*0Ql-_tnZh`K)snDkd^{H(@* z&^Y&pDt(d0J2d{W#;I?p^qCs3)A(hLPitKGrlM=q_z{gi(zx$Ym0qdwW{r<%{8x>` zZz;NVjrVDcEXeRX9S$I3)fr-3RdM literal 0 HcmV?d00001 diff --git a/bacnet-stack/handlers.c b/bacnet-stack/handlers.c index 3f12ed63..928f2886 100644 --- a/bacnet-stack/handlers.c +++ b/bacnet-stack/handlers.c @@ -210,8 +210,6 @@ bool Send_Read_Property_Request( return status; } - - bool Send_Write_Property_Request( uint32_t device_id, // destination device BACNET_OBJECT_TYPE object_type, diff --git a/bacnet-stack/tsm.c b/bacnet-stack/tsm.c index 30692167..a4f3b25e 100644 --- a/bacnet-stack/tsm.c +++ b/bacnet-stack/tsm.c @@ -56,18 +56,35 @@ // FIXME: not coded for segmentation // declare space for the TSM transactions, and set it up in the init. +/* table rules: an Invoke ID = 0 is an unused spot in the table */ static BACNET_TSM_DATA TSM_List[MAX_TSM_TRANSACTIONS] = {{0}}; // returns MAX_TSM_TRANSACTIONS if not found -uint8_t tsm_find_invokeID_index(uint8_t invokeID) +static uint8_t tsm_find_invokeID_index(uint8_t invokeID) { unsigned i = 0; // counter uint8_t index = MAX_TSM_TRANSACTIONS; // return value for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { - if ((TSM_List[i].state != TSM_STATE_IDLE) && - (TSM_List[i].InvokeID == invokeID)) + if (TSM_List[i].InvokeID == invokeID) + { + index = i; + break; + } + } + + return index; +} + +static uint8_t tsm_find_first_free_index(void) +{ + unsigned i = 0; // counter + uint8_t index = MAX_TSM_TRANSACTIONS; // return value + + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) + { + if (TSM_List[i].InvokeID == 0) { index = i; break; @@ -81,10 +98,10 @@ bool tsm_transaction_available(void) { bool status = false; // return value unsigned i = 0; // counter - + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { - if (TSM_List[i].state == TSM_STATE_IDLE) + if (TSM_List[i].InvokeID == 0) { // one is available! status = true; @@ -99,10 +116,11 @@ uint8_t tsm_transaction_idle_count(void) { uint8_t count = 0; // return value unsigned i = 0; // counter - + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { - if (TSM_List[i].state == TSM_STATE_IDLE) + if ((TSM_List[i].InvokeID == 0) && + (TSM_List[i].state == TSM_STATE_IDLE)) { // one is available! count++; @@ -112,6 +130,9 @@ uint8_t tsm_transaction_idle_count(void) return count; } +/* gets the next free invokeID, + and reserves a spot in the table + returns 0 if none are available */ uint8_t tsm_next_free_invokeID(void) { static uint8_t current_invokeID = 1; // incremented... @@ -122,18 +143,26 @@ uint8_t tsm_next_free_invokeID(void) while (!found) { index = tsm_find_invokeID_index(current_invokeID); + /* not found - that is good! */ if (index == MAX_TSM_TRANSACTIONS) { found = true; - invokeID = current_invokeID; + /* set this id into the table */ + index = tsm_find_first_free_index(); + if (index != MAX_TSM_TRANSACTIONS) + { + TSM_List[index].InvokeID = invokeID = current_invokeID; + TSM_List[index].state = TSM_STATE_IDLE; + TSM_List[index].RequestTimer = Device_APDU_Timeout(); + /* update for the next call or check */ + current_invokeID++; + // skip zero - we treat that internally as invalid or no free + if (current_invokeID == 0) + current_invokeID = 1; + } } - /* update for the next call or check */ - current_invokeID++; - // skip zero - we treat that internally as invalid or no free - if (current_invokeID == 0) - current_invokeID++; } - + return invokeID; } @@ -166,7 +195,7 @@ void tsm_set_confirmed_unsegmented_transaction( } } - return; + return; } // used to retrieve the transaction payload @@ -200,15 +229,15 @@ bool tsm_get_transaction_pdu( } } - return found; + return found; } -// called once a millisecond +/* called once a millisecond or slower */ void tsm_timer_milliseconds(uint16_t milliseconds) { unsigned i = 0; // counter int bytes_sent = 0; - + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { if (TSM_List[i].state == TSM_STATE_AWAIT_CONFIRMATION) @@ -217,7 +246,7 @@ void tsm_timer_milliseconds(uint16_t milliseconds) TSM_List[i].RequestTimer -= milliseconds; else TSM_List[i].RequestTimer = 0; - // timeout. retry? + /* timeout. retry? */ if (TSM_List[i].RequestTimer == 0) { TSM_List[i].RetryCount--; @@ -225,12 +254,15 @@ void tsm_timer_milliseconds(uint16_t milliseconds) if (TSM_List[i].RetryCount) { bytes_sent = datalink_send_pdu( - &TSM_List[i].dest, // destination address + &TSM_List[i].dest, /* destination address */ &TSM_List[i].pdu[0], - TSM_List[i].pdu_len); // number of bytes of data + TSM_List[i].pdu_len); /* number of bytes of data */ } else + { + TSM_List[i].InvokeID = 0; TSM_List[i].state = TSM_STATE_IDLE; + } } } } @@ -239,10 +271,26 @@ void tsm_timer_milliseconds(uint16_t milliseconds) void tsm_free_invoke_id(uint8_t invokeID) { uint8_t index; - + index = tsm_find_invokeID_index(invokeID); if (index < MAX_TSM_TRANSACTIONS) + { TSM_List[index].state = TSM_STATE_IDLE; + TSM_List[index].InvokeID = 0; + } +} + +/* see if the invoke ID has been made free */ +bool tsm_invoke_id_free(uint8_t invokeID) +{ + bool status = true; + uint8_t index; + + index = tsm_find_invokeID_index(invokeID); + if (index < MAX_TSM_TRANSACTIONS) + status = false; + + return status; } #ifdef TEST diff --git a/bacnet-stack/tsm.h b/bacnet-stack/tsm.h index cccc43e9..59620829 100644 --- a/bacnet-stack/tsm.h +++ b/bacnet-stack/tsm.h @@ -108,6 +108,8 @@ bool tsm_get_transaction_pdu( uint8_t *pdu, uint16_t *pdu_len); +bool tsm_invoke_id_free(uint8_t invokeID); + #ifdef __cplusplus } #endif /* __cplusplus */