diff --git a/bacnet-stack/demo/writefile/makefile.mak b/bacnet-stack/demo/writefile/makefile.mak new file mode 100644 index 00000000..7fb6415e --- /dev/null +++ b/bacnet-stack/demo/writefile/makefile.mak @@ -0,0 +1,141 @@ +# +# 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 = writefile +PRODUCT_EXE = $(PRODUCT).exe + +# Choose the Data Link Layer to Enable +DEFINES = -DBACDL_BIP=1 + +SRCS = writefile.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 $(OBJS) # command too long, bummer! + del *.obj + del ..\..\*.obj + del ..\..\demo\handler\*.obj + del ..\..\demo\object\*.obj + del ..\..\ports\win32\*.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/writefile/writefile.c b/bacnet-stack/demo/writefile/writefile.c new file mode 100644 index 00000000..24b21479 --- /dev/null +++ b/bacnet-stack/demo/writefile/writefile.c @@ -0,0 +1,422 @@ +/************************************************************************** +* +* 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 writes a file to a BACnet device. */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "awf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.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_Write_File_Stream( + uint32_t device_id, + uint32_t file_instance, + int fileStartPosition, + BACNET_OCTET_STRING *fileData) +{ + 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_WRITE_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; + status = octetstring_copy(&data.fileData, fileData); + if (status) + { + pdu_len += awf_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 AtomicWriteFile Request (%s)!\n", + strerror(errno)); + } + else + fprintf(stderr,"Failed to Send AtomicWriteFile Request " + "(payload [%d] exceeds destination maximum APDU [%u])!\n", + pdu_len,max_apdu); + } + else + fprintf(stderr,"Failed to Send AtomicWriteFile Request " + "(payload [%d] exceeds octet string capacity)!\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 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 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; + 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; + uint16_t my_max_apdu = 0; + FILE *pFile = NULL; + static BACNET_OCTET_STRING fileData; + size_t len = 0; + + 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_interface("eth0"); /* for linux */ + 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 20 octets max). + note: we could fail if there is a bottle neck (router) + and smaller MPDU in betweeen. */ + if (max_apdu < MAX_APDU) + my_max_apdu = max_apdu; + else + my_max_apdu = MAX_APDU; + requestedOctetCount = my_max_apdu - 20; + /* 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 */ + pFile = fopen(Local_File_Name,"rb"); + if (pFile) + { + (void)fseek(pFile, + fileStartPosition, + SEEK_SET); + len = fread(octetstring_value(&fileData), 1, + requestedOctetCount, pFile); + if (len < requestedOctetCount) + End_Of_File_Detected = true; + octetstring_truncate(&fileData,len); + fclose(pFile); + } + printf("\rSending %d bytes",(fileStartPosition+len)); + invoke_id = Send_Atomic_Write_File_Stream( + Target_Device_Object_Instance, + Target_File_Object_Instance, + fileStartPosition, + &fileData); + Current_Invoke_ID = invoke_id; + } + } + else + { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/writefile/writefile.ide b/bacnet-stack/demo/writefile/writefile.ide new file mode 100644 index 00000000..a6a3468e Binary files /dev/null and b/bacnet-stack/demo/writefile/writefile.ide differ