Extensively revised bacepics to produce a full list of objects, not just the required properties of just the Device object.
Added a state machine which tries to get all properties in one RPM call first, then falls back to getting all object properties and calling RP once for each property, except the Device Object List, which calls RP once for each ObjectID in the list. Adding support for Structured Views but not complete yet.
This commit is contained in:
+651
-202
@@ -30,6 +30,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <time.h> /* for time */
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user