diff --git a/bacnet-stack/demo/epics/main.c b/bacnet-stack/demo/epics/main.c index 450907fa..2489ec51 100644 --- a/bacnet-stack/demo/epics/main.c +++ b/bacnet-stack/demo/epics/main.c @@ -30,6 +30,7 @@ #include #include /* for time */ #include +#include #include "bactext.h" #include "iam.h" #include "arf.h" @@ -51,6 +52,7 @@ #include "txbuf.h" #include "dlenv.h" #include "keylist.h" +#include "bacepics.h" /* buffer used for receive */ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; @@ -60,28 +62,52 @@ static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; static BACNET_ADDRESS Target_Address; /* any errors are picked up in main loop */ static bool Error_Detected = false; -/* any valid data returned is put here */ -typedef struct BACnet_RP_Service_Data_t { +static EPICS_STATES myState = INITIAL_BINDING; + +/* any valid RP or RPM data returned is put here */ +/* Now using one structure for both RP and RPM data: + * typedef struct BACnet_RP_Service_Data_t { + * bool new_data; + * BACNET_CONFIRMED_SERVICE_ACK_DATA service_data; + * BACNET_READ_PROPERTY_DATA data; + * } BACNET_RP_SERVICE_DATA; + * static BACNET_RP_SERVICE_DATA Read_Property_Data; + */ + +typedef struct BACnet_RPM_Service_Data_t { bool new_data; BACNET_CONFIRMED_SERVICE_ACK_DATA service_data; - BACNET_READ_PROPERTY_DATA data; -} BACNET_RP_SERVICE_DATA; -static BACNET_RP_SERVICE_DATA Read_Property_Data; + BACNET_READ_ACCESS_DATA *rpm_data; +} BACNET_RPM_SERVICE_DATA; +static BACNET_RPM_SERVICE_DATA Read_Property_Multiple_Data; + /* We get the length of the object list, and then get the objects one at a time */ static uint32_t Object_List_Length = 0; -static uint32_t Object_List_Index = 0; +static int32_t Object_List_Index = 0; /* object that we are currently printing */ static OS_Keylist Object_List; -static BACNET_OBJECT_ID Object_List_Element; + +/* When we need to process an Object's properties one at a time, + * then we build and use this list */ +#define MAX_PROPS 100 /* Supersized so it always is big enough. */ +static uint32_t Property_List_Length = 0; +static uint32_t Property_List_Index = 0; +static int32_t Property_List[MAX_PROPS + 2]; +/* This normally points to Property_List. */ +static const int *pPropList = NULL; + +/* When we have to walk through an array of things, like ObjectIDs or + * Subordinate_Annotations, one RP call at a time, use these for indexing. */ +static uint32_t Walked_List_Length = 0; +static uint32_t Walked_List_Index = 0; +static bool Using_Walked_List = false; + #if !defined(PRINT_ERRORS) #define PRINT_ERRORS 1 #endif -/* FIXME: keep the object list in here */ -/* static OS_Keylist Object_List; */ - static void MyErrorHandler( BACNET_ADDRESS * src, uint8_t invoke_id, @@ -112,7 +138,9 @@ void MyAbortHandler( (void) invoke_id; (void) server; #if PRINT_ERRORS - printf("BACnet Abort: %s\r\n", bactext_abort_reason_name(abort_reason)); + /* It is normal for this to fail, so don't print. */ + if ( myState != GET_ALL_RESPONSE ) + printf("BACnet Abort: %s\r\n", bactext_abort_reason_name(abort_reason)); #else (void) abort_reason; #endif @@ -135,7 +163,100 @@ void MyRejectHandler( Error_Detected = true; } -/** Provide a nicer output for Supported Services and Object Types. +void MyReadPropertyAckHandler( + uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data) +{ + int len = 0; + BACNET_READ_ACCESS_DATA *rp_data; + + (void) src; + rp_data = calloc(1, sizeof(BACNET_READ_ACCESS_DATA)); + if (rp_data) { + len = + rp_ack_fully_decode_service_request(service_request, service_len, + rp_data); + } + if (len > 0) { + memmove(&Read_Property_Multiple_Data.service_data, service_data, + sizeof(BACNET_CONFIRMED_SERVICE_ACK_DATA)); + Read_Property_Multiple_Data.rpm_data = rp_data; + Read_Property_Multiple_Data.new_data = true; + } + else + { + if ( len < 0 ) /* Eg, failed due to no segmentation */ + Error_Detected = true; + free( rp_data ); + } +} + +void MyReadPropertyMultipleAckHandler( + uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data) +{ + int len = 0; + BACNET_READ_ACCESS_DATA *rpm_data; + + (void) src; + rpm_data = calloc(1, sizeof(BACNET_READ_ACCESS_DATA)); + if (rpm_data) { + len = + rpm_ack_decode_service_request(service_request, service_len, + rpm_data); + } + if (len > 0) { + memmove(&Read_Property_Multiple_Data.service_data, service_data, + sizeof(BACNET_CONFIRMED_SERVICE_ACK_DATA)); + Read_Property_Multiple_Data.rpm_data = rpm_data; + Read_Property_Multiple_Data.new_data = true; + // Will process and free the RPM data later + } + else + { + if ( len < 0 ) /* Eg, failed due to no segmentation */ + Error_Detected = true; + free( rpm_data ); + } + } + +static void Init_Service_Handlers( + void) +{ + Device_Init(); + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + /* set the handler for all the services we don't implement + It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler + (handler_unrecognized_service); + /* we must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, + MyReadPropertyAckHandler); + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, + MyReadPropertyMultipleAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + + +/** Provide a nicer output for Supported Services and Object Types bitfields. + * We have to override the library's normal bitfield print because the + * EPICS format wants just T and F, and we want to provide (as comments) + * the names of the active types. + * We also limit the output to 4 bit fields per line. * * @param stream [in] Normally stdout * @param value [in] The structure holding this property's value (union) and type. @@ -160,7 +281,7 @@ void MyRejectHandler( for (i = 0; i < len; i++) { fprintf(stream, "%s", bitstring_bit(&value->type.Bit_String, - (uint8_t) i) ? " true" : "false"); + (uint8_t) i) ? "T" : "F"); if (i < len - 1) fprintf(stream, ","); else @@ -168,7 +289,7 @@ void MyRejectHandler( // Tried with 8 per line, but with the comments, got way too long. if ( (i == (len-1) ) || ( (i % 4) == 3 ) ) // line break every 4 { - fprintf(stream, " # "); + fprintf(stream, " -- "); // EPICS comments begin with "--" // Now rerun the same 4 bits, but print labels for true ones for ( j = i - (i%4); j <= i; j++) { @@ -202,7 +323,7 @@ void MyRejectHandler( fprintf(stream, "\r\n "); } } - fprintf(stream, "}"); + fprintf(stream, "} \r\n"); } else { @@ -215,18 +336,34 @@ void MyRejectHandler( } +/** Print out the value(s) for one Property. + * This function may be called repeatedly for one property if we are walking + * through a list (Using_Walked_List is True) to show just one value of the + * array per call. + * + * @param rpm_property [in] Points to structure holding the Property, + * Value, and Error information. + */ void PrintReadPropertyData( - BACNET_READ_PROPERTY_DATA * data) + BACNET_PROPERTY_REFERENCE *rpm_property) { - BACNET_APPLICATION_DATA_VALUE value; /* for decode value data */ - int len = 0; - uint8_t *application_data; - int application_data_len; - bool first_value = true; + BACNET_APPLICATION_DATA_VALUE *value, *old_value; bool print_brace = false; KEY object_list_element; - if (data) { + if (rpm_property == NULL ) + { + fprintf( stdout, " -- Null Property data \r\n" ); + return; + } + value = rpm_property->value; + if ( value == NULL ) + { + /* Then we print the error information */ + fprintf(stdout, "\r\n"); + return; + } + #if 0 if (data->array_index == BACNET_ARRAY_ALL) fprintf(stderr, "%s #%u %s\n", @@ -240,164 +377,269 @@ void PrintReadPropertyData( bactext_property_name(data->object_property), data->array_index); #endif - application_data = data->application_data; - application_data_len = data->application_data_len; - /* value? loop until all of the len is gone... */ - for (;;) { - len = - bacapp_decode_application_data(application_data, - (uint8_t) application_data_len, &value); - if (first_value && (len < application_data_len)) { - first_value = false; - fprintf(stdout, "{"); - print_brace = true; - } - /* Grab the value of the Device Object List length - don't print it! */ - if (data->object_property == PROP_OBJECT_LIST) { - if ((data->array_index == 0) && - (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT)) { - Object_List_Length = value.type.Unsigned_Int; - fprintf(stdout, "{"); - } else { - if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { - /* FIXME: store the object list so we can interrogate - each object. */ - object_list_element = - KEY_ENCODE(value.type.Object_Id.type, - value.type.Object_Id.instance); + if( ( value != NULL ) && ( value->next != NULL ) ) + { + /* Then this is an array of values; open brace */ + fprintf(stdout, "{ "); + print_brace = true; /* remember to close it */ + } + + if ( !Using_Walked_List ) + Walked_List_Index = Walked_List_Length = 0; /* In case we need this. */ + /* value(s) loop until there is no "next" ... */ + while ( value != NULL ) + { + switch( rpm_property->propertyIdentifier ) + { + case PROP_OBJECT_LIST: + case PROP_STRUCTURED_OBJECT_LIST: + case PROP_SUBORDINATE_LIST: + if ( Using_Walked_List ) + { + if ( (rpm_property->propertyArrayIndex == 0) && + (value->tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) ) + { + /* Grab the value of the Object List length - don't print it! */ + Walked_List_Length = value->type.Unsigned_Int; + if ( rpm_property->propertyIdentifier == PROP_OBJECT_LIST) + Object_List_Length = value->type.Unsigned_Int; + break; + } + else + assert( Walked_List_Index == rpm_property->propertyArrayIndex); + } + else + Walked_List_Index++; + if ( Walked_List_Index == 1 ) + { + /* Open the list of Objects (opening brace may be already printed) */ + if( value->next == NULL ) + fprintf(stdout, "{ \r\n "); + else + fprintf(stdout, "\r\n "); + } + if ( value->tag != BACNET_APPLICATION_TAG_OBJECT_ID ) { + assert( false ); /* Something not right here */ + break; + } + else if ( rpm_property->propertyIdentifier == PROP_OBJECT_LIST) + { + /* Store the object list so we can interrogate + each object. */ + object_list_element = + KEY_ENCODE(value->type.Object_Id.type, + value->type.Object_Id.instance); + /* We don't have anything to put in the data pointer + * yet, so just leave it null. The key is Key here. */ + Keylist_Data_Add( Object_List, object_list_element, NULL ); + } + else if ( rpm_property->propertyIdentifier == PROP_SUBORDINATE_LIST) + { + /* TODO: handle Sequence of { Device ObjID, Object ID }, */ + } + bacapp_print_value(stdout, value, rpm_property->propertyIdentifier ); + if ( ( Walked_List_Index < Walked_List_Length ) || + ( value->next != NULL ) ) + { + /* There are more. */ + fprintf(stdout, ","); + if (!(Walked_List_Index % 4)) + fprintf(stdout, "\r\n "); + } else { + fprintf(stdout, " } \r\n"); + } + break; + + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + case PROP_PROTOCOL_SERVICES_SUPPORTED: + PrettyPrintPropertyValue(stdout, value, rpm_property->propertyIdentifier); + break; + + default: + bacapp_print_value(stdout, value, rpm_property->propertyIdentifier); + if ( value->next != NULL ) { + /* there's more! */ + fprintf(stdout, ","); + } + else + { + if (print_brace) { + /* Closing brace for this multi-valued array */ + fprintf(stdout, " }"); + } + fprintf(stdout, "\r\n"); + } + break; + } + + old_value = value; + value = value->next; /* next or NULL */ + free( old_value ); + } /* End while loop */ - } - bacapp_print_value(stdout, &value, data->object_property); - if (Object_List_Index <= Object_List_Length) { - fprintf(stdout, ","); - if (!(Object_List_Index % 4)) { - fprintf(stdout, "\r\n "); - } - } else { - fprintf(stdout, "}"); - fprintf(stdout, "\r\n"); - } - } - } - else if ( (data->object_property == PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED) || - (data->object_property == PROP_PROTOCOL_SERVICES_SUPPORTED) ) - { - PrettyPrintPropertyValue(stdout, &value, data->object_property); - } - else { - bacapp_print_value(stdout, &value, data->object_property); - } - if (len) { - if (len < application_data_len) { - application_data += len; - application_data_len -= len; - /* there's more! */ - fprintf(stdout, ","); - } else - break; - } else - break; - } - if (print_brace) - fprintf(stdout, "}"); - if (data->object_property != PROP_OBJECT_LIST) { - fprintf(stdout, "\r\n"); - } - } -} - -void MyReadPropertyAckHandler( - 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; - - (void) src; - len = rp_ack_decode_service_request(service_request, service_len, &data); - if (len > 0) { - memmove(&Read_Property_Data.service_data, service_data, sizeof(data)); - memmove(&Read_Property_Data.data, &data, sizeof(data)); - Read_Property_Data.new_data = true; - } -} - -static void Init_Service_Handlers( - void) -{ - Device_Init(); - /* we need to handle who-is - to support dynamic device binding to us */ - apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); - /* handle i-am to support binding to other devices */ - apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); - /* set the handler for all the services we don't implement - It is required to send the proper reject message... */ - apdu_set_unrecognized_service_handler_handler - (handler_unrecognized_service); - /* we must implement read property - it's required! */ - apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, - handler_read_property); - /* handle the data coming back from confirmed requests */ - apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, - MyReadPropertyAckHandler); - /* handle any errors coming back */ - apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); - apdu_set_abort_handler(MyAbortHandler); - apdu_set_reject_handler(MyRejectHandler); } +/** Send an RP request to read one property from the current Object. + * Singly process large arrays too, like the Device Object's Object_List. + * If GET_LIST_OF_ALL_RESPONSE failed, we will fall back to using just + * the list of known Required properties for this type of object. + * + * @param device_instance [in] Our target device's instance. + * @param pMyObject [in] The current Object's type and instance numbers. + * @return The invokeID of the message sent, or 0 if reached the end + * of the property list. + */ static uint8_t Read_Properties( - uint32_t device_instance) + uint32_t device_instance, + BACNET_OBJECT_ID *pMyObject ) { uint8_t invoke_id = 0; - static unsigned index = 0; - /* note: you could just loop through - all the properties in all the objects. */ - static const int *pRequired = NULL; + struct special_property_list_t PropertyListStruct; - if (!pRequired) { - Device_Property_Lists(&pRequired, NULL, NULL); + if ( Property_List_Length == 0 ) + { + /* If we failed to get the Properties with RPM, just settle for what we + * know is the fixed list of Required (only) properties. + * In practice, this should only happen for simple devices that don't + * implement RPM or have really limited MAX_APDU size. + */ + Device_Objects_Property_List( pMyObject->type, &PropertyListStruct); + pPropList = PropertyListStruct.Required.pList; + if ( pPropList != NULL ) + { + Property_List_Length = PropertyListStruct.Required.count; + } + else + { + fprintf( stdout, " -- No Properties available for %s \r\n", + bactext_object_type_name( pMyObject->type ) ); + } } + else + pPropList = Property_List; - if (pRequired[index] != -1) { - if (pRequired[index] == PROP_OBJECT_LIST) { - if (Object_List_Length == 0) { - printf(" %s: ", bactext_property_name(pRequired[index])); + if ( (pPropList != NULL ) && ( pPropList[Property_List_Index] != -1) ) + { + int prop = pPropList[Property_List_Index]; + if ( Using_Walked_List ) + { + if (Walked_List_Length == 0) { + printf(" %s: ", bactext_property_name( prop ) ); invoke_id = - Send_Read_Property_Request(device_instance, OBJECT_DEVICE, - device_instance, PROP_OBJECT_LIST, 0); - if (invoke_id) { - Object_List_Index = 1; - } + Send_Read_Property_Request(device_instance, + pMyObject->type, pMyObject->instance, + prop, 0); } else { invoke_id = - Send_Read_Property_Request(device_instance, OBJECT_DEVICE, - device_instance, PROP_OBJECT_LIST, Object_List_Index); - if (invoke_id) { - Object_List_Index++; - if (Object_List_Index > Object_List_Length) { - /* go on to next property */ - index++; - } - } + Send_Read_Property_Request(device_instance, + pMyObject->type, pMyObject->instance, + prop, Walked_List_Index); } } else { - printf(" %s: ", bactext_property_name(pRequired[index])); + printf(" %s: ", bactext_property_name( prop ) ); invoke_id = - Send_Read_Property_Request(device_instance, OBJECT_DEVICE, - device_instance, pRequired[index], BACNET_ARRAY_ALL); - if (invoke_id) { - index++; - } + Send_Read_Property_Request(device_instance, + pMyObject->type, pMyObject->instance, + prop, BACNET_ARRAY_ALL); } } return invoke_id; } +/** Process the RPM list, either printing out on success or building a + * properties list for later use. + * Also need to free the data in the list. + * @param rpm_data [in] The list of RPM data received. + * @param myState [in] The current state. + * @return The next state of the EPICS state machine, normally NEXT_OBJECT + * if the RPM got good data, or GET_PROPERTY_REQUEST if we have to + * singly process the list of Properties. + */ +EPICS_STATES ProcessRPMData( + BACNET_READ_ACCESS_DATA * rpm_data, + EPICS_STATES myState ) +{ + BACNET_READ_ACCESS_DATA *old_rpm_data; + BACNET_PROPERTY_REFERENCE *rpm_property; + BACNET_PROPERTY_REFERENCE *old_rpm_property; + BACNET_APPLICATION_DATA_VALUE *value; + BACNET_APPLICATION_DATA_VALUE *old_value; + bool bSuccess = true; + EPICS_STATES nextState = myState; // assume no change + /* Some flags to keep the output "pretty" - object lists at the end */ + bool bHasObjectList = false; + bool bHasStructuredViewList = false; + + while (rpm_data) + { + rpm_property = rpm_data->listOfProperties; + while (rpm_property) { + /* For the GET_LIST_OF_ALL_RESPONSE case, + * just keep what property this was */ + if ( myState == GET_LIST_OF_ALL_RESPONSE ) + { + switch ( rpm_property->propertyIdentifier ) + { + case PROP_OBJECT_LIST: + bHasObjectList = true; /* Will append below */ + break; + case PROP_STRUCTURED_OBJECT_LIST: + bHasStructuredViewList = true; + break; + default: + Property_List[ Property_List_Index++ ] = + rpm_property->propertyIdentifier; + Property_List_Length++; + break; + } + /* Free up the value(s) */ + value = rpm_property->value; + while (value) { + old_value = value; + value = value->next; + free(old_value); + } + } + else + { + printf(" %s: ", bactext_property_name( + rpm_property->propertyIdentifier) ); + PrintReadPropertyData( rpm_property ); + } + old_rpm_property = rpm_property; + rpm_property = rpm_property->next; + free(old_rpm_property); + } + old_rpm_data = rpm_data; + rpm_data = rpm_data->next; + free(old_rpm_data); + } + + // Now determine the next state + if ( bSuccess && ( myState == GET_ALL_RESPONSE) ) + nextState = NEXT_OBJECT; + else if ( bSuccess ) // and GET_LIST_OF_ALL_RESPONSE + { + /* Now append the properties we waited on. */ + if ( bHasStructuredViewList ) { + Property_List[ Property_List_Index++ ] = PROP_STRUCTURED_OBJECT_LIST; + Property_List_Length++; + } + if ( bHasObjectList ) { + Property_List[ Property_List_Index++ ] = PROP_OBJECT_LIST; + Property_List_Length++; + } + /* Now insert the -1 list terminator, but don't count it. */ + Property_List[ Property_List_Index ] = -1; + assert ( Property_List_Length < MAX_PROPS ); + Property_List_Index = 0; /* Will start at top of the list */ + nextState = GET_PROPERTY_REQUEST; + } + return nextState; +} + + int main( int argc, char *argv[]) @@ -414,6 +656,11 @@ int main( time_t timeout_seconds = 0; uint8_t invoke_id = 0; bool found = false; + BACNET_OBJECT_ID myObject; + uint8_t buffer[MAX_PDU] = { 0 }; + BACNET_READ_ACCESS_DATA *rpm_object; + BACNET_PROPERTY_REFERENCE *rpm_property; + KEY nextKey; /* FIXME: handle multi homed systems - use an argument passed to the datalink_init() */ @@ -437,7 +684,7 @@ int main( Init_Service_Handlers(); dlenv_init(); /* configure the timeout values */ - last_seconds = time(NULL); + current_seconds = time(NULL); timeout_seconds = (apdu_timeout() / 1000) * apdu_retries(); /* try to bind with the device */ found = @@ -448,51 +695,252 @@ int main( Target_Device_Object_Instance); } printf("List of Objects in test device:\r\n"); - printf("{\r\n"); - /* loop forever */ - for (;;) { - /* increment timer - exit if timed out */ + /* Print Opening brace, then kick off the Device Object */ + printf("{ \r\n"); + printf(" { \r\n"); /* And opening brace for the first object */ + myObject.type = OBJECT_DEVICE; + myObject.instance = Target_Device_Object_Instance; + myState = INITIAL_BINDING; + do { + /* increment timer - will exit if timed out */ + last_seconds = current_seconds; current_seconds = time(NULL); - - /* 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); - } - /* at least one second has passed */ + /* Has at least one second passed ? */ if (current_seconds != last_seconds) { tsm_timer_milliseconds(((current_seconds - last_seconds) * 1000)); } - /* wait until the device is bound, or timeout and quit */ - found = - address_bind_request(Target_Device_Object_Instance, &max_apdu, - &Target_Address); - if (found) { - /* invoke ID is set to zero when it is not in use */ - if (invoke_id == 0) { - invoke_id = Read_Properties(Target_Device_Object_Instance); - if (invoke_id == 0) { + + // OK to proceed; see what we are up to now + switch ( myState ) + { + case INITIAL_BINDING: + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process; normally is some initial error */ + 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)); +// } + /* will wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if ( !found ) + { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + printf("\rError: APDU Timeout!\r\n"); break; } - } else if ((Read_Property_Data.new_data) && - (invoke_id == Read_Property_Data.service_data.invoke_id)) { - Read_Property_Data.new_data = false; - PrintReadPropertyData(&Read_Property_Data.data); - if (tsm_invoke_id_free(invoke_id)) { - invoke_id = 0; - } - } else if (tsm_invoke_id_free(invoke_id)) { - invoke_id = 0; - } else if (tsm_invoke_id_failed(invoke_id)) { - fprintf(stderr, "\rError: TSM Timeout!\r\n"); - tsm_free_invoke_id(invoke_id); - invoke_id = 0; - } else if (Error_Detected) { - invoke_id = 0; + // else, loop back and try again + continue; } - } else { + else + myState = GET_ALL_REQUEST; + break; + + case GET_ALL_REQUEST: + case GET_LIST_OF_ALL_REQUEST: /* differs in ArrayIndex only */ + Error_Detected = false; + Property_List_Index = Property_List_Length = 0; + /* Update times; aids single-step debugging */ + last_seconds = current_seconds; + rpm_object = calloc(1, sizeof(BACNET_READ_ACCESS_DATA)); + assert( rpm_object ); + rpm_object->object_type = myObject.type; + rpm_object->object_instance = myObject.instance; + rpm_property = calloc(1, sizeof(BACNET_PROPERTY_REFERENCE)); + rpm_object->listOfProperties = rpm_property; + assert( rpm_property ); + rpm_property->propertyIdentifier = PROP_ALL; + if ( myState == GET_LIST_OF_ALL_REQUEST ) + rpm_property->propertyArrayIndex = 0; /* Get count of arrays */ + else + rpm_property->propertyArrayIndex = -1; /* optional: None */ + invoke_id = + Send_Read_Property_Multiple_Request( buffer, MAX_PDU, + Target_Device_Object_Instance, rpm_object ); + if ( invoke_id > 0 ) + { + if ( myState == GET_LIST_OF_ALL_REQUEST ) + myState = GET_LIST_OF_ALL_RESPONSE; + else + myState = GET_ALL_RESPONSE; + } + break; + + case GET_ALL_RESPONSE: + case GET_LIST_OF_ALL_RESPONSE: + /* 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); + } + + if ((Read_Property_Multiple_Data.new_data) && + (invoke_id == Read_Property_Multiple_Data.service_data.invoke_id)) { + Read_Property_Multiple_Data.new_data = false; + myState = ProcessRPMData( Read_Property_Multiple_Data.rpm_data, + myState ); + if (tsm_invoke_id_free(invoke_id)) { + invoke_id = 0; + } + else { + assert( false ); /* How can this be? */ + invoke_id = 0; + } + } else if (tsm_invoke_id_free(invoke_id)) { + invoke_id = 0; + if (Error_Detected) /* The normal case for Device Object */ + { + /* Try again, just to get a list of properties. */ + if ( myState == GET_ALL_RESPONSE ) + myState = GET_LIST_OF_ALL_REQUEST; + /* Else it may be that RPM is not implemented. */ + else + myState = GET_PROPERTY_REQUEST; + } + else + myState = GET_ALL_REQUEST; /* Let's try again */ + } else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + invoke_id = 0; + myState = GET_ALL_REQUEST; /* Let's try again */ + } else if (Error_Detected) { + /* Don't think we'll ever actually reach this point. */ + invoke_id = 0; + myState = NEXT_OBJECT; /* Give up and move on to the next.*/ + } + break; + + /* Process the next single property in our list, + * if we couldn't GET_ALL at once above. */ + case GET_PROPERTY_REQUEST: + Error_Detected = false; + /* Update times; aids single-step debugging */ + last_seconds = current_seconds; + invoke_id = Read_Properties(Target_Device_Object_Instance, &myObject ); + if (invoke_id == 0) { + /* Reached the end of the list. */ + myState = NEXT_OBJECT; /* Move on to the next.*/ + } + else + myState = GET_PROPERTY_RESPONSE; + break; + + case GET_PROPERTY_RESPONSE: + /* 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); + } + + if ( (Read_Property_Multiple_Data.new_data) && + (invoke_id == Read_Property_Multiple_Data.service_data.invoke_id)) + { + Read_Property_Multiple_Data.new_data = false; + PrintReadPropertyData( Read_Property_Multiple_Data.rpm_data->listOfProperties ); + if (tsm_invoke_id_free(invoke_id)) { + invoke_id = 0; + } + else { + assert( false ); /* How can this be? */ + invoke_id = 0; + } + /* Advance the property (or Array List) index */ + if ( Using_Walked_List ) + { + Walked_List_Index++; + if (Walked_List_Index > Walked_List_Length) { + /* go on to next property */ + Property_List_Index++; + Using_Walked_List = false; + } + } + else + Property_List_Index++; + if ( pPropList[Property_List_Index] == PROP_OBJECT_LIST ) + { + if ( !Using_Walked_List ) /* Just switched */ + { + Using_Walked_List = true; + Walked_List_Index = Walked_List_Length = 0; + } + } + myState = GET_PROPERTY_REQUEST; /* Go fetch next Property */ + } + else if (tsm_invoke_id_free(invoke_id)) { + invoke_id = 0; + if (Error_Detected) + { + /* OK, skip this one and try the next property. */ + fprintf( stdout, " -- Failed to get %s \r\n", + bactext_property_name(pPropList[Property_List_Index]) ); + Property_List_Index++; + } + myState = GET_PROPERTY_REQUEST; + } else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + invoke_id = 0; + myState = GET_PROPERTY_REQUEST; /* Let's try again, same Property */ + } else if (Error_Detected) { + /* Don't think we'll ever actually reach this point. */ + invoke_id = 0; + myState = NEXT_OBJECT; /* Give up and move on to the next.*/ + } + break; + + case NEXT_OBJECT: + if ( myObject.type == OBJECT_DEVICE ) + { + printf(" -- Found %d Objects \r\n", Keylist_Count( Object_List ) ); + Object_List_Index = -1; /* start over (will be incr to 0) */ + } + /* Advance to the next object, as long as it's not the Device object */ + do + { + Object_List_Index++; + nextKey = Keylist_Key( Object_List, Object_List_Index ); + /* If done with all Objects, signal end of this while loop */ + if ( ( nextKey == 0 ) || ( Object_List_Index >= Object_List_Length ) ) + { + /* Closing brace for the last Object */ + printf(" } \r\n"); + myObject.type = MAX_BACNET_OBJECT_TYPE; + } + else + { + /* Closing brace for the previous Object */ + printf(" }, \r\n"); + myObject.type = KEY_DECODE_TYPE( nextKey ); + /* Opening brace for the new Object */ + printf(" { \r\n"); + } + myObject.instance = KEY_DECODE_ID( nextKey ); + myState = GET_ALL_REQUEST; + + } while ( myObject.type == OBJECT_DEVICE ); + /* Else, don't re-do the Device Object; move to the next object. */ + break; + + default: + assert( false ); // program error; fix this + break; + } + + /* Check for timeouts */ + if ( !found || ( invoke_id > 0 ) ) + { /* increment timer - exit if timed out */ elapsed_seconds += (current_seconds - last_seconds); if (elapsed_seconds > timeout_seconds) { @@ -500,10 +948,11 @@ int main( break; } } - /* keep track of time for next check */ - last_seconds = current_seconds; - } - printf("}\r\n"); + + } while ( myObject.type < MAX_BACNET_OBJECT_TYPE ); + + /* Closing brace for all Objects */ + printf("} \r\n"); return 0; }