Files
bacnet_stack/bacnet-stack/demo/epics/main.c
T
tbrennan3 15f8e6741d Corrected the Object Names for 37 and 38 (and friendlier text now).
In bacepics, now counts errors and displays error count at the end (if any).
Always prints out the Device Address Binding as a '?' if it is Null.
2010-06-01 21:21:20 +00:00

1195 lines
46 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;
/* = { 6, { 127, 0, 0, 1, 0xBA, 0xC0, 0 }, 0 }; loopback address to talk to myself */
static uint16_t My_BIP_Port = 0; /* If set, use this as the source port. */
static bool Provided_Targ_MAC = false;
/* any errors are picked up in main loop */
static bool Error_Detected = false;
static uint16_t Last_Error_Class = 0;
static uint16_t Last_Error_Code = 0;
static uint16_t Error_Count = 0; /* Counts errors we couldn't get around */
static bool Has_RPM = true; /* Assume device can do RPM, to start */
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;
#define MINIMAL_PROPLIST_SIZE 4
static int32_t MinimalPropList[MINIMAL_PROPLIST_SIZE] =
{
PROP_OBJECT_IDENTIFIER,
PROP_OBJECT_NAME,
PROP_OBJECT_TYPE,
-1
};
/* 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
if ( ShowValues )
fprintf(stderr, "-- BACnet Error: %s: %s\r\n",
bactext_error_class_name(error_class),
bactext_error_code_name(error_code));
#endif
Error_Detected = true;
Last_Error_Class = error_class;
Last_Error_Code = error_code;
}
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 && ShowValues )
fprintf(stderr, "-- BACnet Abort: %s \r\n",
bactext_abort_reason_name(abort_reason));
#endif
Error_Detected = true;
Last_Error_Class = ERROR_CLASS_SERVICES;
if ( abort_reason < MAX_BACNET_ABORT_REASON )
Last_Error_Code = (ERROR_CODE_ABORT_BUFFER_OVERFLOW -1) + abort_reason;
else
Last_Error_Code = ERROR_CODE_ABORT_OTHER;
}
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
if ( ShowValues )
fprintf(stderr, "BACnet Reject: %s\r\n", bactext_reject_reason_name(reject_reason));
#endif
Error_Detected = true;
Last_Error_Class = ERROR_CLASS_SERVICES;
if ( reject_reason < MAX_BACNET_REJECT_REASON )
Last_Error_Code = (ERROR_CODE_REJECT_BUFFER_OVERFLOW -1) + reject_reason;
else
Last_Error_Code = ERROR_CODE_REJECT_OTHER;
}
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(stderr, "? -- 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 ((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:
/* Make it VTS3-friendly and don't show "Null"
* as a value. */
if ( value->tag == BACNET_APPLICATION_TAG_NULL ) {
fprintf(stdout, "?");
break;
}
/* Else, fall through for normal processing. */
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 */
}
/** Print the property identifier name to stdout,
* handling the proprietary property numbers.
* @param propertyIdentifier [in] The property identifier number.
*/
void Print_Property_Identifier(unsigned propertyIdentifier)
{
if (propertyIdentifier < 512) {
fprintf(stdout, "%s", bactext_property_name(propertyIdentifier));
} else {
fprintf(stdout, "proprietary %u", propertyIdentifier);
}
}
/** 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;
int i;
if ( ( !Has_RPM && ( Property_List_Index == 0 ) ) ||
( 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, " -- Just Minimal Properties: \r\n" );
pPropList = MinimalPropList;
Property_List_Length = MINIMAL_PROPLIST_SIZE -1;
}
/* Copy this list for later one-by-one processing */
for ( i = 0; i < Property_List_Length; i++ )
Property_List[i] = pPropList[i];
Property_List[i] = -1; /* Just to be sure we terminate */
} 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) {
array_index = 0;
} else {
array_index = Walked_List_Index;
}
} else {
fprintf(stdout, " ");
Print_Property_Identifier(prop);
fprintf(stdout, ": ");
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 {
fprintf(stdout, " ");
Print_Property_Identifier(rpm_property->propertyIdentifier);
fprintf(stdout, ": ");
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] [-p sport] [-t target_mac] device-instance \r\n");
printf(" -v: show values instead of '?' \r\n");
printf(" -p: Use sport for \"my\" port, instead of 0xBAC0 (BACnet/IP only) \r\n");
printf(" Allows you to communicate with a localhost target. \r\n");
printf(" -t: declare target's MAC instead of using Who-Is to bind to \r\n");
printf(" device-instance. Format is \"C0:A8:00:18:BA:C0\" (as usual) \r\n");
printf("\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");
printf("\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] == '-') {
switch ( anArg[1] )
{
case 'v':
ShowValues = true;
break;
case 'p':
if ( ++i < argc )
My_BIP_Port = (uint16_t) strtol(argv[i], NULL, 0);
/* Used strtol so sport can be either 0xBAC0 or 47808 */
break;
case 't':
if ( ++i < argc )
{
uint8_t *mac = Target_Address.mac;
/* The %hhx specifies unsigned char */
Target_Address.mac_len = sscanf( argv[i],
"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
&mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
if ( Target_Address.mac_len == 6 ) /* success */
{
Target_Address.net = 0;
Target_Address.len = 0; /* No src address */
Provided_Targ_MAC = true;
break;
}
else
printf( "ERROR: invalid Target MAC %s \r\n", argv[i] );
/* And fall through to PrintUsage */
}
/* Either break or fall through, as above */
/* break; */
default:
PrintUsage();
break;
}
} 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 */
}
/* Initialize fields for a new Object */
void StartNextObject( BACNET_READ_ACCESS_DATA *rpm_object, BACNET_OBJECT_ID *pNewObject )
{
BACNET_PROPERTY_REFERENCE *rpm_property;
Error_Detected = false;
Property_List_Index = Property_List_Length = 0;
rpm_object->object_type = pNewObject->type;
rpm_object->object_instance = pNewObject->instance;
rpm_property = calloc(1, sizeof(BACNET_PROPERTY_REFERENCE));
rpm_object->listOfProperties = rpm_property;
assert(rpm_property);
rpm_property->propertyIdentifier = PROP_ALL;
/* Start with a count of the array size */
rpm_property->propertyArrayIndex = 0;
}
/** 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;
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();
/* For BACnet/IP, we might have set a different port for "me", so
* (eg) we could talk to a BACnet/IP device on our same interface.
* My_BIP_Port will be non-zero in this case.
*/
if ( My_BIP_Port > 0 )
bip_set_port( My_BIP_Port );
address_init();
Init_Service_Handlers();
dlenv_init();
/* configure the timeout values */
current_seconds = time(NULL);
timeout_seconds = (apdu_timeout() / 1000) * apdu_retries();
if ( My_BIP_Port > 0 )
bip_set_port( 0xBAC0 ); /* Set back to std BACnet/IP port */
/* try to bind with the target device */
found =
address_bind_request(Target_Device_Object_Instance, &max_apdu,
&Target_Address);
if (!found) {
if ( Provided_Targ_MAC ) {
/* Update by adding the MAC address */
if ( max_apdu == 0 )
max_apdu = MAX_APDU; /* Whatever set for this datalink. */
address_add_binding( Target_Device_Object_Instance, max_apdu,
&Target_Address);
} else {
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) {
fprintf(stderr, "\rError: APDU Timeout!\r\n");
break;
}
/* else, loop back and try again */
continue;
} else
{
myState = GET_ALL_REQUEST;
rpm_object = calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
assert(rpm_object);
}
break;
case GET_ALL_REQUEST:
case GET_LIST_OF_ALL_REQUEST: /* differs in ArrayIndex only */
/* Update times; aids single-step debugging */
last_seconds = current_seconds;
StartNextObject( rpm_object, &myObject );
/* Override the default and set the optional array index to "None" */
if (myState == GET_ALL_REQUEST)
rpm_object->listOfProperties->propertyArrayIndex = -1;
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 */
/* Was it because the Device can't do RPM? */
if ( Last_Error_Code == ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE )
{
Has_RPM = false;
myState = GET_PROPERTY_REQUEST;
}
/* Try again, just to get a list of properties. */
else if (myState == GET_ALL_RESPONSE)
myState = GET_LIST_OF_ALL_REQUEST;
/* Else drop back to RP. */
else
{
myState = GET_PROPERTY_REQUEST;
StartNextObject( rpm_object, &myObject );
}
}
else if ( Has_RPM )
myState = GET_ALL_REQUEST; /* Let's try again */
else
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_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. */
Error_Count++;
}
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;
myState = GET_PROPERTY_REQUEST;
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 ");
Print_Property_Identifier(
pPropList[Property_List_Index]);
fprintf(stdout, " \r\n");
Error_Count++;
if ( ++Property_List_Index >= Property_List_Length )
myState = NEXT_OBJECT; /* Give up and move on to the next. */
}
}
} 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. */
Error_Count++;
}
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 );
*/
}
if ( Has_RPM )
myState = GET_ALL_REQUEST;
else
{
myState = GET_PROPERTY_REQUEST;
StartNextObject( rpm_object, &myObject );
}
} 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) {
fprintf(stderr, "\rError: APDU Timeout!\r\n");
break;
}
}
} while (myObject.type < MAX_BACNET_OBJECT_TYPE);
if ( Error_Count > 0 )
fprintf(stderr, "\r-- Found %d Errors \r\n", Error_Count );
/* Closing brace for all Objects */
printf("} \r\n");
return 0;
}
/*@}*//* End group BACEPICS */