Added skeleton Trend Log object. Currently allows properties to be read and written but has little of the required logic for trending implemented yet.

This commit is contained in:
petermcs
2009-11-23 08:27:09 +00:00
parent 2bb6ecfb3a
commit 916067ec59
9 changed files with 931 additions and 25 deletions
+790
View File
@@ -0,0 +1,790 @@
/**************************************************************************
*
* Copyright (C) Peter Mc Shane
*
* 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.
*
*********************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <string.h> /* for memmove */
#include "bacdef.h"
#include "bacdcode.h"
#include "bacenum.h"
#include "bacapp.h"
#include "config.h" /* the custom stuff */
#include "apdu.h"
#include "wp.h" /* write property handling */
#include "version.h"
#include "device.h" /* me */
#include "handlers.h"
#include "datalink.h"
#include "address.h"
#include "bacdevobjpropref.h"
#if defined(BACFILE)
#include "bacfile.h" /* object list dependency */
#endif
#define MAX_TREND_LOGS 8
/* Error code for Trend Log storage */
typedef struct tl_error {
uint16_t usClass;
uint16_t usCode;
} TL_ERROR;
/* Bit string of up to 32 bits for Trend Log storage */
typedef struct tl_bits {
uint8_t ucLen;
uint8_t ucStore[4];
} TL_BITS;
/* Storage structure for Trend Log data */
typedef struct tl_data_record {
uint32_t ulTimeStamp; /* When the event occurred */
uint8_t ucRecType; /* What type of Event */
uint8_t ucStatus; /* Optional Status for read value */
union {
uint8_t ucLogStatus; /* Change of log state flags */
uint8_t ucBoolean; /* Stored boolean value */
float fReal; /* Stored floating point value */
uint32_t ulEnum; /* Stored enumerated value - max 32 bits */
uint32_t ulUValue; /* Stored unsigned value - max 32 bits */
int32_t lSValue; /* Stored signed value - max 32 bits */
TL_BITS Bits; /* Stored bitstring - max 32 bits */
TL_ERROR Error; /* Two part error class/code combo */
float fTime; /* Interval value for change of time - seconds */
} Datum;
} TL_DATA_REC;
#define TL_T_START_WILD 1 /* Start time is wild carded */
#define TL_T_STOP_WILD 2 /* Stop Time is wild carded */
#define TL_MAX_ENTRIES 1000 /* Entries per datalog */
TL_DATA_REC Logs[MAX_TREND_LOGS][TL_MAX_ENTRIES];
/* Structure containing config and status info for a Trend Log */
typedef struct tl_log_info {
bool bEnable; /* Trend log is active when this is true */
BACNET_DATE_TIME StartTime; /* BACnet format start time */
uint32_t ulStartTime; /* Local time working copy of start time */
BACNET_DATE_TIME StopTime; /* BACnet format stop time */
uint32_t ulStopTime; /* Local time working copy of stop time */
uint8_t ucTimeFlags; /* Shorthand info on times */
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE Source; /* Where the data comes from */
uint32_t ulLogInterval; /* Time between entries in 1/100s */
bool bStopWhenFull; /* Log halts when full if true */
uint32_t ulRecordCount; /* Count of items currently in the buffer */
uint32_t ulTotalRecordCount; /* Count of all items that have ever been inserted into the buffer */
BACNET_LOGGING_TYPE LoggingType; /* Polled/cov/triggered */
bool bAlignIntervals; /* If true align to the clock */
uint32_t ulIntervalOffset; /* Offset from start of period for taking reading */
bool bTrigger; /* Set to 1 to cause a reading to be taken */
} TL_LOG_INFO;
static TL_LOG_INFO LogInfo[MAX_TREND_LOGS];
/*
* Data types associated with a BACnet Log Record. We use these for managing the
* log buffer but they are also the tag numbers to use when encoding/decoding
* the log datum field.
*/
#define TL_TYPE_STATUS 0
#define TL_TYPE_BOOL 1
#define TL_TYPE_REAL 2
#define TL_TYPE_ENUM 3
#define TL_TYPE_UNSIGN 4
#define TL_TYPE_SIGN 5
#define TL_TYPE_BITS 6
#define TL_TYPE_NULL 7
#define TL_TYPE_ERROR 8
#define TL_TYPE_DELTA 9
#define TL_TYPE_ANY 10 /* We don't support this particular can of worms! */
/* These three arrays are used by the ReadPropertyMultiple handler */
static const int Trend_Log_Properties_Required[] = {
PROP_OBJECT_IDENTIFIER,
PROP_OBJECT_NAME,
PROP_OBJECT_TYPE,
PROP_ENABLE,
PROP_STOP_WHEN_FULL,
PROP_BUFFER_SIZE,
PROP_LOG_BUFFER,
PROP_RECORD_COUNT,
PROP_TOTAL_RECORD_COUNT,
PROP_EVENT_STATE,
PROP_LOGGING_TYPE,
PROP_STATUS_FLAGS,
-1
};
static const int Trend_Log_Properties_Optional[] = {
PROP_DESCRIPTION,
PROP_START_TIME,
PROP_STOP_TIME,
PROP_LOG_DEVICE_OBJECT_PROPERTY,
PROP_LOG_INTERVAL,
/* Required if COV logging supported
PROP_COV_RESUBSCRIPTION_INTERVAL,
PROP_CLIENT_COV_INCREMENT, */
/* Required if intrinsic reporting supported
PROP_NOTIFICATION_THRESHOLD,
PROP_RECORDS_SINCE_NOTIFICATION,
PROP_LAST_NOTIFY_RECORD,
PROP_NOTIFICATION_CLASS,
PROP_EVENT_ENABLE,
PROP_ACKED_TRANSITIONS,
PROP_NOTIFY_TYPE,
PROP_EVENT_TIME_STAMPS, */
PROP_ALIGN_INTERVALS,
PROP_INTERVAL_OFFSET,
PROP_TRIGGER,
-1
};
static const int Trend_Log_Properties_Proprietary[] = {
-1
};
void Trend_Log_Property_Lists(
const int **pRequired,
const int **pOptional,
const int **pProprietary)
{
if (pRequired)
*pRequired = Trend_Log_Properties_Required;
if (pOptional)
*pOptional = Trend_Log_Properties_Optional;
if (pProprietary)
*pProprietary = Trend_Log_Properties_Proprietary;
return;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then you need validate that the */
/* given instance exists */
bool Trend_Log_Valid_Instance(
uint32_t object_instance)
{
if (object_instance < MAX_TREND_LOGS) {
return true;
}
return false;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then count how many you have */
unsigned Trend_Log_Count(
void)
{
return MAX_TREND_LOGS;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then you need to return the instance */
/* that correlates to the correct index */
uint32_t Trend_Log_Index_To_Instance(
unsigned index)
{
return index;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then you need to return the index */
/* that correlates to the correct instance number */
unsigned Trend_Log_Instance_To_Index(
uint32_t object_instance)
{
unsigned index = MAX_TREND_LOGS;
if (object_instance < MAX_TREND_LOGS) {
index = object_instance;
}
return index;
}
/*
* Things to do when starting up the stack for Trend Logs.
* Should be called whenever we reset the device or power it up
*/
void Trend_Log_Init(
void)
{
static bool initialized = false;
int iLog;
int iEntry;
struct tm TempTime;
time_t Clock;
if (!initialized) {
initialized = true;
/* initialize all the values */
for (iLog = 0; iLog < MAX_TREND_LOGS; iLog++) {
/*
* Do we need to do anything here?
* Trend logs are usually assumed to survive over resets
* and are frequently implemented using Battery Backed RAM
* If they are implemented using Flash or SD cards or some
* such mechanism there may be some RAM based setup needed
* for log management purposes.
* We probably need to look at inserting LOG_INTERRUPTED
* entries into any active logs if the power down or reset
* may have caused us to miss readings.
*/
/* We will just fill the logs with some entries for testing
* purposes.
*/
TempTime.tm_year = 109;
TempTime.tm_mon = iLog + 1; /* Different month for each log */
TempTime.tm_mday = 1;
TempTime.tm_hour = 0;
TempTime.tm_min = 0;
TempTime.tm_sec = 0;
Clock = mktime(&TempTime);
for(iEntry = 0; iEntry < TL_MAX_ENTRIES; iEntry++) {
Logs[iLog][iEntry].ulTimeStamp = Clock;
Logs[iLog][iEntry].ucRecType = TL_TYPE_REAL;
Logs[iLog][iEntry].Datum.fReal = (float)(iEntry + (iLog * TL_MAX_ENTRIES));
Logs[iLog][iEntry].ucStatus = 0;
Clock += 900; /* advance 15 minutes */
}
LogInfo[iLog].bAlignIntervals = true;
LogInfo[iLog].bEnable = true;
LogInfo[iLog].bStopWhenFull = false;
LogInfo[iLog].bTrigger = false;
LogInfo[iLog].LoggingType = LOGGING_TYPE_POLLED;
LogInfo[iLog].Source.arrayIndex = 0;
LogInfo[iLog].ucTimeFlags = 0;
LogInfo[iLog].ulIntervalOffset = 0;
LogInfo[iLog].ulLogInterval = 900;
LogInfo[iLog].ulRecordCount = 1000;
LogInfo[iLog].ulTotalRecordCount = 10000;
LogInfo[iLog].Source.deviceIndentifier.instance = Device_Object_Instance_Number();
LogInfo[iLog].Source.deviceIndentifier.type = OBJECT_DEVICE;
LogInfo[iLog].Source.objectIdentifier.instance = iLog;
LogInfo[iLog].Source.objectIdentifier.type = OBJECT_ANALOG_INPUT;
LogInfo[iLog].Source.propertyIdentifier = PROP_PRESENT_VALUE;
datetime_set_values(&LogInfo[iLog].StartTime, 109, 1, 1, 0, 0, 0, 0);
datetime_set_values(&LogInfo[iLog].StopTime, 109, 11, 22, 23, 59, 59, 99);
}
}
return;
}
char *Trend_Log_Name(
uint32_t object_instance)
{
static char text_string[32] = ""; /* okay for single thread */
if (object_instance < MAX_TREND_LOGS) {
sprintf(text_string, "Trend Log %u", object_instance);
return text_string;
}
return NULL;
}
/* return the length of the apdu encoded or -1 for error or
-2 for abort message */
int Trend_Log_Encode_Property_APDU(
uint8_t * apdu,
uint32_t object_instance,
BACNET_PROPERTY_ID property,
int32_t array_index,
BACNET_ERROR_CLASS * error_class,
BACNET_ERROR_CODE * error_code)
{
int apdu_len = 0; /* return value */
int len = 0; /* apdu len intermediate value */
BACNET_BIT_STRING bit_string;
BACNET_CHARACTER_STRING char_string;
unsigned i = 0;
int object_type = 0;
uint32_t instance = 0;
unsigned count = 0;
TL_LOG_INFO *CurrentLog;
CurrentLog = &LogInfo[Trend_Log_Instance_To_Index(object_instance)]; /* Pin down which log to look at */
switch (property) {
case PROP_OBJECT_IDENTIFIER:
apdu_len =
encode_application_object_id(&apdu[0], OBJECT_TRENDLOG,
object_instance);
break;
case PROP_DESCRIPTION:
case PROP_OBJECT_NAME:
characterstring_init_ansi(&char_string, Trend_Log_Name(object_instance));
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_OBJECT_TYPE:
apdu_len = encode_application_enumerated(&apdu[0], OBJECT_TRENDLOG);
break;
case PROP_ENABLE:
apdu_len = encode_application_boolean(&apdu[0], CurrentLog->bEnable);
break;
case PROP_STOP_WHEN_FULL:
apdu_len = encode_application_boolean(&apdu[0], CurrentLog->bEnable);
break;
case PROP_BUFFER_SIZE:
apdu_len = encode_application_unsigned(&apdu[0], TL_MAX_ENTRIES);
break;
case PROP_LOG_BUFFER:
/* You can only read the buffer via the ReadRange service */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_READ_ACCESS_DENIED;
apdu_len = -1;
break;
case PROP_RECORD_COUNT:
apdu_len += encode_application_unsigned(&apdu[apdu_len], CurrentLog->ulRecordCount);
break;
case PROP_TOTAL_RECORD_COUNT:
apdu_len += encode_application_unsigned(&apdu[apdu_len], CurrentLog->ulTotalRecordCount);
break;
case PROP_EVENT_STATE:
/* note: see the details in the standard on how to use this */
apdu_len = encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
break;
case PROP_LOGGING_TYPE:
apdu_len = encode_application_enumerated(&apdu[0], CurrentLog->LoggingType);
break;
case PROP_STATUS_FLAGS:
/* note: see the details in the standard on how to use these */
bitstring_init(&bit_string);
bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,false);
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
break;
case PROP_START_TIME:
len = encode_application_date(&apdu[0],
&CurrentLog->StartTime.date);
apdu_len = len;
len = encode_application_time(&apdu[apdu_len],
&CurrentLog->StartTime.time);
apdu_len += len;
break;
case PROP_STOP_TIME:
len = encode_application_date(&apdu[0],
&CurrentLog->StopTime.date);
apdu_len = len;
len = encode_application_time(&apdu[apdu_len],
&CurrentLog->StopTime.time);
apdu_len += len;
break;
case PROP_LOG_DEVICE_OBJECT_PROPERTY:
/*
* BACnetDeviceObjectPropertyReference ::= SEQUENCE {
* objectIdentifier [0] BACnetObjectIdentifier,
* propertyIdentifier [1] BACnetPropertyIdentifier,
* propertyArrayIndex [2] Unsigned OPTIONAL, -- used only with array datatype
* -- if omitted with an array then
* -- the entire array is referenced
* deviceIdentifier [3] BACnetObjectIdentifier OPTIONAL
* }
*/
apdu_len += bacapp_encode_device_obj_property_ref(&apdu[apdu_len], &CurrentLog->Source);
break;
case PROP_LOG_INTERVAL:
/* We only log to 1 sec accuracy so must multiply by 100 before passing it on */
apdu_len += encode_application_unsigned(&apdu[apdu_len], CurrentLog->ulLogInterval * 100);
break;
case PROP_ALIGN_INTERVALS:
apdu_len = encode_application_boolean(&apdu[0], CurrentLog->bAlignIntervals);
break;
case PROP_INTERVAL_OFFSET:
/* We only log to 1 sec accuracy so must multiply by 100 before passing it on */
apdu_len += encode_application_unsigned(&apdu[apdu_len], CurrentLog->ulIntervalOffset * 100);
break;
case PROP_TRIGGER:
break;
default:
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_UNKNOWN_PROPERTY;
apdu_len = -1;
break;
}
/* only array properties can have array options */
if ((apdu_len >= 0) &&
(property != PROP_EVENT_TIME_STAMPS) &&
(array_index != BACNET_ARRAY_ALL)) {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
apdu_len = -1;
}
return apdu_len;
}
/* returns true if successful */
bool Trend_Log_Write_Property(
BACNET_WRITE_PROPERTY_DATA * wp_data,
BACNET_ERROR_CLASS * error_class,
BACNET_ERROR_CODE * error_code)
{
bool status = false; /* return value */
int len = 0;
int iOffset = 0;
BACNET_APPLICATION_DATA_VALUE value;
TL_LOG_INFO *CurrentLog;
BACNET_DATE TempDate; /* build here in case of error in time half of datetime */
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE TempSource;
if (!Trend_Log_Valid_Instance(wp_data->object_instance)) {
*error_class = ERROR_CLASS_OBJECT;
*error_code = ERROR_CODE_UNKNOWN_OBJECT;
return false;
}
CurrentLog = &LogInfo[Trend_Log_Instance_To_Index(wp_data->object_instance)]; /* Pin down which log to look at */
/* decode the some of the request */
len =
bacapp_decode_application_data(wp_data->application_data,
wp_data->application_data_len, &value);
/* FIXME: len < application_data_len: more data? */
/* FIXME: len == 0: unable to decode? */
switch (wp_data->object_property) {
case PROP_ENABLE:
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bEnable = value.type.Boolean;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_STOP_WHEN_FULL:
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bStopWhenFull = value.type.Boolean;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
/* To do: implement logic for when full log is switched from
* normal to stop when full see 12.25.12
*/
break;
case PROP_BUFFER_SIZE:
/* Fixed size buffer so deny write */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
break;
case PROP_RECORD_COUNT:
/* To Do - Reset log if count of zero is written.
* what do we do if any other value is written?
*/
if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
CurrentLog->ulRecordCount = value.type.Unsigned_Int;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_LOGGING_TYPE:
/* To Do - implement logic as per 12.25.27 for
* triggered and polled options.
*/
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
if(value.type.Enumerated != LOGGING_TYPE_COV) {
CurrentLog->LoggingType = value.type.Enumerated;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
}
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_START_TIME:
/* Copy the date part to safe place */
if (value.tag == BACNET_APPLICATION_TAG_DATE) {
TempDate = value.type.Date;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
break;
}
/* Then decode the time part */
len = bacapp_decode_application_data(wp_data->application_data + len,
wp_data->application_data_len - len, &value);
if (len && value.tag == BACNET_APPLICATION_TAG_TIME) {
CurrentLog->StartTime.date = TempDate; /* Safe to copy the date now */
CurrentLog->StartTime.time = value.type.Time;
if (datetime_wildcard(&CurrentLog->StartTime)) {
CurrentLog->ucTimeFlags |= TL_T_START_WILD;
CurrentLog->ulStartTime = 0;
} else {
CurrentLog->ucTimeFlags &= ~TL_T_START_WILD;
/* To do - convert bacnet date time to local 32 bit value */
}
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_STOP_TIME:
/* Copy the date part to safe place */
if (value.tag == BACNET_APPLICATION_TAG_DATE) {
TempDate = value.type.Date;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
break;
}
/* Then decode the time part */
len = bacapp_decode_application_data(wp_data->application_data + len,
wp_data->application_data_len - len, &value);
if (len && value.tag == BACNET_APPLICATION_TAG_TIME) {
CurrentLog->StopTime.date = TempDate; /* Safe to copy the date now */
CurrentLog->StopTime.time = value.type.Time;
if (datetime_wildcard(&CurrentLog->StopTime)) {
CurrentLog->ucTimeFlags |= TL_T_STOP_WILD;
CurrentLog->ulStopTime = 0xFFFFFFFF;
} else {
CurrentLog->ucTimeFlags &= ~TL_T_STOP_WILD;
/* To do - convert bacnet date time to local 32 bit value */
}
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_LOG_DEVICE_OBJECT_PROPERTY:
memset(&TempSource, 0, sizeof(TempSource)); /* Start with clean sheet */
/* First up is the object ID */
len = bacapp_decode_context_data(wp_data->application_data, wp_data->application_data_len, &value, PROP_LOG_DEVICE_OBJECT_PROPERTY);
if((len == 0) || (value.context_tag != 0) || ((wp_data->application_data_len - len) == 0)) {
/* Bad decode, wrong tag or following required parameter missing */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OTHER;
break;
}
TempSource.objectIdentifier = value.type.Object_Id;
wp_data->application_data_len -= len;
iOffset = len;
/* Second up is the property id */
len = bacapp_decode_context_data(&wp_data->application_data[iOffset], wp_data->application_data_len, &value, PROP_LOG_DEVICE_OBJECT_PROPERTY);
if((len == 0) || (value.context_tag != 1)) {
/* Bad decode or wrong tag */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OTHER;
break;
}
TempSource.propertyIdentifier = value.type.Enumerated;
wp_data->application_data_len -= len;
/* If there is still more to come */
if(wp_data->application_data_len != 0) {
iOffset += len;
len = bacapp_decode_context_data(&wp_data->application_data[iOffset], wp_data->application_data_len, &value, PROP_LOG_DEVICE_OBJECT_PROPERTY);
if((len == 0) || ((value.context_tag != 2) && (value.context_tag != 3))) {
/* Bad decode or wrong tag */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OTHER;
break;
}
if(value.context_tag == 2) {
/* Got an index so deal with it */
TempSource.arrayIndex = value.type.Unsigned_Int;
wp_data->application_data_len -= len;
/* Still some remaining */
if(wp_data->application_data_len != 0) {
iOffset += len;
len = bacapp_decode_context_data(&wp_data->application_data[iOffset], wp_data->application_data_len, &value, PROP_LOG_DEVICE_OBJECT_PROPERTY);
if((len == 0) || (value.context_tag != 3)) {
/* Bad decode or wrong tag */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OTHER;
break;
}
}
}
if(value.context_tag == 2) {
/* Got a device ID so deal with it */
TempSource.deviceIndentifier = value.type.Object_Id;
if(TempSource.deviceIndentifier.instance != Device_Object_Instance_Number()) {
/* Not our ID so can't handle it at the moment */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
break;
}
}
}
CurrentLog->Source = TempSource;
status = true;
break;
case PROP_LOG_INTERVAL:
/* To Do - Make non writable when Logging set to triggered
* also implement rules for switching between cov and polled by
* writing to this propert see section 12.25.9
*/
/* We only log to 1 sec accuracy so must divide by 100 before passing it on */
if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
CurrentLog->ulLogInterval = value.type.Unsigned_Int / 100;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_ALIGN_INTERVALS:
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bAlignIntervals = value.type.Boolean;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_INTERVAL_OFFSET:
/* We only log to 1 sec accuracy so must divide by 100 before passing it on */
if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
CurrentLog->ulIntervalOffset = value.type.Unsigned_Int / 100;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
case PROP_TRIGGER:
/* To Do - implement trigger logic as per
* 12.25.30
*/
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bAlignIntervals = value.type.Boolean;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
break;
default:
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
break;
}
return status;
}
void TrendLog_Init(
void)
{
}
bool Trend_Log_GetRRInfo(
uint32_t Object, /* Which particular object - obviously not important for device object */
BACNET_PROPERTY_ID Property, /* Which property */
RR_PROP_INFO *pInfo, /* Where to put the information */
BACNET_ERROR_CLASS *error_class,
BACNET_ERROR_CODE *error_code)
{
if(Property == PROP_LOG_BUFFER) {
pInfo->RequestTypes = RR_BY_POSITION | RR_BY_TIME | RR_BY_SEQUENCE;
pInfo->Handler = NULL;
return(true);
} else {
*error_class = ERROR_CLASS_SERVICES;
*error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
}
return(false);
}
+8
View File
@@ -66,6 +66,7 @@
#include "lc.h"
#include "lsp.h"
#include "mso.h"
#include "trendlog.h"
#include "bacfile.h"
#if defined(__BORLANDC__)
@@ -217,6 +218,13 @@ static void Init_Objects(
Multistate_Output_Count, Multistate_Output_Index_To_Instance,
Multistate_Output_Name, NULL);
Trend_Log_Init();
Init_Object(OBJECT_TRENDLOG, Trend_Log_Property_Lists,
Trend_Log_Encode_Property_APDU,
Trend_Log_Valid_Instance, Trend_Log_Write_Property,
Trend_Log_Count, Trend_Log_Index_To_Instance,
Trend_Log_Name, NULL);
#if defined(BACFILE)
bacfile_init();
Init_Object(OBJECT_FILE, BACfile_Property_Lists,
@@ -640,6 +640,10 @@
RelativePath="..\..\..\src\timesync.c"
>
</File>
<File
RelativePath="..\..\object\trendlog.c"
>
</File>
<File
RelativePath="..\..\..\src\tsm.c"
>
@@ -998,6 +1002,10 @@
RelativePath="..\..\..\include\timesync.h"
>
</File>
<File
RelativePath="..\..\..\include\trendlog.h"
>
</File>
<File
RelativePath="..\..\..\include\tsm.h"
>
+13 -3
View File
@@ -393,17 +393,27 @@ extern "C" {
uint8_t invoke_id,
uint8_t service_choice);
/* from clause 20.2.1.3.2 Constructed Data */
/* from clause 20.2.1.2 Tag Number */
/* true if extended tag numbering is used */
#define IS_EXTENDED_TAG_NUMBER(x) ((x & 0xF0) == 0xF0)
/* from clause 20.2.1.3.2 Constructed Data */
/* from clause 20.2.1.3.1 Primitive Data */
/* true if the extended value is used */
#define IS_EXTENDED_VALUE(x) ((x & 0x07) == 5)
/* from clause 20.2.1.3.2 Constructed Data */
/* from clause 20.2.1.1 Class */
/* true if the tag is context specific */
#define IS_CONTEXT_SPECIFIC(x) ((x & BIT3) == BIT3)
/* from clause 20.2.1.3.2 Constructed Data */
/* true if the tag is an opening tag */
#define IS_OPENING_TAG(x) ((x & 0x07) == 6)
/* from clause 20.2.1.3.2 Constructed Data */
/* true if the tag is a closing tag */
#define IS_CLOSING_TAG(x) ((x & 0x07) == 7)
#ifdef __cplusplus
}
+7 -1
View File
@@ -1125,9 +1125,15 @@ typedef enum {
typedef enum {
LOG_STATUS_LOG_DISABLED = 0,
LOG_STATUS_BUFFER_PURGED = 1,
LOG_STATUS_LOG_INTERRUPTED = 2,
LOG_STATUS_LOG_INTERRUPTED = 2
} BACNET_LOG_STATUS;
typedef enum {
LOGGING_TYPE_POLLED = 0,
LOGGING_TYPE_COV = 1,
LOGGING_TYPE_TRIGGERED = 2
} BACNET_LOGGING_TYPE;
typedef enum {
ACKNOWLEDGMENT_FILTER_ALL = 0,
ACKNOWLEDGMENT_FILTER_ACKED = 1,
+74
View File
@@ -0,0 +1,74 @@
/**************************************************************************
*
* Copyright (C) 2009 Peter Mc Shane
*
* 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.
*
*********************************************************************/
#ifndef TRENDLOG_H
#define TRENDLOG_H
#include <stdbool.h>
#include <stdint.h>
#include "bacdef.h"
#include "cov.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
void Trend_Log_Property_Lists(
const int **pRequired,
const int **pOptional,
const int **pProprietary);
bool Trend_Log_Valid_Instance(
uint32_t object_instance);
unsigned Trend_Log_Count(
void);
uint32_t Trend_Log_Index_To_Instance(
unsigned index);
unsigned Trend_Log_Instance_To_Index(
uint32_t instance);
bool Trend_Log_Object_Instance_Add(
uint32_t instance);
char *Trend_Log_Name(
uint32_t object_instance);
int Trend_Log_Encode_Property_APDU(
uint8_t * apdu,
uint32_t object_instance,
BACNET_PROPERTY_ID property,
int32_t array_index,
BACNET_ERROR_CLASS * error_class,
BACNET_ERROR_CODE * error_code);
bool Trend_Log_Write_Property(
BACNET_WRITE_PROPERTY_DATA * wp_data,
BACNET_ERROR_CLASS * error_class,
BACNET_ERROR_CODE * error_code);
void Trend_Log_Init(
void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+19 -3
View File
@@ -537,6 +537,22 @@ BACNET_APPLICATION_TAG bacapp_context_tag_type(
break;
}
break;
case PROP_LOG_DEVICE_OBJECT_PROPERTY:
switch (tag_number) {
case 0: /* Object ID */
case 3: /* Device ID */
tag = BACNET_APPLICATION_TAG_OBJECT_ID;
break;
case 1: /* Property ID */
tag = BACNET_APPLICATION_TAG_ENUMERATED;
break;
case 2: /* Array index */
tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
break;
default:
break;
}
break;
default:
break;
}
@@ -735,20 +751,20 @@ int bacapp_data_len(
uint32_t value = 0;
BACNET_APPLICATION_DATA_VALUE application_value;
if (decode_is_opening_tag(&apdu[0])) {
if (IS_OPENING_TAG(apdu[0])) {
len =
decode_tag_number_and_value(&apdu[apdu_len], &tag_number, &value);
apdu_len += len;
opening_tag_number = tag_number;
opening_tag_number_counter = 1;
while (opening_tag_number_counter) {
if (decode_is_opening_tag(&apdu[apdu_len])) {
if (IS_OPENING_TAG(apdu[apdu_len])) {
len =
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
&value);
if (tag_number == opening_tag_number)
opening_tag_number_counter++;
} else if (decode_is_closing_tag(&apdu[apdu_len])) {
} else if (IS_CLOSING_TAG(apdu[apdu_len])) {
len =
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
&value);
+10 -16
View File
@@ -399,9 +399,9 @@ int decode_tag_number_and_value(
}
len++;
}
} else if (decode_is_opening_tag(&apdu[0]) && value) {
} else if (IS_OPENING_TAG(apdu[0]) && value) {
*value = 0;
} else if (decode_is_closing_tag(&apdu[0]) && value) {
} else if (IS_CLOSING_TAG(apdu[0]) && value) {
/* closing tag */
*value = 0;
} else if (value) {
@@ -454,9 +454,9 @@ int decode_tag_number_and_value_safe(
/* packet is truncated */
len = 0;
}
} else if (decode_is_opening_tag(&apdu[0]) && value) {
} else if (IS_OPENING_TAG(apdu[0]) && value) {
*value = 0;
} else if (decode_is_closing_tag(&apdu[0]) && value) {
} else if (IS_CLOSING_TAG(apdu[0]) && value) {
/* closing tag */
*value = 0;
} else if (value) {
@@ -500,12 +500,9 @@ bool decode_is_opening_tag_number(
uint8_t tag_number)
{
uint8_t my_tag_number = 0;
bool opening_tag = false;
opening_tag = (bool) ((apdu[0] & 0x07) == 6);
decode_tag_number(apdu, &my_tag_number);
return (bool) (opening_tag && (my_tag_number == tag_number));
return (bool) (IS_OPENING_TAG(apdu[0]) && (my_tag_number == tag_number));
}
/* from clause 20.2.1.3.2 Constructed Data */
@@ -515,12 +512,9 @@ bool decode_is_closing_tag_number(
uint8_t tag_number)
{
uint8_t my_tag_number = 0;
bool closing_tag = false;
closing_tag = (bool) ((apdu[0] & 0x07) == 7);
decode_tag_number(apdu, &my_tag_number);
return (bool) (closing_tag && (my_tag_number == tag_number));
return (bool) (IS_CLOSING_TAG(apdu[0]) && (my_tag_number == tag_number));
}
/* from clause 20.2.3 Encoding of a Boolean Value */
@@ -1850,16 +1844,16 @@ void testBACDCodeTags(
ct_test(pTest, value == 0);
ct_test(pTest, len == test_len);
ct_test(pTest, tag_number == test_tag_number);
ct_test(pTest, decode_is_opening_tag(&apdu[0]) == true);
ct_test(pTest, decode_is_closing_tag(&apdu[0]) == false);
ct_test(pTest, IS_OPENING_TAG(apdu[0]) == true);
ct_test(pTest, IS_CLOSING_TAG(apdu[0]) == false);
len = encode_closing_tag(&apdu[0], tag_number);
ct_test(pTest, len == test_len);
len = decode_tag_number_and_value(&apdu[0], &test_tag_number, &value);
ct_test(pTest, len == test_len);
ct_test(pTest, value == 0);
ct_test(pTest, tag_number == test_tag_number);
ct_test(pTest, decode_is_opening_tag(&apdu[0]) == false);
ct_test(pTest, decode_is_closing_tag(&apdu[0]) == true);
ct_test(pTest, IS_OPENING_TAG(apdu[0]) == false);
ct_test(pTest, IS_CLOSING_TAG(apdu[0]) == true);
/* test the len-value-type portion */
for (value = 1;; value = value << 1) {
len = encode_tag(&apdu[0], tag_number, false, value);
+2 -2
View File
@@ -266,7 +266,7 @@ int rpm_decode_object_property(
*object_property = (BACNET_PROPERTY_ID) property;
/* Tag 1: Optional propertyArrayIndex */
if ((len < apdu_len) && IS_CONTEXT_SPECIFIC(apdu[len]) &&
(!decode_is_closing_tag(&apdu[len]))) {
(!IS_CLOSING_TAG(apdu[len]))) {
option_len =
(unsigned) decode_tag_number_and_value(&apdu[len], &tag_number,
&len_value_type);
@@ -459,7 +459,7 @@ int rpm_ack_decode_object_property(
*object_property = (BACNET_PROPERTY_ID) property;
/* Tag 3: Optional propertyArrayIndex */
if ((len < apdu_len) && IS_CONTEXT_SPECIFIC(apdu[len]) &&
(!decode_is_closing_tag(&apdu[len]))) {
(!IS_CLOSING_TAG(apdu[len]))) {
tag_len =
(unsigned) decode_tag_number_and_value(&apdu[len], &tag_number,
&len_value_type);