/************************************************************************** * * 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. * *********************************************************************/ /** @file epics/main.c Command line tool to build a list of Objects and * Properties that can be used with VTS3 EPICS files. */ #include #include #include #include #include /* for time */ #include #include #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 "net.h" #include "datalink.h" #include "whois.h" #include "rp.h" /* some demo stuff needed */ #include "filename.h" #include "handlers.h" #include "client.h" #include "txbuf.h" #include "dlenv.h" #include "keylist.h" #include "bacepics.h" /* (Doxygen note: The next two lines pull all the following Javadoc * into the BACEPICS module.) */ /** @addtogroup BACEPICS * @{ */ /* buffer used for receive */ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; /* target information converted from command line */ 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; 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_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 int32_t Object_List_Index = 0; /* object that we are currently printing */ static OS_Keylist Object_List; /* 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; /* TODO: Probably should have done this as additional EPICS_STATES */ static bool Using_Walked_List = false; /* When requesting RP for BACNET_ARRAY_ALL of what we know can be a long * array, then set this true in case it aborts and we need Using_Walked_List */ static bool IsLongArray = false; static bool ShowValues = false; /* Show value instead of '?' */ #if !defined(PRINT_ERRORS) #define PRINT_ERRORS 1 #endif static void MyErrorHandler( 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; #if PRINT_ERRORS printf("-- BACnet Error: %s: %s\r\n", bactext_error_class_name(error_class), bactext_error_code_name(error_code)); #else (void) error_class; (void) error_code; #endif Error_Detected = true; } void MyAbortHandler( BACNET_ADDRESS * src, uint8_t invoke_id, uint8_t abort_reason, bool server) { /* FIXME: verify src and invoke id */ (void) src; (void) invoke_id; (void) server; #if PRINT_ERRORS /* It is normal for this to fail, so don't print. */ if ( ( myState != GET_ALL_RESPONSE ) && !IsLongArray ) printf("-- BACnet Abort: %s ", bactext_abort_reason_name(abort_reason)); #else (void) abort_reason; #endif 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; #if PRINT_ERRORS printf("BACnet Reject: %s\r\n", bactext_reject_reason_name(reject_reason)); #else (void) reject_reason; #endif Error_Detected = true; } 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. * These bitfields use opening and closing parentheses instead of braces. * 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. * @param property [in] Which property we are printing. * @return True if success. Or otherwise. */ bool PrettyPrintPropertyValue( FILE * stream, BACNET_APPLICATION_DATA_VALUE * value, BACNET_PROPERTY_ID property) { bool status = true; /*return value */ size_t len = 0, i = 0, j = 0; if ( (value != NULL) && (value->tag == BACNET_APPLICATION_TAG_BIT_STRING) && ( (property == PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED) || (property == PROP_PROTOCOL_SERVICES_SUPPORTED) ) ) { len = bitstring_bits_used(&value->type.Bit_String); fprintf(stream, "( \r\n "); for (i = 0; i < len; i++) { fprintf(stream, "%s", bitstring_bit(&value->type.Bit_String, (uint8_t) i) ? "T" : "F"); if (i < len - 1) fprintf(stream, ","); else fprintf(stream, " "); /* 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, " -- "); // EPICS comments begin with "--" /* Now rerun the same 4 bits, but print labels for true ones */ for ( j = i - (i%4); j <= i; j++) { if ( bitstring_bit(&value->type.Bit_String, (uint8_t) j) ) { if (property == PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED) fprintf( stream, " %s,", bactext_object_type_name(j) ); /* PROP_PROTOCOL_SERVICES_SUPPORTED */ else { bool bIsConfirmed; size_t newIndex; if ( apdu_service_supported_to_index( j, &newIndex, &bIsConfirmed ) ) { if ( bIsConfirmed ) fprintf( stream, " %s,", bactext_confirmed_service_name(newIndex) ); else fprintf( stream, " %s,", bactext_unconfirmed_service_name( (newIndex) ) ); } } } else /* not supported */ fprintf( stream, "," ); } fprintf(stream, "\r\n "); } } fprintf(stream, ") \r\n"); } else if ( value != NULL ) { assert( false ); /* How did I get here? Fix your code. */ /* Meanwhile, a fallback plan */ status = bacapp_print_value(stdout, value, property); } else fprintf(stream, "? \r\n"); return status; } /** 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_PROPERTY_REFERENCE *rpm_property) { BACNET_APPLICATION_DATA_VALUE *value, *old_value; bool print_brace = false; KEY object_list_element; bool isSequence = false; /* Ie, will need bracketing braces {} */ 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, "? -- BACnet Error: %s: %s\r\n", bactext_error_class_name((int) rpm_property->error.error_class), bactext_error_code_name((int) rpm_property->error.error_code)); return; } #if 0 if (data->array_index == BACNET_ARRAY_ALL) fprintf(stderr, "%s #%u %s\n", bactext_object_type_name(data->object_type), data->object_instance, bactext_property_name(data->object_property)); else fprintf(stderr, "%s #%u %s[%d]\n", bactext_object_type_name(data->object_type), data->object_instance, bactext_property_name(data->object_property), data->array_index); #endif 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 ) { /* These are all arrays, so they open and close with braces */ case PROP_OBJECT_LIST: case PROP_STATE_TEXT: case PROP_STRUCTURED_OBJECT_LIST: case PROP_SUBORDINATE_ANNOTATIONS: 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 we got the whole Object List array in one RP call, keep * the Index and List_Length in sync as we cycle through. */ if ( rpm_property->propertyIdentifier == PROP_OBJECT_LIST) Object_List_Length = ++Object_List_Index; } if ( Walked_List_Index == 1 ) { /* Open this Array of Objects for the first entry (unless * opening brace has already printed, since this is an array * of values[] ) */ if( value->next == NULL ) fprintf(stdout, "{ \r\n "); else fprintf(stdout, "\r\n "); } if ( rpm_property->propertyIdentifier == PROP_OBJECT_LIST) { if ( value->tag != BACNET_APPLICATION_TAG_OBJECT_ID ) { assert( false ); /* Something not right here */ break; } /* 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_STATE_TEXT ) { /* Make sure it fits within 31 chars for original VTS3 limitation. * If longer, take first 15 dash, and last 15 chars. */ if ( value->type.Character_String.length > 31 ) { int iLast15idx = value->type.Character_String.length - 15; value->type.Character_String.value[15] = '-'; memcpy( &value->type.Character_String.value[16], &value->type.Character_String.value[iLast15idx], 15 ); value->type.Character_String.value[31] = 0; value->type.Character_String.length = 31; } } else if ( rpm_property->propertyIdentifier == PROP_SUBORDINATE_LIST) { if ( value->tag != BACNET_APPLICATION_TAG_OBJECT_ID ) { assert( false ); /* Something not right here */ break; } /* TODO: handle Sequence of { Device ObjID, Object ID }, */ isSequence = true; } /* If the object is a Sequence, it needs its own bracketing braces */ if ( isSequence ) fprintf(stdout, "{"); bacapp_print_value(stdout, value, rpm_property->propertyIdentifier ); if ( isSequence ) fprintf(stdout, "}"); 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: /* Some properties are presented just as '?' in an EPICS; * screen these out here, unless ShowValues is true. */ switch( rpm_property->propertyIdentifier ) { case PROP_DEVICE_ADDRESS_BINDING: case PROP_DAYLIGHT_SAVINGS_STATUS: case PROP_LOCAL_DATE: case PROP_LOCAL_TIME: case PROP_PRESENT_VALUE: case PROP_PRIORITY_ARRAY: case PROP_RELIABILITY: case PROP_UTC_OFFSET: case PROP_DATABASE_REVISION: if ( !ShowValues ) { fprintf(stdout, "?"); break; } /* Else, fall through and print value: */ default: bacapp_print_value(stdout, value, rpm_property->propertyIdentifier); break; } 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 */ } /** 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, BACNET_OBJECT_ID *pMyObject ) { uint8_t invoke_id = 0; struct special_property_list_t PropertyListStruct; 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 ( (pPropList != NULL ) && ( pPropList[Property_List_Index] != -1) ) { int prop = pPropList[Property_List_Index]; int32_t array_index; IsLongArray = false; if ( Using_Walked_List ) { if (Walked_List_Length == 0) { // printf(" %s: ", bactext_property_name( prop ) ); array_index = 0; } else { array_index = Walked_List_Index; } } else { printf(" %s: ", bactext_property_name( prop ) ); array_index = BACNET_ARRAY_ALL; switch( prop ) { /* These are all potentially long arrays, so they may abort */ case PROP_OBJECT_LIST: case PROP_STATE_TEXT: case PROP_STRUCTURED_OBJECT_LIST: case PROP_SUBORDINATE_ANNOTATIONS: case PROP_SUBORDINATE_LIST: IsLongArray = true; break; } } invoke_id = Send_Read_Property_Request(device_instance, pMyObject->type, pMyObject->instance, prop, array_index ); } 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" - * wait and put these 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; } void PrintUsage() { printf("bacepics -- Generates Object and Property List for EPICS \r\n" ); printf("Usage: \r\n" ); printf(" bacepics [-v] device-instance \r\n" ); printf(" Use the -v option to show values instead of '?' \r\n\r\n" ); printf("Insert the output in your EPICS file as the last section: \r\n"); printf("\"List of Objects in test device:\" \r\n"); printf("before the final statement: \r\n"); printf("\"End of BACnet Protocol Implementation Conformance Statement\" \r\n"); exit(0); } int CheckCommandLineArgs( int argc, char *argv[] ) { int i; bool bFoundTarget = false; /* FIXME: handle multi homed systems - use an argument passed to the datalink_init() */ /* print help if not enough arguments */ if (argc < 2) { fprintf(stderr, "Must provide a device-instance \r\n\r\n" ); PrintUsage(); /* Will exit */ } for ( i = 1; i < argc; i++ ) { char *anArg = argv[i]; if ( anArg[0] == '-' ) { if ( anArg[1] == 'v' ) ShowValues = true; else PrintUsage(); } else { /* decode the Target Device Instance parameter */ Target_Device_Object_Instance = strtol( anArg, NULL, 0); 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 + 1); PrintUsage(); } bFoundTarget = true; } } if ( !bFoundTarget ) { fprintf(stderr, "Must provide a device-instance \r\n\r\n" ); PrintUsage(); /* Will exit */ } return 0; /* All OK if we reach here */ } /** Main function of the bacepics program. * * @see Device_Set_Object_Instance_Number, Keylist_Create, address_init, * dlenv_init, address_bind_request, Send_WhoIs, * tsm_timer_milliseconds, datalink_receive, npdu_handler, * Send_Read_Property_Multiple_Request, * * * @param argc [in] Arg count. * @param argv [in] Takes one or two arguments: an optional -v "Show Values" * switch, and the Device Instance #. * @return 0 on success. */ 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; 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; CheckCommandLineArgs( argc, argv ); /* Won't return if there is an issue. */ /* setup my info */ Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); Object_List = Keylist_Create(); address_init(); Init_Service_Handlers(); dlenv_init(); /* configure the timeout values */ current_seconds = time(NULL); timeout_seconds = (apdu_timeout() / 1000) * apdu_retries(); /* try to bind with the device */ found = address_bind_request(Target_Device_Object_Instance, &max_apdu, &Target_Address); if (!found) { Send_WhoIs(Target_Device_Object_Instance, Target_Device_Object_Instance); } printf("List of Objects in test device:\r\n"); /* 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); /* Has at least one second passed ? */ if (current_seconds != last_seconds) { tsm_timer_milliseconds(((current_seconds - last_seconds) * 1000)); } /* 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); } /* 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, loop back and try again */ continue; } 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) { if ( IsLongArray ) { /* Change to using a Walked List and retry this property */ Using_Walked_List = true; Walked_List_Index = Walked_List_Length = 0; } else { /* 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 ); myObject.instance = KEY_DECODE_ID( nextKey ); /* Opening brace for the new Object */ printf(" { \r\n"); /* Test code: if ( myObject.type == OBJECT_STRUCTURED_VIEW ) printf( " -- Structured View %d \n", myObject.instance ); */ } 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) { printf("\rError: APDU Timeout!\r\n"); break; } } } while ( myObject.type < MAX_BACNET_OBJECT_TYPE ); /* Closing brace for all Objects */ printf("} \r\n"); return 0; } /*@}*/ /* End group BACEPICS */