Updates to the Trend Log object:

1. Added more of the required logic to the Trend Log write property handler.
2. Fixed some mistakes in the read property handler
3. TrendLog_Init now sets up data to allow for testing.
4. Enabled write property handling in main.c
This commit is contained in:
petermcs
2009-11-25 10:23:23 +00:00
parent 916067ec59
commit 35ec69c307
3 changed files with 230 additions and 35 deletions
+215 -33
View File
@@ -39,6 +39,7 @@
#include "datalink.h"
#include "address.h"
#include "bacdevobjpropref.h"
#include "trendlog.h"
#if defined(BACFILE)
#include "bacfile.h" /* object list dependency */
#endif
@@ -62,7 +63,7 @@ typedef struct tl_bits {
/* Storage structure for Trend Log data */
typedef struct tl_data_record {
uint32_t ulTimeStamp; /* When the event occurred */
time_t tTimeStamp; /* When the event occurred */
uint8_t ucRecType; /* What type of Event */
uint8_t ucStatus; /* Optional Status for read value */
union {
@@ -90,9 +91,9 @@ TL_DATA_REC Logs[MAX_TREND_LOGS][TL_MAX_ENTRIES];
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 */
time_t tStartTime; /* 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 */
time_t tStopTime; /* 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 */
@@ -103,6 +104,7 @@ typedef struct tl_log_info {
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 */
int iIndex; /* Current insertion point */
} TL_LOG_INFO;
@@ -277,7 +279,7 @@ void Trend_Log_Init(
Clock = mktime(&TempTime);
for(iEntry = 0; iEntry < TL_MAX_ENTRIES; iEntry++) {
Logs[iLog][iEntry].ulTimeStamp = Clock;
Logs[iLog][iEntry].tTimeStamp = Clock;
Logs[iLog][iEntry].ucRecType = TL_TYPE_REAL;
Logs[iLog][iEntry].Datum.fReal = (float)(iEntry + (iLog * TL_MAX_ENTRIES));
Logs[iLog][iEntry].ucStatus = 0;
@@ -292,6 +294,7 @@ void Trend_Log_Init(
LogInfo[iLog].Source.arrayIndex = 0;
LogInfo[iLog].ucTimeFlags = 0;
LogInfo[iLog].ulIntervalOffset = 0;
LogInfo[iLog].iIndex = 0;
LogInfo[iLog].ulLogInterval = 900;
LogInfo[iLog].ulRecordCount = 1000;
LogInfo[iLog].ulTotalRecordCount = 10000;
@@ -303,7 +306,9 @@ void Trend_Log_Init(
LogInfo[iLog].Source.propertyIdentifier = PROP_PRESENT_VALUE;
datetime_set_values(&LogInfo[iLog].StartTime, 109, 1, 1, 0, 0, 0, 0);
LogInfo[iLog].tStartTime = TL_BAC_Time_To_Local(&LogInfo[iLog].StartTime);
datetime_set_values(&LogInfo[iLog].StopTime, 109, 11, 22, 23, 59, 59, 99);
LogInfo[iLog].tStopTime = TL_BAC_Time_To_Local(&LogInfo[iLog].StopTime);
}
}
@@ -370,7 +375,7 @@ int Trend_Log_Encode_Property_APDU(
break;
case PROP_STOP_WHEN_FULL:
apdu_len = encode_application_boolean(&apdu[0], CurrentLog->bEnable);
apdu_len = encode_application_boolean(&apdu[0], CurrentLog->bStopWhenFull);
break;
case PROP_BUFFER_SIZE:
@@ -458,6 +463,7 @@ int Trend_Log_Encode_Property_APDU(
break;
case PROP_TRIGGER:
apdu_len = encode_application_boolean(&apdu[0], CurrentLog->bTrigger);
break;
default:
@@ -491,6 +497,7 @@ bool Trend_Log_Write_Property(
TL_LOG_INFO *CurrentLog;
BACNET_DATE TempDate; /* build here in case of error in time half of datetime */
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE TempSource;
bool bEffectiveEnable;
if (!Trend_Log_Valid_Instance(wp_data->object_instance)) {
*error_class = ERROR_CLASS_OBJECT;
@@ -510,7 +517,36 @@ bool Trend_Log_Write_Property(
switch (wp_data->object_property) {
case PROP_ENABLE:
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bEnable = value.type.Boolean;
/* Section 12.25.5 can't enable a full log with stop when full set */
if((CurrentLog->bEnable == false) &&
(CurrentLog->bStopWhenFull == true) &&
(CurrentLog->ulRecordCount == TL_MAX_ENTRIES) &&
(value.type.Boolean == true)) {
*error_class = ERROR_CLASS_OBJECT;
*error_code = ERROR_CODE_LOG_BUFFER_FULL;
break;
}
/* Only trigger this validation on a potential change of state */
if(CurrentLog->bEnable != value.type.Boolean) {
bEffectiveEnable = TL_Is_Enabled(wp_data->object_instance);
CurrentLog->bEnable = value.type.Boolean;
/* To do: what actions do we need to take on writing ? */
if(value.type.Boolean == false) {
if(bEffectiveEnable == true) {
/* Only insert record if we really were enabled i.e. times and enable flags */
TL_Insert_Status_Rec(wp_data->object_instance, LOG_STATUS_LOG_DISABLED, true);
}
} else {
if(TL_Is_Enabled(wp_data->object_instance)) {
/* Have really gone from disabled to enabled as
* enable flag and times were correct
*/
TL_Insert_Status_Rec(wp_data->object_instance, LOG_STATUS_LOG_DISABLED, false);
}
}
}
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
@@ -520,30 +556,45 @@ bool Trend_Log_Write_Property(
case PROP_STOP_WHEN_FULL:
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bStopWhenFull = value.type.Boolean;
/* Only trigger this on a change of state */
if(CurrentLog->bStopWhenFull != value.type.Boolean) {
CurrentLog->bStopWhenFull = value.type.Boolean;
if((value.type.Boolean == true) &&
(CurrentLog->ulRecordCount == TL_MAX_ENTRIES) &&
(CurrentLog->bEnable == true)) {
/* When full log is switched from normal to stop when full
* disable the log and record the fact - see 135-2008 12.25.12
*/
CurrentLog->bEnable = false;
TL_Insert_Status_Rec(wp_data->object_instance, LOG_STATUS_LOG_DISABLED, true);
}
}
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 */
/* Fixed size buffer so deny write. If buffer size was writable
* we would probably erase the current log, resize, re-initalise
* and carry on - however write is not allowed if enable is true.
*/
*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;
if(value.type.Unsigned_Int == 0) {
/* Time to clear down the log */
CurrentLog->ulRecordCount = 0;
CurrentLog->iIndex = 0;
TL_Insert_Status_Rec(wp_data->object_instance, LOG_STATUS_BUFFER_PURGED, true);
}
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
@@ -552,15 +603,26 @@ bool Trend_Log_Write_Property(
break;
case PROP_LOGGING_TYPE:
/* To Do - implement logic as per 12.25.27 for
/* logic
* triggered and polled options.
*/
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
if(value.type.Enumerated != LOGGING_TYPE_COV) {
CurrentLog->LoggingType = value.type.Enumerated;
if(value.type.Enumerated == LOGGING_TYPE_POLLED) {
/* As per 12.25.27 pick a suitable default if interval is 0 */
if(CurrentLog->ulLogInterval == 0) {
CurrentLog->ulLogInterval = 900;
}
}
if(value.type.Enumerated == LOGGING_TYPE_TRIGGERED) {
/* As per 12.25.27 0 the interval if triggered logging selected */
CurrentLog->ulLogInterval = 0;
}
status = true;
} else {
/* We don't currently support COV */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
}
@@ -588,11 +650,13 @@ bool Trend_Log_Write_Property(
CurrentLog->StartTime.time = value.type.Time;
if (datetime_wildcard(&CurrentLog->StartTime)) {
/* Mark start time as wild carded */
CurrentLog->ucTimeFlags |= TL_T_START_WILD;
CurrentLog->ulStartTime = 0;
CurrentLog->tStartTime = 0;
} else {
/* Clear wild card flag and set time in local format */
CurrentLog->ucTimeFlags &= ~TL_T_START_WILD;
/* To do - convert bacnet date time to local 32 bit value */
CurrentLog->tStartTime = TL_BAC_Time_To_Local(&CurrentLog->StartTime);
}
status = true;
@@ -620,13 +684,15 @@ bool Trend_Log_Write_Property(
CurrentLog->StopTime.time = value.type.Time;
if (datetime_wildcard(&CurrentLog->StopTime)) {
/* Mark stop time as wild carded */
CurrentLog->ucTimeFlags |= TL_T_STOP_WILD;
CurrentLog->ulStopTime = 0xFFFFFFFF;
CurrentLog->tStopTime = 0xFFFFFFFF; /* Fixme: how do we set this to max for time_t ? */
} else {
/* Clear wild card flag and set time in local format */
CurrentLog->ucTimeFlags &= ~TL_T_STOP_WILD;
/* To do - convert bacnet date time to local 32 bit value */
CurrentLog->tStartTime = TL_BAC_Time_To_Local(&CurrentLog->StartTime);
}
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
@@ -676,7 +742,7 @@ bool Trend_Log_Write_Property(
/* Got an index so deal with it */
TempSource.arrayIndex = value.type.Unsigned_Int;
wp_data->application_data_len -= len;
/* Still some remaining */
/* Still some remaining so fetch potential device ID */
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);
@@ -689,10 +755,11 @@ bool Trend_Log_Write_Property(
}
}
if(value.context_tag == 2) {
if(value.context_tag == 3) {
/* Got a device ID so deal with it */
TempSource.deviceIndentifier = value.type.Object_Id;
if(TempSource.deviceIndentifier.instance != Device_Object_Instance_Number()) {
if((TempSource.deviceIndentifier.instance != Device_Object_Instance_Number()) ||
(TempSource.deviceIndentifier.type != OBJECT_DEVICE)) {
/* Not our ID so can't handle it at the moment */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
@@ -700,19 +767,34 @@ bool Trend_Log_Write_Property(
}
}
}
/* Make sure device ID is set to ours in case not supplied */
TempSource.deviceIndentifier.type = OBJECT_DEVICE;
TempSource.deviceIndentifier.instance = Device_Object_Instance_Number();
/* Quick comparison if structures are packed ... */
if(memcmp(&TempSource, &CurrentLog->Source, sizeof(BACNET_OBJECT_ID)) != 0) {
/* Clear buffer if property being logged is changed */
CurrentLog->ulRecordCount = 0;
CurrentLog->iIndex = 0;
TL_Insert_Status_Rec(wp_data->object_instance, LOG_STATUS_BUFFER_PURGED, true);
}
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
*/
if(CurrentLog->LoggingType == LOGGING_TYPE_TRIGGERED) {
/* Read only if triggered log so flag error and bail out */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
} else if((CurrentLog->LoggingType == LOGGING_TYPE_POLLED) &&
(value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) &&
(value.type.Unsigned_Int == 0)) {
/* We don't support COV at the moment so don't allow switching
* to it by clearing interval whilst in polling mode */
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
} else if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
/* 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 {
@@ -747,7 +829,7 @@ bool Trend_Log_Write_Property(
* 12.25.30
*/
if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) {
CurrentLog->bAlignIntervals = value.type.Boolean;
CurrentLog->bTrigger = value.type.Boolean;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
@@ -788,3 +870,103 @@ bool Trend_Log_GetRRInfo(
return(false);
}
/*****************************************************************************
* Insert a status record into a trend log - does not check for enable/log *
* full, time slots and so on as these type of entries have to go in *
* irrespective of such things which means that valid readings may get *
* pushed out of the log to make room. *
*****************************************************************************/
void TL_Insert_Status_Rec(int iLog, BACNET_LOG_STATUS eStatus, bool bState)
{
TL_LOG_INFO *CurrentLog;
TL_DATA_REC TempRec;
CurrentLog = &LogInfo[iLog];
TempRec.tTimeStamp = time(NULL);
TempRec.ucRecType = TL_TYPE_STATUS;
TempRec.ucStatus = 0;
TempRec.Datum.ucLogStatus = 0;
switch(eStatus) {
case LOG_STATUS_LOG_DISABLED:
if(bState)
TempRec.Datum.ucLogStatus = 1 << LOG_STATUS_LOG_DISABLED;
break;
case LOG_STATUS_BUFFER_PURGED:
if(bState)
TempRec.Datum.ucLogStatus = 1 << LOG_STATUS_BUFFER_PURGED;
break;
case LOG_STATUS_LOG_INTERRUPTED:
TempRec.Datum.ucLogStatus = 1 << LOG_STATUS_LOG_INTERRUPTED;
break;
default:
break;
}
Logs[iLog][CurrentLog->iIndex++] = TempRec;
if(CurrentLog->iIndex >= TL_MAX_ENTRIES)
CurrentLog->iIndex = 0;
CurrentLog->ulTotalRecordCount++;
if(CurrentLog->ulRecordCount < TL_MAX_ENTRIES)
CurrentLog->ulRecordCount++;
}
/*****************************************************************************
* Use the combination of the enable flag and the enable times to determine *
* if the log is really enabled now. See 135-2008 sections 12.25.5 - 12.25.7 *
*****************************************************************************/
bool TL_Is_Enabled(int iLog)
{
TL_LOG_INFO *CurrentLog;
time_t Now;
bool bStatus;
bStatus = true;
CurrentLog = &LogInfo[iLog];
if(CurrentLog->bEnable == false) {
/* Not enabled so time is irrelevant */
bStatus = false;
} else if((CurrentLog->ucTimeFlags == 0) && (CurrentLog->tStopTime < CurrentLog->tStartTime)) {
/* Start time was after stop time as per 12.25.6 and 12.25.7 */
bStatus = false;
} else if(CurrentLog->ucTimeFlags != (TL_T_START_WILD | TL_T_STOP_WILD)) {
/* enabled and either 1 wild card or none */
Now = time(NULL);
if((CurrentLog->ucTimeFlags | TL_T_START_WILD) != 0) {
/* wild card start time */
if(Now > CurrentLog->tStopTime)
bStatus = false;
} else if((CurrentLog->ucTimeFlags | TL_T_STOP_WILD) != 0) {
/* wild card stop time */
if(Now < CurrentLog->tStartTime)
bStatus = false;
} else {
if((Now < CurrentLog->tStartTime) || (Now > CurrentLog->tStopTime))
bStatus = false;
}
}
return(bStatus);
}
/*****************************************************************************
* Convert a BACnet time into a local time in seconds since the local epoch *
*****************************************************************************/
time_t TL_BAC_Time_To_Local(BACNET_DATE_TIME *SourceTime)
{
struct tm LocalTime;
LocalTime.tm_year = SourceTime->date.year;
LocalTime.tm_mon = SourceTime->date.month + 1;
LocalTime.tm_mday = SourceTime->date.day;
LocalTime.tm_hour = SourceTime->time.hour;
LocalTime.tm_min = SourceTime->time.min;
LocalTime.tm_sec = SourceTime->time.sec;
return(mktime(&LocalTime));
}
+4 -2
View File
@@ -253,8 +253,10 @@ static void Init_Service_Handlers(
/* we must implement read property - it's required! */
apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY,
handler_read_property);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE,
handler_read_property_multiple);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_RANGE,
handler_read_range);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY,
handler_write_property);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_RANGE,
handler_read_range);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_PRIVATE_TRANSFER,
+11
View File
@@ -68,6 +68,17 @@ extern "C" {
void Trend_Log_Init(
void);
void TL_Insert_Status_Rec(
int iLog,
BACNET_LOG_STATUS eStatus,
bool bState);
bool TL_Is_Enabled(
int iLog);
time_t TL_BAC_Time_To_Local(
BACNET_DATE_TIME *SourceTime);
#ifdef __cplusplus
}
#endif /* __cplusplus */