Files
bacnet_stack/bacnet-stack/demo/epics/main.c
T
tbrennan3 8dd671d5a1 Sorry about the missing header; adding it in here.
Some comment tweaks for Doxygen.
2010-04-17 15:39:20 +00:00

1054 lines
35 KiB
C

/**************************************************************************
*
* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
*
* 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 <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> /* for time */
#include <errno.h>
#include <assert.h>
#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;
static bool Using_Walked_List = 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 )
printf("BACnet Abort: %s\r\n", 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;
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 )
{
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:
/* 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];
if ( Using_Walked_List )
{
if (Walked_List_Length == 0) {
printf(" %s: ", bactext_property_name( prop ) );
invoke_id =
Send_Read_Property_Request(device_instance,
pMyObject->type, pMyObject->instance,
prop, 0);
} else {
invoke_id =
Send_Read_Property_Request(device_instance,
pMyObject->type, pMyObject->instance,
prop, Walked_List_Index);
}
} else {
printf(" %s: ", bactext_property_name( prop ) );
invoke_id =
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" -
* 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)
{
/* 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 */