diff --git a/bacnet-stack/Makefile b/bacnet-stack/Makefile index 21fb6c12..08ca2692 100644 --- a/bacnet-stack/Makefile +++ b/bacnet-stack/Makefile @@ -19,6 +19,8 @@ SRCS = ports/linux/main.c \ iam.c \ rp.c \ wp.c \ + tsm.c \ + address.c \ device.c \ ai.c \ ao.c \ diff --git a/bacnet-stack/apdu.c b/bacnet-stack/apdu.c index e447c95c..2b9b4cdc 100644 --- a/bacnet-stack/apdu.c +++ b/bacnet-stack/apdu.c @@ -39,6 +39,7 @@ #include "bacdef.h" #include "bacdcode.h" #include "bacenum.h" +#include "tsm.h" // Confirmed Function Handlers // If they are not set, they are handled by a reject message @@ -244,9 +245,7 @@ void apdu_handler( invoke_id); } else - { - //FIXME: release the invoke id - } + tsm_free_invoke_id(invoke_id); break; default: break; @@ -297,9 +296,7 @@ void apdu_handler( &service_ack_data); } else - { - //FIXME: release the invoke id - } + tsm_free_invoke_id(invoke_id); break; default: break; @@ -310,7 +307,7 @@ void apdu_handler( case PDU_TYPE_REJECT: case PDU_TYPE_ABORT: invoke_id = apdu[1]; - // FIXME: release the invoke id + tsm_free_invoke_id(invoke_id); break; default: break; diff --git a/bacnet-stack/config.h b/bacnet-stack/config.h index 02d4c6b9..185b0a09 100644 --- a/bacnet-stack/config.h +++ b/bacnet-stack/config.h @@ -22,4 +22,11 @@ // requests available. #define MAX_TSM_TRANSACTIONS 16 +// The address cache is used for binding to BACnet devices +// The number of entries corresponds to the number of +// devices that might respond to an I-Am on the network. +// If your device is a simple server and does not need to bind, +// then you don't need to use this. +#define MAX_ADDRESS_CACHE 255 + #endif diff --git a/bacnet-stack/handlers.c b/bacnet-stack/handlers.c index 1dfe83e3..f5a4e076 100644 --- a/bacnet-stack/handlers.c +++ b/bacnet-stack/handlers.c @@ -42,6 +42,8 @@ #include "reject.h" #include "abort.h" #include "bacerror.h" +#include "address.h" +#include "tsm.h" // Example handlers of services @@ -70,6 +72,8 @@ // flag to send an I-Am bool I_Am_Request = true; +// flag to send a global Who-Is +bool Who_Is_Request = true; // buffers used for transmit and receive static uint8_t Tx_Buf[MAX_MPDU] = {0}; @@ -150,6 +154,110 @@ void Send_IAm(void) fprintf(stderr,"Failed to Send I-Am Request (%s)!\n", strerror(errno)); } +void Send_WhoIs(void) +{ + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + + // Who-Is is a global broadcast + bacdl_get_broadcast_address(&dest); + + // encode the NPDU portion of the packet + pdu_len = npdu_encode_apdu( + &Tx_Buf[0], + &dest, + NULL, + false, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + + // encode the APDU portion of the packet + pdu_len += whois_encode_apdu( + &Tx_Buf[pdu_len], + -1, // send to all + -1);// send to all + + bytes_sent = bacdl_send_pdu( + &dest, // destination address + &Tx_Buf[0], + pdu_len); // number of bytes of data + if (bytes_sent > 0) + fprintf(stderr,"Sent Who-Is Request!\n"); + else + fprintf(stderr,"Failed to Send Who-Is Request (%s)!\n", strerror(errno)); +} + +// returns false if device is not bound +bool Send_Read_Property_Request( + uint32_t device_id, // destination device + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + int32_t array_index) +{ + 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; + + status = address_get_by_device(device_id, &max_apdu, &dest); + if (status) + { + bacdl_get_my_address(&my_address); + pdu_len = npdu_encode_apdu( + &Tx_Buf[0], + &dest, + &my_address, + true, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + + invoke_id = tsm_next_free_invokeID(); + pdu_len += rp_encode_apdu( + &Tx_Buf[pdu_len], + invoke_id, + object_type, + object_instance, + object_property, + array_index); + if (pdu_len < max_apdu) + { + tsm_set_confirmed_unsegmented_transaction( + invoke_id, + &dest, + &Tx_Buf[0], + pdu_len); + bytes_sent = bacdl_send_pdu( + &dest, // destination address + &Tx_Buf[0], + pdu_len); // number of bytes of data + if (bytes_sent > 0) + fprintf(stderr,"Sent ReadProperty Request!\n"); + else + fprintf(stderr,"Failed to Send ReadProperty Request (%s)!\n", + strerror(errno)); + } + else + fprintf(stderr,"Failed to Send ReadProperty Request " + "(exceeds destination maximum APDU)!\n"); + } + + return status; +} + +int handler_send_pdu( + BACNET_ADDRESS *dest, // destination address + uint8_t *pdu, // any data to be sent - may be null + unsigned pdu_len) // number of bytes of data +{ + return bacdl_send_pdu( + dest, + pdu, + pdu_len); +} + void WhoIsHandler( uint8_t *service_request, uint16_t service_len, @@ -206,6 +314,29 @@ void IAmHandler( return; } +void ReadPropertyAckHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) +{ + int len = 0; + BACNET_READ_PROPERTY_DATA data; + + tsm_free_invoke_id(service_data->invoke_id); + len = rp_ack_decode_service_request( + service_request, + service_len, + &data); + fprintf(stderr,"Received Read-Property Ack!\n"); + if (len > 0) + fprintf(stderr,"type=%u instance=%u property=%u index=%d\n", + data.object_type, + data.object_instance, + data.object_property, + data.array_index); +} + void ReadPropertyHandler( uint8_t *service_request, uint16_t service_len, diff --git a/bacnet-stack/handlers.h b/bacnet-stack/handlers.h index 2bc98e77..d53f0882 100644 --- a/bacnet-stack/handlers.h +++ b/bacnet-stack/handlers.h @@ -33,6 +33,13 @@ // flag to send an I-Am extern bool I_Am_Request; +// flag to send a global Who-Is +extern bool Who_Is_Request; + +int handler_send_pdu( + BACNET_ADDRESS *dest, // destination address + uint8_t *pdu, // any data to be sent - may be null + unsigned pdu_len); // number of bytes of data void UnrecognizedServiceHandler( uint8_t *service_request, @@ -41,6 +48,7 @@ void UnrecognizedServiceHandler( BACNET_CONFIRMED_SERVICE_DATA *service_data); void Send_IAm(void); +void Send_WhoIs(void); void WhoIsHandler( uint8_t *service_request, @@ -52,6 +60,12 @@ void IAmHandler( uint16_t service_len, BACNET_ADDRESS *src); +void ReadPropertyAckHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data); + void ReadPropertyHandler( uint8_t *service_request, uint16_t service_len, @@ -64,4 +78,12 @@ void WritePropertyHandler( BACNET_ADDRESS *src, BACNET_CONFIRMED_SERVICE_DATA *service_data); +// returns false if device is not bound +bool Send_Read_Property_Request( + uint32_t device_id, // destination device + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + int32_t array_index); + #endif diff --git a/bacnet-stack/ports/linux/main.c b/bacnet-stack/ports/linux/main.c index 99451ca6..2c300ff9 100644 --- a/bacnet-stack/ports/linux/main.c +++ b/bacnet-stack/ports/linux/main.c @@ -28,12 +28,16 @@ #include #include #include +#include #include "config.h" +#include "address.h" #include "bacdef.h" #include "handlers.h" #include "bacdcode.h" #include "npdu.h" #include "apdu.h" +#include "iam.h" +#include "tsm.h" #include "device.h" #ifdef BACDL_ETHERNET #include "ethernet.h" @@ -133,6 +137,46 @@ static void Init_Device_Parameters(void) return; } + +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); + fprintf(stderr,"Received I-Am Request"); + if (len != -1) + { + fprintf(stderr," from %u!\n",device_id); + address_add(device_id, + max_apdu, + src); + (void)Send_Read_Property_Request( + device_id, // destination device + OBJECT_DEVICE, + device_id, + PROP_OBJECT_NAME, + BACNET_ARRAY_ALL); + } + else + fprintf(stderr,"!\n"); + + return; +} + static void Init_Service_Handlers(void) { @@ -142,7 +186,7 @@ static void Init_Service_Handlers(void) WhoIsHandler); apdu_set_unconfirmed_handler( SERVICE_UNCONFIRMED_I_AM, - IAmHandler); + LocalIAmHandler); // set the handler for all the services we don't implement // It is required to send the proper reject message... @@ -155,6 +199,34 @@ static void Init_Service_Handlers(void) apdu_set_confirmed_handler( SERVICE_CONFIRMED_WRITE_PROPERTY, WritePropertyHandler); + // handle the data coming back from confirmed requests + apdu_set_confirmed_ack_handler( + SERVICE_CONFIRMED_READ_PROPERTY, + ReadPropertyAckHandler); +} + +static void print_address_cache(void) +{ + unsigned i,j; + BACNET_ADDRESS address; + uint32_t device_id = 0; + unsigned max_apdu = 0; + + fprintf(stderr,"Device\tMAC\tMaxAPDU\tNet\n"); + for (i = 0; i < MAX_ADDRESS_CACHE; i++) + { + if (address_get_by_index(i,&device_id, &max_apdu, &address)) + { + fprintf(stderr,"%u\t",device_id); + for (j = 0; j < address.mac_len; j++) + { + fprintf(stderr,"%02X",address.mac[j]); + } + fprintf(stderr,"\t"); + fprintf(stderr,"%hu\t",max_apdu); + fprintf(stderr,"%hu\n",address.net); + } + } } static void sig_handler(int signo) @@ -166,6 +238,8 @@ static void sig_handler(int signo) bip_cleanup(); #endif + print_address_cache(); + exit(0); } @@ -174,7 +248,10 @@ int main(int argc, char *argv[]) BACNET_ADDRESS src = {0}; // address where message came from uint16_t pdu_len = 0; unsigned timeout = 100; // milliseconds - + time_t start_time; + time_t new_time = 0; + + start_time = time(NULL); /* get current time */ // Linux specials signal(SIGINT, sig_handler); signal(SIGHUP, sig_handler); @@ -193,12 +270,11 @@ int main(int argc, char *argv[]) return 1; #endif - // loop forever for (;;) { // input - + new_time = time(NULL); // returns 0 bytes on timeout #ifdef BACDL_ETHERNET pdu_len = ethernet_receive( @@ -223,10 +299,19 @@ int main(int argc, char *argv[]) &Rx_Buf[0], pdu_len); } + if (new_time > start_time) + { + tsm_timer_milliseconds(new_time - start_time * 1000); + start_time = new_time; + } if (I_Am_Request) { I_Am_Request = false; Send_IAm(); + } else if (Who_Is_Request) + { + Who_Is_Request = false; + Send_WhoIs(); } // output diff --git a/bacnet-stack/test.sh b/bacnet-stack/test.sh index a1449dae..c3ac28bb 100755 --- a/bacnet-stack/test.sh +++ b/bacnet-stack/test.sh @@ -64,3 +64,15 @@ make -f ai.mak ./analog_input >> test.log make -f ai.mak clean +make -f wp.mak clean +make -f wp.mak +./writeproperty >> test.log +make -f wp.mak clean + +make -f address.mak clean +make -f address.mak +./address >> test.log +make -f address.mak clean + + + diff --git a/bacnet-stack/tsm.c b/bacnet-stack/tsm.c index 7d7f545d..ad14a2f9 100644 --- a/bacnet-stack/tsm.c +++ b/bacnet-stack/tsm.c @@ -44,6 +44,8 @@ #include "tsm.h" #include "config.h" #include "device.h" +#include "handlers.h" +#include "address.h" // Transaction State Machine // Really only needed for segmented messages @@ -135,40 +137,79 @@ uint8_t tsm_next_free_invokeID(void) } // returns 0 if there are no free transactions -uint8_t tsm_request_confirmed_unsegmented_transaction( +void tsm_set_confirmed_unsegmented_transaction( + uint8_t invokeID, BACNET_ADDRESS *dest, uint8_t *pdu, uint16_t pdu_len) { - uint8_t invokeID = 0; unsigned i = 0, j = 0; + uint8_t index; + - // see if there is a free transaction - for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) + if (invokeID) { - if (TSM_List[i].state == TSM_STATE_IDLE) + index = tsm_find_invokeID_index(invokeID); + if (index < MAX_TSM_TRANSACTIONS) { - // see if the current invoke id is free - invokeID = tsm_next_free_invokeID(); - if (invokeID) + // assign the transaction + TSM_List[index].state = TSM_STATE_AWAIT_CONFIRMATION; + TSM_List[index].RetryCount = Device_Number_Of_APDU_Retries(); + // start the timer + TSM_List[index].RequestTimer = Device_APDU_Timeout(); + // copy the data + for (j = 0; j < pdu_len; j++) { - // assign the transaction - TSM_List[i].state = TSM_STATE_AWAIT_CONFIRMATION; - TSM_List[i].RetryCount = Device_Number_Of_APDU_Retries(); - // start the timer - TSM_List[i].RequestTimer = Device_APDU_Timeout(); - // copy the data - for (j = 0; j < pdu_len; j++) - { - TSM_List[i].pdu[j] = pdu[j]; - } - memmove(&TSM_List[i].dest,dest,sizeof(TSM_List[i].dest)); + TSM_List[index].pdu[j] = pdu[j]; } - break; + TSM_List[index].pdu_len = pdu_len; + address_copy(&TSM_List[i].dest,dest); } } - return invokeID; + return; +} + +// called once a millisecond +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) + { + if (TSM_List[i].RequestTimer > milliseconds) + TSM_List[i].RequestTimer -= milliseconds; + else + TSM_List[i].RequestTimer = 0; + } + // timeout. retry? + if (TSM_List[i].RequestTimer == 0) + { + TSM_List[i].RetryCount--; + TSM_List[i].RequestTimer = Device_APDU_Timeout(); + if (TSM_List[i].RetryCount) + { + bytes_sent = handler_send_pdu( + &TSM_List[i].dest, // destination address + &TSM_List[i].pdu[0], + TSM_List[i].pdu_len); // number of bytes of data + } + else + TSM_List[i].state = TSM_STATE_IDLE; + } + } +} + +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; } #ifdef TEST diff --git a/bacnet-stack/tsm.h b/bacnet-stack/tsm.h index fdad232a..07ad07d0 100644 --- a/bacnet-stack/tsm.h +++ b/bacnet-stack/tsm.h @@ -71,7 +71,8 @@ typedef struct BACnet_TSM_Data // used to perform timeout on PDU segments //uint8_t SegmentTimer; // used to perform timeout on Confirmed Requests - uint8_t RequestTimer; + // in milliseconds + uint16_t RequestTimer; // unique id uint8_t InvokeID; // state that the TSM is in @@ -80,17 +81,22 @@ typedef struct BACnet_TSM_Data BACNET_ADDRESS dest; // copy of the PDU, should we need to send it again uint8_t pdu[MAX_PDU]; + unsigned pdu_len; } BACNET_TSM_DATA; bool tsm_transaction_available(void); uint8_t tsm_transaction_idle_count(void); -uint8_t tsm_request_confirmed_unsegmented_transaction( +void tsm_timer_milliseconds(uint16_t milliseconds); +// free the invoke ID when the reply comes back +void tsm_free_invoke_id(uint8_t invokeID); +// use these in tandem +uint8_t tsm_next_free_invokeID(void); +// returns the same invoke ID that was given +void tsm_set_confirmed_unsegmented_transaction( + uint8_t invokeID, BACNET_ADDRESS *dest, uint8_t *pdu, uint16_t pdu_len); -void tsm_init_list(BACNET_TSM_DATA *list); - - #endif