From 916067ec59b961a33d3743924db1c2b9c519668c Mon Sep 17 00:00:00 2001 From: petermcs Date: Mon, 23 Nov 2009 08:27:09 +0000 Subject: [PATCH] Added skeleton Trend Log object. Currently allows properties to be read and written but has little of the required logic for trending implemented yet. --- bacnet-stack/demo/object/trendlog.c | 790 ++++++++++++++++++ bacnet-stack/demo/readrange/main.c | 8 + .../demo/readrange/readrange/readrange.vcproj | 8 + bacnet-stack/include/bacdcode.h | 16 +- bacnet-stack/include/bacenum.h | 8 +- bacnet-stack/include/trendlog.h | 74 ++ bacnet-stack/src/bacapp.c | 22 +- bacnet-stack/src/bacdcode.c | 26 +- bacnet-stack/src/rpm.c | 4 +- 9 files changed, 931 insertions(+), 25 deletions(-) create mode 100644 bacnet-stack/demo/object/trendlog.c create mode 100644 bacnet-stack/include/trendlog.h diff --git a/bacnet-stack/demo/object/trendlog.c b/bacnet-stack/demo/object/trendlog.c new file mode 100644 index 00000000..9db8ed29 --- /dev/null +++ b/bacnet-stack/demo/object/trendlog.c @@ -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 +#include +#include /* 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); +} + diff --git a/bacnet-stack/demo/readrange/main.c b/bacnet-stack/demo/readrange/main.c index c25844b5..3496871e 100644 --- a/bacnet-stack/demo/readrange/main.c +++ b/bacnet-stack/demo/readrange/main.c @@ -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, diff --git a/bacnet-stack/demo/readrange/readrange/readrange.vcproj b/bacnet-stack/demo/readrange/readrange/readrange.vcproj index 74460206..c4b9c468 100644 --- a/bacnet-stack/demo/readrange/readrange/readrange.vcproj +++ b/bacnet-stack/demo/readrange/readrange/readrange.vcproj @@ -640,6 +640,10 @@ RelativePath="..\..\..\src\timesync.c" > + + @@ -998,6 +1002,10 @@ RelativePath="..\..\..\include\timesync.h" > + + diff --git a/bacnet-stack/include/bacdcode.h b/bacnet-stack/include/bacdcode.h index f6e5edaa..d29a9344 100644 --- a/bacnet-stack/include/bacdcode.h +++ b/bacnet-stack/include/bacdcode.h @@ -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 } diff --git a/bacnet-stack/include/bacenum.h b/bacnet-stack/include/bacenum.h index bdf6932c..8c20161c 100644 --- a/bacnet-stack/include/bacenum.h +++ b/bacnet-stack/include/bacenum.h @@ -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, diff --git a/bacnet-stack/include/trendlog.h b/bacnet-stack/include/trendlog.h new file mode 100644 index 00000000..caff7696 --- /dev/null +++ b/bacnet-stack/include/trendlog.h @@ -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 +#include +#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 diff --git a/bacnet-stack/src/bacapp.c b/bacnet-stack/src/bacapp.c index 3b707098..c5038b66 100644 --- a/bacnet-stack/src/bacapp.c +++ b/bacnet-stack/src/bacapp.c @@ -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); diff --git a/bacnet-stack/src/bacdcode.c b/bacnet-stack/src/bacdcode.c index 3603e97e..12310d03 100644 --- a/bacnet-stack/src/bacdcode.c +++ b/bacnet-stack/src/bacdcode.c @@ -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); diff --git a/bacnet-stack/src/rpm.c b/bacnet-stack/src/rpm.c index d32bfd30..3033629f 100644 --- a/bacnet-stack/src/rpm.c +++ b/bacnet-stack/src/rpm.c @@ -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);