Files
redAcore/DeviceCore.cpp
Charl Wentzel a972fb9101 Major Update:
- Implement consistent Function addition to application
  - Use TYPE_XXX constants to declare function type
  - Use NewXXXX() methods to call constructor correctly
  - Add FunctionType list (with constructor) to Application
  - Create Function by comparison to FunctionType list
- Simplify LoadConfig() and Init() methods for functions
  - Combine methods into Init() method
  - Pass relevant data member to Init() method
  - Remove all CDataMember references on functions
- ApplicationCore:
  - Split ReadParam() method from LoadConfig() method
  - Split main configuration into separate files:
    - config/ - main config file, general application settings
    - definition/ - application definition, e.g. function blocks
  - Definition and Address List files specified in config file
  - Load address file in address/ branch
  - Made DataTree & JSONparser private
  - Made Config, Definition & Address branches public
  - Removed unnecessary branch references
  - Improved event logging
- DataTreeCore:
  - Allow GetChFirstChild & GetChElement to create parent branches
    with correct type, ie. Object/Array
  - Remove unnecessary Create param from GetXxx functions
  - Bug fix: Print empty objects/arrays correct, ie. empty brackets
  - Bug fix: Adding element at specific index
  - Bug fix: Error when get/create string value with "null"
- FunctionCore:
  - Type param now set as constant via constructor
  - Create empty Handles & Channels objects if none in Config
- SelectableCore:
  - Add Queue length parameter to handles for UNIX and TCP sockets
- DeviceCore:
  - Bug fix: missing Process() method
2018-11-24 13:35:23 +02:00

939 lines
27 KiB
C++

/*
* FunctionCore.cpp
*
* Created on: 18 May 2016
* Author: wentzelc
*/
//---------------------------------------------------------------------------
// Standard C/C++ Libraries
/* none */
// redA Libraries
#include "ApplicationCore.h"
#include "DeviceCore.h"
//---------------------------------------------------------------------------
// Global Vars
extern char * ProcessName;
//extern CApplication * Application;
//---------------------------------------------------------------------------
//CFunctionCore * NewDeviceCore( const char * Name ) {
// return (CFunctionCore*) new CDeviceCore( Name );
//}
//---------------------------------------------------------------------------
CDeviceCore::CDeviceCore( const char * pName, const char * pType ) : CFunctionCore( pName, pType )
{
// Clear Parameters
FirstDevice = NULL;
ActiveDevice = NULL;
// Standard channels
DeviceChannel = NULL;
CmdChannel = NULL;
// Polling
PollStep = 0;
PollInterval = 250;
SetStartTime( &PollWait );
WaitingForReply = false;
ReplyTimeout = 200;
PollRetry = 0;
MaxRetries = 3;
// Update Timer
SetUpdate( 0 );
}
//---------------------------------------------------------------------------
CDeviceCore::~CDeviceCore()
{
// Destroy Params
while (FirstDevice)
DestroyDevice( &FirstDevice );
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetPollParam( int pPollInterval )
{
PollInterval = pPollInterval;
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetReplyParam( int pReplyTimeout, int pMaxRetries )
{
ReplyTimeout = pReplyTimeout;
MaxRetries = pMaxRetries;
return true;
}
//---------------------------------------------------------------------------
// Generate events
bool CDeviceCore::SetUpdate( int pUpdateInterval )
{
UpdateInterval = pUpdateInterval;
SetStartTime( &UpdateTimeout );
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::DeviceOnline( TDevice * Device, bool Online )
{
// Validate
if (!Device)
return false;
// Check if change of state
if (Device->Online == Online)
return true;
// Log Event
Device->Online = Online;
if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Device '%s' %s",
ProcessName, Name, Device->Name, ((Online)? "online" : "offline") );
return true;
}
//---------------------------------------------------------------------------
void CDeviceCore::SetWaitForReply()
{
// Start timer
SetStartTime( &PollWait );
// Set flag
WaitingForReply = true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::CheckReplyTimeout( int TimeoutPollStep )
{
// Check for Reply timeout
if (WaitingForReply && Timeout( PollWait, ReplyTimeout ))
{
// Increment retries
PollRetry++;
// Handle No Reply / Retry
if (PollRetry < MaxRetries) {
// Log Event
if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Channel '%s' - %s timeout, retry [%d]",
ProcessName, Name, DeviceChannel->Name, ActiveDevice->Name, PollRetry );
}
else {
// Log Event
if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Channel '%s' - %s timeout, max [%d]",
ProcessName, Name, DeviceChannel->Name, ActiveDevice->Name, PollRetry );
// Set Device Offline
DeviceOnline( ActiveDevice, false );
PollStep = TimeoutPollStep;
}
// Reset flag - retry
WaitingForReply = false;
return true;
}
else
{
// Reset retries
PollRetry = 0;
return false;
}
}
//---------------------------------------------------------------------------
TDevice * CDeviceCore::AddDevice( const char * DeviceName )
{
// Get register or end of list
TDevice ** Device = &FirstDevice;
while (*Device && strcmp( DeviceName, (*Device)->Name ))
Device = &((*Device)->Next);
// Create if not found
if (!*Device) {
// Create register
*Device = (TDevice*)malloc( sizeof(TDevice) );
memset( *Device, 0x0, sizeof(TDevice) );
// Set Name
(*Device)->Name = (char *)malloc( strlen( DeviceName )+1 );
strcpy( (*Device)->Name, DeviceName );
}
// Report creation
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: Device added - '%s'",
ProcessName, Name, DeviceName );
return *Device;
}
//---------------------------------------------------------------------------
bool CDeviceCore::DestroyDevice( const char * DeviceName )
{
TDevice ** Device = NULL;
// Find device
if (!(Device = GetDevicePtr(DeviceName)))
return false;
// Destroy Device
if (!DestroyDevice( Device ))
return false;
// Device destroyed
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::DestroyDevice( TDevice ** Device )
{
TDevice * NextDevice = NULL;
// Validate param
if (!Device || !*Device)
return false;
// Save reference to next Device
NextDevice = (*Device)->Next;
// Destroy Device structure
if ((*Device)->Name)
free( (*Device)->Name );
// Destroy paramters
while ((*Device)->FirstParam) {
DestroyDeviceParam( &((*Device)->FirstParam) );
}
// Destory Device
free( *Device );
// Remove from list
*Device = NextDevice;
// Device successfully destroyed
return true;
}
//---------------------------------------------------------------------------
TDeviceParam * CDeviceCore::AddDeviceParam( TDevice * Device, const char * ParamName, EMBDataType DataType, int ParamLen )
{
// Get register or end of list
TDeviceParam ** Param = &Device->FirstParam;
while (*Param && strcmp( ParamName, (*Param)->Name ))
Param = &((*Param)->Next);
// Create if not found
if (!*Param) {
// Create register
*Param = (TDeviceParam*)malloc( sizeof(TDeviceParam) );
memset( *Param, 0x0, sizeof(TDeviceParam) );
// Set Name
(*Param)->Name = (char *)malloc( strlen( ParamName )+1 );
strcpy( (*Param)->Name, ParamName );
(*Param)->DataType = DataType;
// Init values
switch (DataType)
{
case dtUnsigned16 :
// Create Value pointer
(*Param)->Value = (u_int16_t*)malloc( sizeof(u_int16_t) );
*((u_int16_t*)(*Param)->Value) = 0;
(*Param)->Len = sizeof(u_int16_t);
// Create Set Value pointer
(*Param)->SetValue = (u_int16_t*)malloc( sizeof(u_int16_t) );
*((u_int16_t*)(*Param)->SetValue) = 0;
(*Param)->SetLen = sizeof(u_int16_t);
break;
case dtSigned16 :
// Create Value pointer
(*Param)->Value = (int16_t*)malloc( sizeof(int16_t) );
*((int16_t*)(*Param)->Value) = 0;
(*Param)->Len = sizeof(int16_t);
// Create Set Value pointer
(*Param)->SetValue = (int16_t*)malloc( sizeof(int16_t) );
*((int16_t*)(*Param)->SetValue) = 0;
(*Param)->SetLen = sizeof(int16_t);
break;
case dtUnsigned32 :
// Create Value pointer
(*Param)->Value = (u_int32_t*)malloc( sizeof(u_int32_t) );
*((u_int32_t*)(*Param)->Value) = 0;
(*Param)->Len = sizeof(u_int32_t);
// Create Set Value pointer
(*Param)->SetValue = (u_int32_t*)malloc( sizeof(u_int32_t) );
*((u_int32_t*)(*Param)->SetValue) = 0;
(*Param)->SetLen = sizeof(u_int32_t);
break;
case dtSigned32 :
// Create Value pointer
(*Param)->Value = (int32_t*)malloc( sizeof(int32_t) );
*((int32_t*)(*Param)->Value) = 0;
(*Param)->Len = sizeof(int32_t);
// Create Set Value pointer
(*Param)->SetValue = (int32_t*)malloc( sizeof(int32_t) );
*((int32_t*)(*Param)->SetValue) = 0;
(*Param)->SetLen = sizeof(int32_t);
break;
case dtFloat32 :
// Create Value pointer
(*Param)->Value = (float*)malloc( sizeof(float) );
*((float*)(*Param)->Value) = 0.0;
(*Param)->Len = sizeof(float);
// Create Set Value pointer
(*Param)->SetValue = (float*)malloc( sizeof(float) );
*((float*)(*Param)->SetValue) = 0.0;
(*Param)->SetLen = sizeof(float);
break;
case dtString :
// Create Value pointer
(*Param)->Len = ParamLen;
(*Param)->Value = (char*)malloc( ParamLen + 1 );
memset( (*Param)->Value, 0x0, (*Param)->Len+1 );
// Create Set Value pointer
(*Param)->SetLen = ParamLen;
(*Param)->SetValue = (char*)malloc( ParamLen + 1 );
memset( (*Param)->SetValue, 0x0, (*Param)->SetLen+1 );
break;
}
}
// Mark as updated
(*Param)->Changed = true;
// Report creation
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: Param added - '%s' (%s)",
ProcessName, Name, ParamName, DataTypeName[DataType] );
return *Param;
}
//---------------------------------------------------------------------------
bool CDeviceCore::DestroyDeviceParam( TDevice * Device, const char * ParamName )
{
TDeviceParam ** Param = NULL;
// Find param
if (!(Param = GetDeviceParamPtr( Device, ParamName )))
return false;
// Destroy Param
if (!DestroyDeviceParam( Param ))
return false;
// Parameter destroyed
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::DestroyDeviceParam( TDeviceParam ** Param )
{
TDeviceParam * NextParam = NULL;
// Validate param
if (!Param || !*Param)
return false;
// Save reference to next Param
NextParam = (*Param)->Next;
// Destroy Param structure
if ((*Param)->Name)
free( (*Param)->Name );
if ((*Param)->Value)
free( (*Param)->Value );
if ((*Param)->SetValue)
free( (*Param)->SetValue );
// Destroy Param
free( *Param );
// Remove from list
*Param = NextParam;
// Parameter successfully destroyed
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::UpdateUnsignedValue( TDeviceParam * Param, const u_int32_t Value, bool Init )
{
bool Changed = false;
// Validate
if (!Param)
return false;
switch (Param->DataType)
{
case dtUnsigned16 :
if (Init || (*((u_int16_t*)Param->Value) != Value))
{
// Set new value & mark change
*((u_int16_t*)Param->Value) = Value;
// Mark change & log event
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %u", ProcessName, Name,
Param->Name, ((Init)? "initialised" : "changed"), *((u_int16_t*)Param->Value) );
}
break;
case dtUnsigned32 :
if (Init || (*((u_int32_t*)Param->Value) != Value))
{
// Set new value & mark change
*((u_int32_t*)Param->Value) = Value;
// Mark change & log event
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %u", ProcessName, Name,
Param->Name, ((Init)? "initialised" : "changed"), *((u_int32_t*)Param->Value) );
}
break;
case dtFloat32 : // Special case, where float value is stored in Integer memory (e.g. modbus register)
if (Init || (*((u_int32_t*)Param->Value) != Value))
{
// Set new value & mark change
*((u_int32_t*)Param->Value) = Value;
// Mark change & log event
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %f", ProcessName, Name,
Param->Name, ((Init)? "initialised" : "changed"), *((float*)Param->Value) );
}
break;
default :
// Invalid Data Type
return false;
}
// Generate Channel Event
if (Changed) {
Param->Changed = true;
EventOutput( Param, Changed );
}
return Changed;
}
//---------------------------------------------------------------------------
bool CDeviceCore::UpdateSignedValue( TDeviceParam * Param, const int32_t Value, bool Init )
{
bool Changed = false;
// Validate
if (!Param)
return false;
switch (Param->DataType)
{
case dtSigned16 :
if (Init || (*((int16_t*)Param->Value) != Value))
{
// Set new value & mark change
*((int16_t*)Param->Value) = Value;
// Mark change & log event
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %d", ProcessName, Name,
Param->Name, ((Init)? "initialised" : "changed"), *((int16_t*)Param->Value) );
}
break;
case dtSigned32 :
if (Init || (*((int32_t*)Param->Value) != Value))
{
// Set new value & mark change
*((int32_t*)Param->Value) = Value;
// Mark change & log event
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %d", ProcessName, Name, Param->Name,
((Init)? "initialised" : "changed"), *((int32_t*)Param->Value) );
}
break;
default :
// Invalid Data Type
return false;
}
// Generate Channel Event
if (Changed) {
Param->Changed = true;
EventOutput( Param, Changed );
}
return Changed;
}
//---------------------------------------------------------------------------
bool CDeviceCore::UpdateFloatValue( TDeviceParam * Param, const float Value, bool Init )
{
bool Changed = false;
// Validate
if (!Param)
return false;
switch (Param->DataType)
{
case dtFloat32 :
if (Init || (*((float*)Param->Value) != Value))
{
// Set new value & mark change
*((float*)Param->Value) = Value;
// Mark change & log event
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %f", ProcessName, Name,
Param->Name, ((Init)? "initialised" : "changed"), *((float*)Param->Value) );
}
break;
default :
// Invalid Data Type
return false;
}
// Generate Channel Event
if (Changed) {
Param->Changed = true;
EventOutput( Param, Changed );
}
return Changed;
}
//---------------------------------------------------------------------------
bool CDeviceCore::UpdateStringValue( TDeviceParam * Param, const char * Value, const int Len, bool Init )
{
bool Changed = false;
// Validate
if (!Param || (Param->DataType != dtString))
return false;
switch (Param->DataType)
{
case dtString :
// Update register
if (Init || !CompareParamString( (char*)Param->Value, Param->Len, Value, Len))
{
if (Len >= Param->Len) {
// Copy full register length (ignore additional bytes)
memcpy( Param->Value, Value, Param->Len );
((char*)Param->Value)[ Param->Len ] = 0; // null terminate
}
else {
// Copy new value
memcpy( Param->Value, Value, Len );
// Zero pad remaining
memset( &((char*)Param->Value)[Len], 0x0, (Param->Len - Len + 1) ); // Add null teriminate
}
// Mark Change
Changed = true;
if (Log) Log->Message( LogLevel, dlLow, "%s/%s: '%s' %s - %s", ProcessName, Name,
Param->Name, ((Init)? "initialised" : "changed"), (char*)Param->Value );
}
break;
default :
// Invalid Data Type
return false;
}
// Generate Channel Event
if (Changed) {
Param->Changed = true;
EventOutput( Param, Changed );
}
return Changed;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetUnsignedValue( TDeviceParam * Param, const u_int32_t Value, bool Force )
{
// Validate
if (!Param)
return false;
switch (Param->DataType)
{
case dtUnsigned16 :
if (Force || (*((u_int16_t*)Param->SetValue) != Value))
{
// Set new value
*((u_int16_t*)Param->SetValue) = Value;
// Mark change
Param->SetChanged = true;
}
break;
case dtUnsigned32 :
if (Force || (*((u_int32_t*)Param->SetValue) != Value))
{
// Set new value
*((u_int32_t*)Param->SetValue) = Value;
// Mark change
Param->SetChanged = true;
}
break;
default :
// Invalid data
return false;
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetSignedValue( TDeviceParam * Param, const int32_t Value, bool Force )
{
// Validate
if (!Param)
return false;
switch (Param->DataType)
{
case dtSigned16 :
if (Force || (*((int16_t*)Param->SetValue) != Value))
{
// Set new value
*((int16_t*)Param->SetValue) = Value;
// Mark change
Param->SetChanged = true;
}
break;
case dtSigned32 :
if (Force || (*((int32_t*)Param->SetValue) != Value))
{
// Set new value
*((int32_t*)Param->SetValue) = Value;
// Mark change
Param->SetChanged = true;
}
break;
default :
// Invalid data
return false;
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetFloatValue( TDeviceParam * Param, const float Value, bool Force )
{
// Validate
if (!Param)
return false;
switch (Param->DataType)
{
case dtFloat32 :
if (Force || (*((float*)Param->SetValue) != Value))
{
// Set new value
*((float*)Param->SetValue) = Value;
// Mark change
Param->SetChanged = true;
}
break;
default :
// Invalid data
return false;
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetStringValue( TDeviceParam * Param, const char * Value, const int Len, bool Force )
{
// Validate
if (!Param || (Param->DataType != dtString) || (Len > Param->Len))
return false;
// Update register
if (Force || !CompareParamString( (char*)Param->Value, Param->Len, Value, Len))
{
if (Len >= Param->SetLen) {
// Copy full register length (ignore additional bytes)
memcpy( Param->SetValue, Value, Param->SetLen );
((char*)Param->SetValue)[ Param->SetLen ] = 0; // null terminate
}
else {
// Copy new value
memcpy( Param->SetValue, Value, Len );
// Zero pad remaining
memset( &((char*)Param->SetValue)[Len], 0x0, (Param->SetLen - Len + 1) ); // null terminate
}
// Mark Change
Param->SetChanged = true;
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetValue( TDeviceParam * Param, const char * Value, const int Len, bool Force )
{
u_int32_t UnsignedVal;
int32_t SignedVal;
float FloatVal;
char * TempStr;
bool UseTempStr = false;
// Validate
if (!Param || !Value || !Len)
return false;
// Check if string
if (Param->DataType == dtString)
{
// Simply set string
SetStringValue( Param, Value, Len, Force );
}
else
{
// Ensure string is zero terminated
if (Value[Len] != 0) {
TempStr = (char *)malloc( Len+1 );
memcpy( TempStr, Value, Len );
TempStr[Len] = 0;
UseTempStr = true;
}
// Convert to correct type
switch (Param->DataType)
{
case dtUnsigned16 :
case dtUnsigned32 :
UnsignedVal = (u_int32_t)strtoul( ((UseTempStr)? TempStr : Value), NULL, 10 );
SetUnsignedValue( Param, UnsignedVal, Force );
break;
case dtSigned16 :
case dtSigned32 :
SignedVal = (u_int32_t)strtol( ((UseTempStr)? TempStr : Value), NULL, 10 );
SetUnsignedValue( Param, SignedVal, Force );
break;
case dtFloat32 :
FloatVal = (u_int32_t) strtof( ((UseTempStr)? TempStr : Value), NULL );
SetUnsignedValue( Param, FloatVal, Force );
break;
default :
return false;
}
// Clear memory
if (UseTempStr) {
free( TempStr );
}
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::GetValue( TDeviceParam * Param, char * Value, int &Len )
{
// Validate
if (!Param || !Value)
return false;
// Check if return value longer than actual value
switch (Param->DataType)
{
case dtUnsigned16 :
sprintf( Value, "%u", (*((u_int16_t*)Param->Value)) );
break;
case dtUnsigned32 :
sprintf( Value, "%u", (*((u_int32_t*)Param->Value)) );
break;
case dtSigned16 :
sprintf( Value, "%d", (*((int16_t*)Param->Value)) );
break;
case dtSigned32 :
sprintf( Value, "%d", (*((int32_t*)Param->Value)) );
break;
case dtFloat32 :
sprintf( Value, "%f", (*((float*)Param->Value)) );
break;
case dtString :
if (Len < Param->Len)
{
// Only copy requested no of chars
memcpy( Value, Param->Value, Len );
Value[ Len ] = 0;
}
else
{
memcpy( Value, Param->Value, Param->Len );
Value[ Param->Len ] = 0;
}
break;
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::CompareParamString( const char * ParamValue, const int ParamLen, const char * Value, const int Len )
{
bool Match = true;
char * Pos = NULL;
// Compare Value against current value
if (Len >= ParamLen) {
// Compare full register length (ignore additional bytes)
if (memcmp( Value, ParamValue, ParamLen)) {
Match = false;
}
}
else {
// Compare length of new value only
if (memcmp( ParamValue, Value, Len)) {
Match = false;
}
// Remaining bytes must be null
else {
for (Pos = (char*)ParamValue; Pos < (char*)ParamValue + ParamLen; Pos++) {
if (*Pos != 0) {
Match = false;
break;
}
}
}
}
// Return result
return Match;
}
//---------------------------------------------------------------------------
bool CDeviceCore::SetParamScan( TDeviceParam * Param, bool Scan, const char * ChannelName, long pUpdateInterval )
{
// Validate
if (!Param)
return false;
// Set scan parameters
Param->Scan = Scan;
Param->EventChannel = GetChannel( ChannelName );
// Configure Update timer
if (pUpdateInterval) {
Param->UpdateInterval = pUpdateInterval;
SetStartTime( &(Param->UpdateTimeout) );
}
return true;
}
//---------------------------------------------------------------------------
bool CDeviceCore::TimedParamEvents()
{
TDevice * Device = NULL;
TDeviceParam * Param = NULL;
// Loop through all devices
Device = FirstDevice;
while (Device)
{
// Loop through all scan parameters
while ((Param = GetNextScanParam( Device, Param )))
{
// Generate timed events
EventOutput( Param, false );
}
Device = Device->Next;
}
return true;
}
//---------------------------------------------------------------------------
// Generate Event output
bool CDeviceCore::EventOutput( TDeviceParam * Param, bool Force )
{
// Validate
if (!Param || !(Param->EventChannel))
return false;
// Check Timer or force
if (Force ||
(Param->UpdateInterval && Timeout( Param->UpdateTimeout, Param->UpdateInterval )) )
{
// Post event
char Message[200];
switch (Param->DataType)
{
case dtUnsigned16 :
sprintf( Message, "%s: %u\n", Param->Name, *((u_int16_t*)Param->Value) );
break;
case dtUnsigned32 :
sprintf( Message, "%s: %u\n", Param->Name, *((u_int32_t*)Param->Value) );
break;
case dtSigned16 :
sprintf( Message, "%s: %d\n", Param->Name, *((int16_t*)Param->Value) );
break;
case dtSigned32 :
sprintf( Message, "%s: %d\n", Param->Name, *((int32_t*)Param->Value) );
break;
case dtFloat32 :
sprintf( Message, "%s: %f\n", Param->Name, *((float*)Param->Value) );
break;
case dtString :
sprintf( Message, "%s: %s\n", Param->Name, (char*)Param->Value );
break;
}
Output( Param->EventChannel, Message, strlen(Message) );
// Reset timer
if (Param->UpdateInterval) {
SetStartTime( &(Param->UpdateTimeout) );
}
}
return true;
}
//---------------------------------------------------------------------------