1
0
mirror of https://github.com/stargieg/bacnet-stack synced 2025-10-26 23:35:52 +08:00
bacnet-stack/demo/epics/main.c

1855 lines
74 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 full VTS3 EPICS file,
* including the heading information. */
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> /* for time */
#include <errno.h>
#include <assert.h>
#include "config.h"
#include "bactext.h"
#include "iam.h"
#include "arf.h"
#include "tsm.h"
#include "address.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"
#include "proplist.h"
#include "version.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
* @{ */
/** This is the souped-up version of the program for generating EPICS files.
* It now:
* 1) Prepends the heading information (supported services, etc)
* 2) Determines some basic device properties for the header.
* 3) Postpends the tail information to complete the EPICS file.
*/
/* 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;
/* the invoke id is needed to filter incoming messages */
static uint8_t Request_Invoke_ID = 0;
/* loopback address to talk to myself */
/* = { 6, { 127, 0, 0, 1, 0xBA, 0xC0, 0 }, 0 }; */
#if defined(BACDL_BIP)
/* If set, use this as the source port. */
static uint16_t My_BIP_Port = 0;
#endif
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;
/* Counts errors we couldn't get around */
static uint16_t Error_Count = 0;
/* Assume device can do RPM, to start */
static bool Has_RPM = true;
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 128 /* 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];
struct property_value_list_t {
int32_t property_id;
BACNET_APPLICATION_DATA_VALUE *value;
};
static struct property_value_list_t Property_Value_List[] = {
{PROP_VENDOR_NAME, NULL},
{PROP_MODEL_NAME, NULL},
{PROP_MAX_APDU_LENGTH_ACCEPTED, NULL},
{PROP_PROTOCOL_SERVICES_SUPPORTED, NULL},
{PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, NULL},
{PROP_DESCRIPTION, NULL},
{-1, NULL}
};
static BACNET_APPLICATION_DATA_VALUE *object_property_value(
int32_t property_id)
{
BACNET_APPLICATION_DATA_VALUE *value = NULL;
int32_t index = 0;
do {
if (Property_Value_List[index].property_id == property_id) {
value = Property_Value_List[index].value;
break;
}
index++;
} while (Property_Value_List[index].property_id != -1);
return value;
}
/* 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;
/* Show value instead of '?' */
static bool ShowValues = false;
/* show only device object properties */
static bool ShowDeviceObjectOnly = false;
/* read required and optional properties when RPM ALL does not work */
static bool Optional_Properties = false;
#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)
{
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
#if PRINT_ERRORS
if (ShowValues) {
fprintf(stderr, "-- BACnet Error: %s: %s\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)
{
(void) server;
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
#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 \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)
{
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
#if PRINT_ERRORS
if (ShowValues) {
fprintf(stderr, "BACnet Reject: %s\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;
if (address_match(&Target_Address, src) &&
(service_data->invoke_id == Request_Invoke_ID)) {
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;
if (address_match(&Target_Address, src) &&
(service_data->invoke_id == Request_Invoke_ID)) {
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(NULL);
#if BAC_ROUTING
uint32_t Object_Instance;
BACNET_CHARACTER_STRING name_string;
/* Put this client Device into the Routing table (first entry) */
Object_Instance = Device_Object_Instance_Number();
Device_Object_Name(Object_Instance, &name_string);
Add_Routed_Device(Object_Instance, &name_string, Device_Description());
#endif
/* 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);
}
/** Determine if this is a writable property, and, if so,
* note that in the EPICS output.
* This function may need a lot of customization for different implementations.
*
* @param object_type [in] The BACnet Object type of this object.
* @note object_instance [in] The ID number for this object.
* @param rpm_property [in] Points to structure holding the Property,
* Value, and Error information.
*/
void CheckIsWritableProperty(
BACNET_OBJECT_TYPE object_type,
/* uint32_t object_instance, */
BACNET_PROPERTY_REFERENCE * rpm_property)
{
bool bIsWritable = false;
if ((object_type == OBJECT_ANALOG_OUTPUT) ||
(object_type == OBJECT_BINARY_OUTPUT) ||
(object_type == OBJECT_COMMAND) ||
(object_type == OBJECT_MULTI_STATE_OUTPUT) ||
(object_type == OBJECT_ACCESS_DOOR)) {
if (rpm_property->propertyIdentifier == PROP_PRESENT_VALUE) {
bIsWritable = true;
}
} else if (object_type == OBJECT_AVERAGING) {
if ((rpm_property->propertyIdentifier == PROP_ATTEMPTED_SAMPLES) ||
(rpm_property->propertyIdentifier == PROP_WINDOW_INTERVAL) ||
(rpm_property->propertyIdentifier == PROP_WINDOW_SAMPLES)) {
bIsWritable = true;
}
} else if (object_type == OBJECT_FILE) {
if (rpm_property->propertyIdentifier == PROP_ARCHIVE) {
bIsWritable = true;
}
} else if ((object_type == OBJECT_LIFE_SAFETY_POINT) ||
(object_type == OBJECT_LIFE_SAFETY_ZONE)) {
if (rpm_property->propertyIdentifier == PROP_MODE) {
bIsWritable = true;
}
} else if (object_type == OBJECT_PROGRAM) {
if (rpm_property->propertyIdentifier == PROP_PROGRAM_CHANGE) {
bIsWritable = true;
}
} else if (object_type == OBJECT_PULSE_CONVERTER) {
if (rpm_property->propertyIdentifier == PROP_ADJUST_VALUE) {
bIsWritable = true;
}
} else if ((object_type == OBJECT_TRENDLOG) ||
(object_type == OBJECT_EVENT_LOG) ||
(object_type == OBJECT_TREND_LOG_MULTIPLE)) {
if ((rpm_property->propertyIdentifier == PROP_ENABLE) ||
(rpm_property->propertyIdentifier == PROP_RECORD_COUNT)) {
bIsWritable = true;
}
} else if (object_type == OBJECT_LOAD_CONTROL) {
if ((rpm_property->propertyIdentifier == PROP_REQUESTED_SHED_LEVEL) ||
(rpm_property->propertyIdentifier == PROP_START_TIME) ||
(rpm_property->propertyIdentifier == PROP_SHED_DURATION) ||
(rpm_property->propertyIdentifier == PROP_DUTY_WINDOW) ||
(rpm_property->propertyIdentifier == PROP_SHED_LEVELS)) {
bIsWritable = true;
}
} else if ((object_type == OBJECT_ACCESS_ZONE) ||
(object_type == OBJECT_ACCESS_USER) ||
(object_type == OBJECT_ACCESS_RIGHTS) ||
(object_type == OBJECT_ACCESS_CREDENTIAL)) {
if (rpm_property->propertyIdentifier == PROP_GLOBAL_IDENTIFIER) {
bIsWritable = true;
}
} else if (object_type == OBJECT_NETWORK_SECURITY) {
if ((rpm_property->propertyIdentifier ==
PROP_BASE_DEVICE_SECURITY_POLICY) ||
(rpm_property->propertyIdentifier ==
PROP_NETWORK_ACCESS_SECURITY_POLICIES) ||
(rpm_property->propertyIdentifier == PROP_SECURITY_TIME_WINDOW) ||
(rpm_property->propertyIdentifier == PROP_PACKET_REORDER_TIME) ||
(rpm_property->propertyIdentifier == PROP_LAST_KEY_SERVER) ||
(rpm_property->propertyIdentifier == PROP_SECURITY_PDU_TIMEOUT) ||
(rpm_property->propertyIdentifier == PROP_DO_NOT_HIDE)) {
bIsWritable = true;
}
}
/* Add more checking here, eg for Time_Synchronization_Recipients,
* Manual_Slave_Address_Binding, Object_Property_Reference,
* Life Safety Tracking_Value, Reliability, Mode,
* or Present_Value when Out_Of_Service is TRUE.
*/
if (bIsWritable)
fprintf(stdout, " Writable");
}
static const char *protocol_services_supported_text(
size_t bit_index)
{
bool is_confirmed = false;
size_t text_index = 0;
bool found = false;
const char *services_text = "unknown";
found =
apdu_service_supported_to_index(bit_index, &text_index, &is_confirmed);
if (found) {
if (is_confirmed) {
services_text = bactext_confirmed_service_name(text_index);
} else {
services_text = bactext_unconfirmed_service_name(text_index);
}
}
return services_text;
}
/** Provide a nicer output for Supported Services and Object Types bitfields
* and Date fields.
* 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 object_value [in] The structure holding this property's description
* and value.
* @return True if success. Or otherwise.
*/
bool PrettyPrintPropertyValue(
FILE * stream,
BACNET_OBJECT_PROPERTY_VALUE * object_value)
{
BACNET_APPLICATION_DATA_VALUE *value = NULL;
bool status = true; /*return value */
size_t len = 0, i = 0, j = 0;
BACNET_PROPERTY_ID property = PROP_ALL;
char short_month[4];
value = object_value->value;
property = object_value->object_property;
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, "( \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));
} else {
/* PROP_PROTOCOL_SERVICES_SUPPORTED */
fprintf(stream, " %s,",
protocol_services_supported_text(j));
}
} else /* not supported */
fprintf(stream, ",");
}
fprintf(stream, "\n ");
}
}
fprintf(stream, ") \n");
} else if ((value != NULL) && (value->tag == BACNET_APPLICATION_TAG_DATE)) {
/* eg, property == PROP_LOCAL_DATE
* VTS needs (3-Aug-2011,4) or (8/3/11,4), so we'll use the
* clearer, international form. */
strncpy(short_month, bactext_month_name(value->type.Date.month), 3);
short_month[3] = 0;
fprintf(stream, "(%u-%3s-%u, %u)", (unsigned) value->type.Date.day,
short_month, (unsigned) value->type.Date.year,
(unsigned) value->type.Date.wday);
} else if (value != NULL) {
assert(false); /* How did I get here? Fix your code. */
/* Meanwhile, a fallback plan */
status = bacapp_print_value(stdout, object_value);
} else
fprintf(stream, "? \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 object_type [in] The BACnet Object type of this object.
* @param object_instance [in] The ID number for this object.
* @param rpm_property [in] Points to structure holding the Property,
* Value, and Error information.
*/
void PrintReadPropertyData(
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_REFERENCE * rpm_property)
{
BACNET_OBJECT_PROPERTY_VALUE object_value; /* for bacapp printing */
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 \n");
return;
}
value = rpm_property->value;
if (value == NULL) {
/* Then we print the error information */
fprintf(stdout, "? -- BACnet Error: %s: %s\n",
bactext_error_class_name((int) rpm_property->error.error_class),
bactext_error_code_name((int) rpm_property->error.error_code));
return;
}
object_value.object_type = object_type;
object_value.object_instance = object_instance;
if ((value != NULL) && (value->next != NULL)) {
/* Then this is an array of values.
* But are we showing Values? We (VTS3) want ? instead of {?,?} to show up. */
switch (rpm_property->propertyIdentifier) {
/* Screen the Properties that can be arrays or Sequences */
case PROP_PRESENT_VALUE:
case PROP_PRIORITY_ARRAY:
if (!ShowValues) {
fprintf(stdout, "? \n");
/* We want the Values freed below, but don't want to
* print anything for them. To achieve this, swap
* out the Property for a non-existent Property
* and catch that below. */
rpm_property->propertyIdentifier =
PROP_PROTOCOL_CONFORMANCE_CLASS;
break;
}
if (object_type == OBJECT_DATETIME_VALUE)
break; /* A special case - no braces for this pair */
/* Else, fall through to normal processing. */
default:
/* Normal array: open brace */
fprintf(stdout, "{ ");
print_brace = true; /* remember to close it */
break;
}
}
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) {
object_value.object_property = rpm_property->propertyIdentifier;
object_value.array_index = rpm_property->propertyArrayIndex;
object_value.value = value;
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 == (uint32_t)
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) {
/* If the array is empty (nothing for this first entry),
* Make it VTS3-friendly and don't show "Null" as a value. */
if (value->tag == BACNET_APPLICATION_TAG_NULL) {
fprintf(stdout, "?\n ");
break;
}
/* 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, "{ \n ");
else
fprintf(stdout, "\n ");
}
if (rpm_property->propertyIdentifier == PROP_OBJECT_LIST) {
if (value->tag != BACNET_APPLICATION_TAG_OBJECT_ID) {
assert(value->tag == BACNET_APPLICATION_TAG_OBJECT_ID); /* 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(value->tag == BACNET_APPLICATION_TAG_OBJECT_ID); /* 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, &object_value);
if (isSequence)
fprintf(stdout, "}");
if ((Walked_List_Index < Walked_List_Length) ||
(value->next != NULL)) {
/* There are more. */
fprintf(stdout, ", ");
if (!(Walked_List_Index % 3))
fprintf(stdout, "\n ");
} else {
fprintf(stdout, " } \n");
}
break;
case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
case PROP_PROTOCOL_SERVICES_SUPPORTED:
PrettyPrintPropertyValue(stdout, &object_value);
break;
/* Our special non-existent case; do nothing further here. */
case PROP_PROTOCOL_CONFORMANCE_CLASS:
break;
default:
/* First, if this is a date type, it needs a different format
* for VTS, so pretty print it. */
if (ShowValues &&
(object_value.value->tag == BACNET_APPLICATION_TAG_DATE)) {
/* This would be PROP_LOCAL_DATE, or OBJECT_DATETIME_VALUE,
* or OBJECT_DATE_VALUE */
PrettyPrintPropertyValue(stdout, &object_value);
} else {
/* 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_TIME:
case PROP_LOCAL_DATE: /* Only if !ShowValues */
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, &object_value);
break;
}
}
if (value->next != NULL) {
/* there's more! */
fprintf(stdout, ",");
} else {
if (print_brace) {
/* Closing brace for this multi-valued array */
fprintf(stdout, " }");
}
CheckIsWritableProperty(object_type, /* object_instance, */
rpm_property);
fprintf(stdout, "\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.
*/
static void Print_Property_Identifier(
unsigned propertyIdentifier)
{
if (propertyIdentifier < 512) {
fprintf(stdout, "%s", bactext_property_name(propertyIdentifier));
} else {
fprintf(stdout, "-- proprietary %u", propertyIdentifier);
}
}
/* Build a list of device properties to request with RPM. */
static void BuildPropRequest(
BACNET_READ_ACCESS_DATA * rpm_object)
{
int i;
/* To start with, StartNextObject() has prepopulated one propEntry,
* but we will overwrite it and link more to it
*/
BACNET_PROPERTY_REFERENCE *propEntry = rpm_object->listOfProperties;
BACNET_PROPERTY_REFERENCE *oldEntry = rpm_object->listOfProperties;
for (i = 0; Property_Value_List[i].property_id != -1; i++) {
if (propEntry == NULL) {
propEntry = calloc(1, sizeof(BACNET_PROPERTY_REFERENCE));
assert(propEntry);
oldEntry->next = propEntry;
}
propEntry->propertyIdentifier = Property_Value_List[i].property_id;
propEntry->propertyArrayIndex = BACNET_ARRAY_ALL;
propEntry->next = NULL;
oldEntry = propEntry;
propEntry = NULL;
}
}
/** 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;
unsigned int i = 0, j = 0;
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 and Optional properties.
* In practice, this should only happen for simple devices that don't
* implement RPM or have really limited MAX_APDU size.
*/
property_list_special(pMyObject->type, &PropertyListStruct);
if (Optional_Properties) {
Property_List_Length =
PropertyListStruct.Required.count +
PropertyListStruct.Optional.count;
} else {
Property_List_Length = PropertyListStruct.Required.count;
}
if (Property_List_Length > MAX_PROPS) {
Property_List_Length = MAX_PROPS;
}
/* Copy this list for later one-by-one processing */
for (i = 0; i < Property_List_Length; i++) {
if (i < PropertyListStruct.Required.count) {
Property_List[i] = PropertyListStruct.Required.pList[i];
} else if (Optional_Properties) {
Property_List[i] = PropertyListStruct.Optional.pList[j];
j++;
}
}
/* Just to be sure we terminate */
Property_List[i] = -1;
}
if (Property_List[Property_List_Index] != -1) {
int prop = Property_List[Property_List_Index];
uint32_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.
* If the present state is GET_HEADING_RESPONSE, store the results
* in globals for later use.
* @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;
int i = 0;
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_Index++;
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 if (myState == GET_HEADING_RESPONSE) {
Property_Value_List[i++].value = rpm_property->value;
/* copy this pointer.
* On error, the pointer will be null
* We won't free these values; they will free at exit */
} else {
fprintf(stdout, " ");
Print_Property_Identifier(rpm_property->propertyIdentifier);
fprintf(stdout, ": ");
PrintReadPropertyData(rpm_data->object_type,
rpm_data->object_instance, 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 (myState == GET_HEADING_RESPONSE)
nextState = PRINT_HEADING;
/* press ahead with or without the data */
else 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_Index++;
Property_List_Length++;
}
if (bHasObjectList) {
Property_List[Property_List_Index] = PROP_OBJECT_LIST;
Property_List_Index++;
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;
}
static void print_usage(char *filename)
{
printf("Usage: %s [-v] [-d] [-p sport] [-t target_mac [-n dnet]]"
" device-instance\n", filename);
printf(" [--version][--help]\n");
}
static void print_help(char *filename)
{
printf("Generates Full EPICS file, including Object and Property List\n");
printf("device-instance:\n"
"BACnet Device Object Instance number that you are\n"
"trying to communicate to. This number will be used\n"
"to try and bind with the device using Who-Is and\n"
"I-Am services.\n");
printf("\n");
printf("-v: show values instead of '?' \n");
printf("-d: show only device object properties\n");
printf("-p: Use sport for \"my\" port. 0xBAC0 is default.\n");
printf(" Allows you to communicate with a localhost target.\n");
printf("-t: declare target's MAC instead of using Who-Is to bind to \n");
printf(" device-instance. Format is \"C0:A8:00:18:BA:C0\"\n");
printf(" Use \"7F:00:00:01:BA:C0\" for loopback testing \n");
printf("-n: specify target's DNET if not local BACnet network \n");
printf(" or on routed Virtual Network \n");
printf("\n");
printf("You can redirect the output to a .tpi file for VTS use,\n");
printf("e.g., bacepics 2701876 > epics-2701876.tpi \n");
}
int CheckCommandLineArgs(
int argc,
char *argv[])
{
int i;
bool bFoundTarget = false;
int argi = 0;
char *filename = NULL;
filename = filename_remove_path(argv[0]);
for (argi = 1; argi < argc; argi++) {
if (strcmp(argv[argi], "--help") == 0) {
print_usage(filename);
print_help(filename);
exit(0);
}
if (strcmp(argv[argi], "--version") == 0) {
printf("%s %s\n", filename, BACNET_VERSION_TEXT);
printf("Copyright (C) 2014 by Steve Karg and others.\n"
"This is free software; see the source for copying conditions.\n"
"There is NO warranty; not even for MERCHANTABILITY or\n"
"FITNESS FOR A PARTICULAR PURPOSE.\n");
exit(0);
}
}
if (argc < 2) {
print_usage(filename);
exit(0);
}
for (i = 1; i < argc; i++) {
char *anArg = argv[i];
if (anArg[0] == '-') {
switch (anArg[1]) {
case 'o':
Optional_Properties = true;
break;
case 'v':
ShowValues = true;
break;
case 'd':
ShowDeviceObjectOnly = true;
break;
case 'p':
if (++i < argc) {
#if defined(BACDL_BIP)
My_BIP_Port = (uint16_t) strtol(argv[i], NULL, 0);
/* Used strtol so sport can be either 0xBAC0 or 47808 */
#endif
}
break;
case 'n':
/* Destination Network Number */
if (Target_Address.mac_len == 0)
fprintf(stderr,
"Must provide a Target MAC before DNET \n");
if (++i < argc)
Target_Address.net =
(uint16_t) strtol(argv[i], NULL, 0);
/* Used strtol so dest.net can be either 0x1234 or 4660 */
break;
case 't':
if (++i < argc) {
/* decoded MAC addresses */
unsigned mac[6];
/* number of successful decodes */
int count;
/* loop counter */
unsigned j;
count =
sscanf(argv[i], "%2x:%2x:%2x:%2x:%2x:%2x", &mac[0],
&mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
if (count == 6) { /* success */
Target_Address.mac_len = count;
for (j = 0; j < 6; j++) {
Target_Address.mac[j] = (uint8_t) mac[j];
}
Target_Address.net = 0;
Target_Address.len = 0; /* No src address */
Provided_Targ_MAC = true;
break;
} else
printf("ERROR: invalid Target MAC %s \n",
argv[i]);
/* And fall through to print_usage */
}
/* Either break or fall through, as above */
/* break; */
default:
print_usage(filename);
exit(0);
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(stdout,
"Error: device-instance=%u - it must be less than %u\n",
Target_Device_Object_Instance, BACNET_MAX_INSTANCE + 1);
print_usage(filename);
exit(0);
}
bFoundTarget = true;
}
}
if (!bFoundTarget) {
fprintf(stdout, "Error: Must provide a device-instance \n\n");
print_usage(filename);
exit(0);
}
return 0; /* All OK if we reach here */
}
void PrintHeading(
)
{
BACNET_APPLICATION_DATA_VALUE *value = NULL;
BACNET_OBJECT_PROPERTY_VALUE property_value;
printf("PICS 0\n");
printf("BACnet Protocol Implementation Conformance Statement\n\n");
printf("--\n--\n");
printf("-- Generated by BACnet Protocol Stack library EPICS tool\n");
printf("-- BACnet/IP Interface for BACnet-stack Devices\n");
printf("-- http://sourceforge.net/projects/bacnet/ \n");
printf("-- \n--\n\n");
value = object_property_value(PROP_VENDOR_NAME);
if ((value != NULL) &&
(value->tag == BACNET_APPLICATION_TAG_CHARACTER_STRING)) {
printf("Vendor Name: \"%s\"\n",
characterstring_value(&value->type.Character_String));
} else {
printf("Vendor Name: \"your vendor name here\"\n");
}
value = object_property_value(PROP_MODEL_NAME);
/* Best we can do with Product Name and Model Number is use the same text */
if ((value != NULL) &&
(value->tag == BACNET_APPLICATION_TAG_CHARACTER_STRING)) {
printf("Product Name: \"%s\"\n",
characterstring_value(&value->type.Character_String));
printf("Product Model Number: \"%s\"\n",
characterstring_value(&value->type.Character_String));
} else {
printf("Product Name: \"your product name here\"\n");
printf("Product Model Number: \"your model number here\"\n");
}
value = object_property_value(PROP_DESCRIPTION);
if ((value != NULL) &&
(value->tag == BACNET_APPLICATION_TAG_CHARACTER_STRING)) {
printf("Product Description: \"%s\"\n\n",
characterstring_value(&value->type.Character_String));
} else {
printf("Product Description: "
"\"your product description here\"\n\n");
}
printf("BIBBs Supported:\n");
printf("{\n");
printf(" DS-RP-B\n");
printf("-- possible BIBBs in this device\n");
printf("-- DS-RPM-B\n");
printf("-- DS-WP-B\n");
printf("-- DM-DDB-B\n");
printf("-- DM-DOB-B\n");
printf("-- DM-DCC-B\n");
printf("-- DM-RD-B\n");
printf("-- DS-COV-A\n");
printf("-- DS-COV-B\n");
printf("-- AE-N-A\n");
printf("-- AE-N-I-B\n");
printf("-- AE-N-E-B\n");
printf("-- AE-ACK-B\n");
printf("-- AE-ACK-A\n");
printf("-- DM-UTC-B\n");
#ifdef BAC_ROUTING
/* Next line only for the gateway (ie, if not addressing a subNet) */
if (Target_Address.net == 0)
printf("-- NM-RC-B\n");
#endif
printf("}\n\n");
printf("BACnet Standard Application Services Supported:\n");
printf("{\n");
value = object_property_value(PROP_PROTOCOL_SERVICES_SUPPORTED);
/* We have to process this bit string and determine which Object Types we have,
* and show them
*/
if ((value != NULL) && (value->tag == BACNET_APPLICATION_TAG_BIT_STRING)) {
int i, len = bitstring_bits_used(&value->type.Bit_String);
printf("-- services reported by this device\n");
for (i = 0; i < len; i++) {
if (bitstring_bit(&value->type.Bit_String, (uint8_t) i))
printf(" %s\n", protocol_services_supported_text(i));
}
} else {
printf("-- use \'Initiate\' or \'Execute\' or both for services.\n");
printf(" ReadProperty Execute\n");
printf("-- ReadPropertyMultiple Initiate Execute\n");
printf("-- WriteProperty Initiate Execute\n");
printf("-- DeviceCommunicationControl Initiate Execute\n");
printf("-- Who-Has Initiate Execute\n");
printf("-- I-Have Initiate Execute\n");
printf("-- Who-Is Initiate Execute\n");
printf("-- I-Am Initiate Execute\n");
printf("-- ReinitializeDevice Initiate Execute\n");
printf("-- AcknowledgeAlarm Initiate Execute\n");
printf("-- ConfirmedCOVNotification Initiate Execute\n");
printf("-- UnconfirmedCOVNotification Initiate Execute\n");
printf("-- ConfirmedEventNotification Initiate Execute\n");
printf("-- UnconfirmedEventNotification Initiate Execute\n");
printf("-- GetAlarmSummary Initiate Execute\n");
printf("-- GetEnrollmentSummary Initiate Execute\n");
printf("-- WritePropertyMultiple Initiate Execute\n");
printf("-- ReadRange Initiate Execute\n");
printf("-- GetEventInformation Initiate Execute\n");
printf("-- SubscribeCOVProperty Initiate Execute\n");
#ifdef BAC_ROUTING
if (Target_Address.net == 0) {
printf
("-- Note: The following Routing Services are Supported:\n");
printf("-- Who-Is-Router-To-Network Initiate Execute\n");
printf("-- I-Am-Router-To-Network Initiate Execute\n");
printf("-- Initialize-Routing-Table Execute\n");
printf("-- Initialize-Routing-Table-Ack Initiate\n");
}
#endif
}
printf("}\n\n");
printf("Standard Object-Types Supported:\n");
printf("{\n");
value = object_property_value(PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED);
/* We have to process this bit string and determine which Object Types we have,
* and show them
*/
if ((value != NULL) && (value->tag == BACNET_APPLICATION_TAG_BIT_STRING)) {
int i, len = bitstring_bits_used(&value->type.Bit_String);
printf("-- objects reported by this device\n");
for (i = 0; i < len; i++) {
if (bitstring_bit(&value->type.Bit_String, (uint8_t) i))
printf(" %s\n", bactext_object_type_name(i));
}
} else {
printf("-- possible objects in this device\n");
printf("-- use \'Createable\' or \'Deleteable\' or both or none.\n");
printf("-- Analog Input Createable Deleteable\n");
printf("-- Analog Output Createable Deleteable\n");
printf("-- Analog Value Createable Deleteable\n");
printf("-- Binary Input Createable Deleteable\n");
printf("-- Binary Output Createable Deleteable\n");
printf("-- Binary Value Createable Deleteable\n");
printf("-- Device Createable Deleteable\n");
printf("-- Multi-state Input Createable Deleteable\n");
printf("-- Multi-state Output Createable Deleteable\n");
printf("-- Multi-state Value Createable Deleteable\n");
printf("-- Structured View Createable Deleteable\n");
printf("-- Characterstring Value\n");
printf("-- Datetime Value\n");
printf("-- Integer Value\n");
printf("-- Positive Integer Value\n");
printf("-- Trend Log\n");
printf("-- Load Control\n");
printf("-- Bitstring Value\n");
printf("-- Date Pattern Value\n");
printf("-- Date Value\n");
printf("-- Datetime Pattern Value\n");
printf("-- Large Analog Value\n");
printf("-- Octetstring Value\n");
printf("-- Time Pattern Value\n");
printf("-- Time Value\n");
}
printf("}\n\n");
printf("Data Link Layer Option:\n");
printf("{\n");
printf("-- choose the data link options supported\n");
printf("-- ISO 8802-3, 10BASE5\n");
printf("-- ISO 8802-3, 10BASE2\n");
printf("-- ISO 8802-3, 10BASET\n");
printf("-- ISO 8802-3, fiber\n");
printf("-- ARCNET, coax star\n");
printf("-- ARCNET, coax bus\n");
printf("-- ARCNET, twisted pair star \n");
printf("-- ARCNET, twisted pair bus\n");
printf("-- ARCNET, fiber star\n");
printf("-- ARCNET, twisted pair, EIA-485, Baud rate(s): 156000\n");
printf("-- MS/TP master. Baud rate(s): 9600, 38400\n");
printf("-- MS/TP slave. Baud rate(s): 9600, 38400\n");
printf("-- Point-To-Point. EIA 232, Baud rate(s): 9600\n");
printf("-- Point-To-Point. Modem, Baud rate(s): 9600\n");
printf("-- Point-To-Point. Modem, Baud rate(s): 9600 to 115200\n");
printf("-- BACnet/IP, 'DIX' Ethernet\n");
printf("-- BACnet/IP, Other\n");
printf("-- Other\n");
printf("}\n\n");
printf("Character Sets Supported:\n");
printf("{\n");
printf("-- choose any character sets supported\n");
printf("-- ANSI X3.4\n");
printf("-- IBM/Microsoft DBCS\n");
printf("-- JIS C 6226\n");
printf("-- ISO 8859-1\n");
printf("-- ISO 10646 (UCS-4)\n");
printf("-- ISO 10646 (UCS2)\n");
printf("}\n\n");
printf("Special Functionality:\n");
printf("{\n");
value = object_property_value(PROP_MAX_APDU_LENGTH_ACCEPTED);
printf(" Maximum APDU size in octets: ");
if (value != NULL) {
property_value.object_type = OBJECT_DEVICE;
property_value.object_instance = 0;
property_value.object_property = PROP_MAX_APDU_LENGTH_ACCEPTED;
property_value.array_index = BACNET_ARRAY_ALL;
property_value.value = value;
bacapp_print_value(stdout, &property_value);
} else {
printf("?");
}
printf("\n}\n\n");
printf("Default Property Value Restrictions:\n");
printf("{\n");
printf(" unsigned-integer: <minimum: 0; maximum: 4294967295>\n");
printf(" signed-integer: <minimum: -2147483647; maximum: 2147483647>\n");
printf(" real: <minimum: -3.40282347E38; maximum: 3.40282347E38; resolution: 1.0>\n");
printf(" double: <minimum: 2.2250738585072016E-38; maximum: 1.7976931348623157E38; resolution: 0.0001>\n");
printf(" date: <minimum: 01-January-1970; maximum: 31-December-2038>\n");
printf(" octet-string: <maximum length string: 122>\n");
printf(" character-string: <maximum length string: 122>\n");
printf(" list: <maximum length list: 10>\n");
printf(" variable-length-array: <maximum length array: 10>\n");
printf("}\n\n");
printf("Fail Times:\n");
printf("{\n");
printf(" Notification Fail Time: 2\n");
printf(" Internal Processing Fail Time: 0.5\n");
printf(" Minimum ON/OFF Time: 5\n");
printf(" Schedule Evaluation Fail Time: 1\n");
printf(" External Command Fail Time: 1\n");
printf(" Program Object State Change Fail Time: 2\n");
printf(" Acknowledgement Fail Time: 2\n");
printf("}\n\n");
}
void Print_Device_Heading(void)
{
printf("List of Objects in Test Device:\n");
/* Print Opening brace, then kick off the Device Object */
printf("{\n");
printf(" {\n"); /* And opening brace for the first object */
}
/* 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;
rpm_object->next = NULL;
assert(rpm_property);
rpm_property->propertyIdentifier = PROP_ALL;
rpm_property->propertyArrayIndex = BACNET_ARRAY_ALL;
}
/** 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 to four arguments, processed in CheckCommandLineArgs().
* @return 0 on success.
*/
int main(
int argc,
char *argv[])
{
BACNET_ADDRESS src; /* 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;
bool found = false;
BACNET_OBJECT_ID myObject;
uint8_t buffer[MAX_PDU] = { 0 };
BACNET_READ_ACCESS_DATA *rpm_object = NULL;
KEY nextKey;
CheckCommandLineArgs(argc, argv); /* Won't return if there is an issue. */
memset(&src, 0, sizeof(BACNET_ADDRESS));
/* setup my info */
Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE);
Object_List = Keylist_Create();
#if defined(BACDL_BIP)
/* 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(htons(My_BIP_Port));
}
#endif
address_init();
Init_Service_Handlers();
dlenv_init();
atexit(datalink_cleanup);
/* configure the timeout values */
current_seconds = time(NULL);
timeout_seconds = (apdu_timeout() / 1000) * apdu_retries();
#if defined(BACDL_BIP)
if (My_BIP_Port > 0) {
bip_set_port(htons(0xBAC0)); /* Set back to std BACnet/IP port */
}
#endif
/* 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) {
if (Target_Address.net > 0) {
/* We specified a DNET; call Who-Is to find the full
* routed path to this Device */
Send_WhoIs_Remote(&Target_Address,
Target_Device_Object_Instance,
Target_Device_Object_Instance);
} else {
/* 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);
}
}
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((uint16_t) ((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: Unable to bind to %u"
" after waiting %ld seconds.\n",
Target_Device_Object_Instance,
(long int) elapsed_seconds);
break;
}
/* else, loop back and try again */
continue;
} else {
rpm_object = calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
assert(rpm_object);
myState = GET_HEADING_INFO;
}
break;
case GET_HEADING_INFO:
/* FIXME: get heading properties with ReadProperty */
last_seconds = current_seconds;
StartNextObject(rpm_object, &myObject);
BuildPropRequest(rpm_object);
Request_Invoke_ID =
Send_Read_Property_Multiple_Request(buffer, MAX_PDU,
Target_Device_Object_Instance, rpm_object);
if (Request_Invoke_ID > 0) {
elapsed_seconds = 0;
} else {
/* We failed. Will hurt the header info we can show. */
fprintf(stderr, "\r-- Failed to get Heading info \n");
}
myState = GET_HEADING_RESPONSE;
break;
case PRINT_HEADING:
/* Print out the header information */
if (ShowDeviceObjectOnly) {
Print_Device_Heading();
} else {
PrintHeading();
Print_Device_Heading();
}
myState = GET_ALL_REQUEST;
/* Fall through now */
case GET_ALL_REQUEST:
case GET_LIST_OF_ALL_REQUEST:
/* "list" differs in ArrayIndex only */
/* Update times; aids single-step debugging */
last_seconds = current_seconds;
StartNextObject(rpm_object, &myObject);
Request_Invoke_ID =
Send_Read_Property_Multiple_Request(buffer, MAX_PDU,
Target_Device_Object_Instance, rpm_object);
if (Request_Invoke_ID > 0) {
elapsed_seconds = 0;
if (myState == GET_LIST_OF_ALL_REQUEST)
myState = GET_LIST_OF_ALL_RESPONSE;
else
myState = GET_ALL_RESPONSE;
}
break;
case GET_HEADING_RESPONSE:
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) &&
(Request_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(Request_Invoke_ID)) {
Request_Invoke_ID = 0;
} else {
assert(false); /* How can this be? */
Request_Invoke_ID = 0;
}
elapsed_seconds = 0;
} else if (tsm_invoke_id_free(Request_Invoke_ID)) {
elapsed_seconds = 0;
Request_Invoke_ID = 0;
if (myState == GET_HEADING_RESPONSE)
myState = PRINT_HEADING;
/* just press ahead without the data */
else if (Error_Detected) {
if (Last_Error_Code ==
ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE) {
/* The normal case for Device Object */
/* Was it because the Device can't do RPM? */
Has_RPM = false;
myState = GET_PROPERTY_REQUEST;
} else if (Last_Error_Code ==
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED) {
myState = GET_PROPERTY_REQUEST;
StartNextObject(rpm_object, &myObject);
} else if (myState == GET_ALL_RESPONSE)
/* Try again, just to get a list of properties. */
myState = GET_LIST_OF_ALL_REQUEST;
else {
/* Else drop back to RP. */
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(Request_Invoke_ID)) {
fprintf(stderr, "\rError: TSM Timeout!\n");
tsm_free_invoke_id(Request_Invoke_ID);
Request_Invoke_ID = 0;
elapsed_seconds = 0;
if (myState == GET_HEADING_RESPONSE)
myState = PRINT_HEADING;
/* just press ahead without the data */
else
myState = GET_ALL_REQUEST; /* Let's try again */
} else if (Error_Detected) {
/* Don't think we'll ever actually reach this point. */
elapsed_seconds = 0;
Request_Invoke_ID = 0;
if (myState == GET_HEADING_RESPONSE)
myState = PRINT_HEADING;
/* just press ahead without the data */
else
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;
elapsed_seconds = 0;
Request_Invoke_ID =
Read_Properties(Target_Device_Object_Instance, &myObject);
if (Request_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) &&
(Request_Invoke_ID ==
Read_Property_Multiple_Data.service_data.invoke_id)) {
Read_Property_Multiple_Data.new_data = false;
PrintReadPropertyData(Read_Property_Multiple_Data.
rpm_data->object_type,
Read_Property_Multiple_Data.rpm_data->object_instance,
Read_Property_Multiple_Data.rpm_data->
listOfProperties);
if (tsm_invoke_id_free(Request_Invoke_ID)) {
Request_Invoke_ID = 0;
} else {
assert(false); /* How can this be? */
Request_Invoke_ID = 0;
}
elapsed_seconds = 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++;
}
myState = GET_PROPERTY_REQUEST; /* Go fetch next Property */
} else if (tsm_invoke_id_free(Request_Invoke_ID)) {
Request_Invoke_ID = 0;
elapsed_seconds = 0;
myState = GET_PROPERTY_REQUEST;
if (Error_Detected) {
if ((Last_Error_Class != ERROR_CLASS_PROPERTY) &&
(Last_Error_Code != ERROR_CODE_UNKNOWN_PROPERTY)) {
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(Property_List
[Property_List_Index]);
fprintf(stdout, " \n");
Error_Count++;
Property_List_Index++;
if (Property_List_Index >=
Property_List_Length) {
/* Give up and move on to the next. */
myState = NEXT_OBJECT;
}
}
} else {
fprintf(stdout, " -- unknown property\n");
Error_Count++;
Property_List_Index++;
if (Property_List_Index >= Property_List_Length) {
/* Give up and move on to the next. */
myState = NEXT_OBJECT;
}
}
}
} else if (tsm_invoke_id_failed(Request_Invoke_ID)) {
fprintf(stderr, "\rError: TSM Timeout!\n");
tsm_free_invoke_id(Request_Invoke_ID);
elapsed_seconds = 0;
Request_Invoke_ID = 0;
myState = 3; /* Let's try again, same Property */
} else if (Error_Detected) {
/* Don't think we'll ever actually reach this point. */
elapsed_seconds = 0;
Request_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 \n",
Keylist_Count(Object_List));
Object_List_Index = -1; /* start over (will be incr to 0) */
if (ShowDeviceObjectOnly) {
/* Closing brace for the Device Object */
printf(" }, \n");
/* done with all Objects, signal end of this while loop */
myObject.type = MAX_BACNET_OBJECT_TYPE;
break;
}
}
/* Advance to the next object, as long as it's not the Device object */
do {
Object_List_Index++;
if (Object_List_Index < Keylist_Count(Object_List)) {
nextKey = Keylist_Key(Object_List, Object_List_Index);
myObject.type = KEY_DECODE_TYPE(nextKey);
myObject.instance = KEY_DECODE_ID(nextKey);
/* Don't re-list the Device Object among its objects */
if (myObject.type == OBJECT_DEVICE)
continue;
/* Closing brace for the previous Object */
printf(" }, \n");
/* Opening brace for the new Object */
printf(" { \n");
/* Test code:
if ( myObject.type == OBJECT_STRUCTURED_VIEW )
printf( " -- Structured View %d \n", myObject.instance );
*/
} else {
/* Closing brace for the last Object */
printf(" } \n");
/* done with all Objects, signal end of this while loop */
myObject.type = MAX_BACNET_OBJECT_TYPE;
}
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 || (Request_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! (%lds)\n",
(long int) elapsed_seconds);
break;
}
}
} while (myObject.type < MAX_BACNET_OBJECT_TYPE);
if (Error_Count > 0)
fprintf(stdout, "\r-- Found %d Errors \n", Error_Count);
/* Closing brace for all Objects, if we got any, and closing footer */
if (myState != INITIAL_BINDING) {
printf("} \n");
printf
("End of BACnet Protocol Implementation Conformance Statement\n");
printf("\n");
}
return 0;
}
/*@} */
/* End group BACEPICS */