//
//  Copyright © 2004, RESON Inc. All Rights Reserved.
//
//  No part of this file may be reproduced or transmitted in any form or by
//  any means, electronic or mechanical, including photocopy, recording, or
//  information storage or retrieval system, without permission in writing
//  from RESON Inc.
//
//  Filename:   Logger.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    Implements the main 6046 worker class, CLogger and its associated
//              internal helpers.
//
//  Notes:      This class may be instantiated from any container to implement the
//              full Payload Controller functionality.

///////////////////////////////////////////////////////////////////////////////
// Include files.

#pragma warning( disable : 4786 )       // Disable this little gem from Microsoft. Keep
                                        // before the first header is included.

#include "StdAfx.h"
#include "Logger.h"

#include "Component.h"
#include "SurveyEventLog.h"
#include "ClientConnections.h"
#include "RegistryKeys.h"
#include "6046Options.h"
#include "HaspException.h"

#include "..\Utils\NetUtils\7kProtocol.h"
#include "..\Utils\NetUtils\7kCommand.h"
#include "..\Utils\NetUtils\7kAcknowlege.h"
#include "..\Utils\NetUtils\7kStatus.h"
#include "..\Utils\NetUtils\MiscUtils.h"
#include "..\Utils\NetUtils\Generic7kRecordEncoder.h"

#include <atlbase.h>
#include <stdio.h>
#include <math.h>

#include <algorithm>

///////////////////////////////////////////////////////////////////////////////
// Dongle enable/disable flags etc. FOR INTERNAL DEVELOPMENT ONLY!!!
//
// With the dongle enabled and running within the debugger, the HASP utilities will
// likely throw an exception thus causing this application to terminate.
// In this case, it is recommended the developer TEMPORARILY disable the dongle checking
// by enabling the defined constant DISABLE_DONGLE below. When this is done, messages
// during compile time will be emitted to notify the developer of this fact.

#if 1
#define DISABLE_DONGLE
#else
#undef  DISABLE_DONGLE
#endif

#if defined ( DISABLE_DONGLE )
#if defined ( _DEBUG )
#pragma CompileMessage_m( "CAUTION...DONGLE IS DISABLED !" )
#else
//#error THE DONGLE IS DISABLED. THIS IS NOT PERMITTED IN RELEASE MODE.
#endif
#elif defined ( _DEBUG )
#pragma CompileMessage_m( "DEBUGGING NOTE: Dongle checking is enabled -- you might not be able to debug effectively. See above notes." )
#endif

///////////////////////////////////////////////////////////////////////////////
// Non-standard libraies.

#pragma comment( lib, "shlwapi.lib" )                                                                                   // Import shlwapi.lib... needed to use PathFileExists().

///////////////////////////////////////////////////////////////////////////////
// Macros, constants, definitions and static member initialization.

namespace                                                                                                               // Begin anonymous namespace thus ensuring internal linkage.
{                                                                                                                       
    enum EEVENTLISTINDEX                                                                                                // Event id's (indicies into ahEventList[] of CLogger::WatchCycle() );
    {
        eventHealthCheckTimer               = 0,                                                                        // Indicates that it is time to check the health of the next sensor in the runlist.
        eventScheduleSensorOnline,                                                                                      // Next sensor in the defered sensor startup list is to be brought on-line.
        eventDongleCheck,                                                                                               // Period check for dongle presence.
        eventSensorDataAvailable,                                                                                       // Sensor data is available in the shared memory pool.
        eventTimeTransmission,

        // ... add new indicies here to maintain an accurate count.

        eventMaxEvents                                                                                                  // Number of kernel objects that are waitable.
    };

    enum ESIMULATECOMMAND
    {
        simulateCommandAlarms                  = 0,

        // ... add new simulate command mode indicies here to maintain an accurate count.

        simulateCommandMaxModes
    };

    //  Note:   Per agreement w/ Patrik in September 2002 (and after his reneging) an 8MByte size limitation
    //          has finally taken effect as of Data Format Document v0.51 (version 4 protocol).

    const unsigned long ulMax7kRecordSize_c                 = 8U * 1024U * 1024U;                                       // 8 MBytes.

    const float         fCriticalDiskspacePercent_c         = 10.0f;                                                    // Minimum disk space threshold to be critical.
    const float         fVersion_c                          = 3.0000f;                                                  // PLC's version number.

    const unsigned long ulDongleCheckPeriodInMilliseconds_c = 10UL * 1000UL;                                            // Dongle check period (10s) in ms.
    const unsigned long ulHostTimeNotificationPeriod_c      = 60UL * 1000UL;                                            // Emit a time message to connected clients every minute (approx).

    const unsigned int  uiDefaultListenPort_c               = 6046U;                                                    // 6046 is the default listener TCP/IP port.

    const int           iInvalidSensorIndex_c               = -1;                                                       // Sensor index is unknown or invalid.

    const LPCTSTR       lpszPLCConfigurationFileName_c      = _T( "PLCConfiguration.xml" );                             // Expected name of the configuration file.

    template<typename tCountType>
    class CCounter                                                                                                      // Helper to do instance couting.
    {
    public:

                                CCounter            (   void );
                                CCounter            (   const CCounter<tCountType> &rRhs );
        CCounter<tCountType> &  operator =          (   const CCounter<tCountType> &rRhs );

        virtual                ~CCounter            (   void );

        CCounter<tCountType> &  operator ++         (   int );                                                          // Postincrement operator.
        CCounter<tCountType> &  operator ++         (   void );                                                         // Preincrement  operator.

                                operator tCountType (   void ) const;                                                   // Conversion operator to extract the counter.
    private:

        tCountType              m_tCount;
    };
}                                                                                                                       // End unnamed namespace.

///////////////////////////////////////////////////////////////////////////////
// CLogger class implementation; starting with public members.

using namespace std;
using namespace N6046RegistryKeys;

CLogger::CLogger( PFN_TERMINATE_CALLBACK pfnTerminate )

        // Base class for thread processing; no time out (INFINITE), normal thread priority.

        :CBaseThread                        ( INFINITE, THREAD_PRIORITY_NORMAL ),

        // Internal constants... initializing values defined above.

         m_fVersion                         ( fVersion_c ),
         m_ulMaxLogBufferSize               ( sizeof( RECORDHEADER ) + ulMax7kRecordSize_c ),
         m_ulMaxMessageLength               ( ulMax7kRecordSize_c ),
         m_iInvalidSensorIndex              ( iInvalidSensorIndex_c ),
         m_uiDefaultListenPort              ( uiDefaultListenPort_c ),
         m_ulDongleCheckPeriodInMilliseconds( ulDongleCheckPeriodInMilliseconds_c ),
         m_ulHostTimeNotificationPeriod     ( ulHostTimeNotificationPeriod_c )
{
    m_bInitialized              = false;
    m_pfnTerminate              = pfnTerminate;
    m_bActiveOnStart            = false;                                // Default is to leave active sensors inactive on start (AUV case).

    m_pby7kRecordBuffer         = NULL;
    m_iShutdownMode             = 0;
    m_iRoutingSensorIndex       = m_iInvalidSensorIndex;

    m_uiListenPort              = m_uiDefaultListenPort;
    m_ulRecordCounter           = 0UL;
    m_ulSessionId               = 0UL;
    m_ulNextSensorRecordToRead  = 0UL;
    m_ulSensorCycleDongleCheck  = 0UL;
    m_ulDiskWriteFailCount      = 0UL;

    m_AppPath.Empty();
    m_UserNotes.Empty();
    m_UserName.Empty();

    m_uiDataReadyMessageId      = messageData;
    m_uiReplyReadyMessageId     = messageStatus;

    m_eTimeSyncMode             = timesyncModeStandAlone;

    m_KnownModules.clear();
    m_ModuleList.clear();
    m_RunList.clear();
    m_CommandQueue.clear();

    m_CommandHandler.SetParent( this );

    m_Alarm.SetThreadNotification( true, static_cast<unsigned long>( m_dwThreadID ), static_cast<unsigned int>( messageAlarmStateChange ) );

    m_SurveyEventLog.SetAlarmCallback( &m_Alarm );

    m_pClients                  = NULL;
    m_pSecondaryHandler         = NULL;
    m_pSensorData               = NULL;

    //m_TriggerController.SetCriticalParameterTable( &m_CriticalParameterTable );

    // Initiate our base class' contained thread watch procedure.

    if ( ResumeThread() == m_dwResumeFailCode )
    {
        TRACE( _T( "CLogger::CLogger(), ResumeThread() failed.\n" ) );
        return;
    }

    // We got this far so signal all's well.

    m_bInitialized = true;
}

CLogger::~CLogger( void )
{
    m_bInitialized = false;

    // Wait upto 10s for the logger main thread to terminate before forcing the issue.

    __TRY
    {
        if ( ! CBaseThread::TerminateThread( 10000 ) )
        {
            TRACE( _T( "CLogger::~CLogger(), TerminateThread() timed out or failed.\n" ) );
        }

        __TRY
        {
            if ( m_pClients != NULL )
            {
                delete m_pClients;
                m_pClients = NULL;
            }
        }
        __FINALLY
        {
            ASSERT( m_pClients == NULL );
            m_pClients = NULL;
        }
        __ENDFINALLY

        DestroyDataCollectionObjects();

        m_AppPath.Empty();
        m_ModuleList.clear();
        m_RunList.clear();
        m_KnownModules.clear();
    }
    __FINALLY
    {
        m_bActiveOnStart       = false;
        m_ulDiskWriteFailCount = 0UL;
    }
    __ENDFINALLY
}

bool CLogger::IsInitialized( void ) const
{
    return m_bInitialized;
}

bool CLogger::QueueCommand( const unsigned long             &rulCommand,
                            const int                       &riSensorIndex,
                            const unsigned long             &rulAction,
                            LPCTSTR                          lpszOptions,
                            const unsigned long             &rulTimeStamp,
                            const int                       &riSocketIndex )
{
    // Queues the command and timestamp ready for subsequent handling.

    CQueuedCommand Command( rulCommand, riSensorIndex, rulAction, lpszOptions, rulTimeStamp, riSocketIndex );

    m_CriticalCommandQueue.Enter();
    m_CommandQueue.push_back( Command );
    m_CriticalCommandQueue.Leave();

    if ( ! m_SurveyEventLog.ReportEvent( eventLogDiagnostic,
                                                  eventActionRemoteCommandRequest,
                                                      iUnspecified_c,
                                                          "Client %d, command: %lu",
                                                              riSocketIndex,
                                                                  Command.Command() ) )
    {
        TRACE( _T( "CLogger::QueueCommand(), m_SurveyEventLog.ReportEvent() failed\n" ) );
    }

    return ( PostThreadMessage( messageCommand ) != FALSE );
}

bool CLogger::Queue7kMessageForRouting( const BYTE          *pbyData,
                                        const unsigned long &rulNumBytes,
                                        const unsigned long &rulTimeStamp,
                                        const int           &riSocketIndex )
{
    bool bSuccess = false;

    if ( rulNumBytes == 0UL )
    {
        bSuccess = true;
    }
    else if ( pbyData != NULL )
    {
        // First, give the PLC the change to handle any pre-processing of the routed 7k message before trying
        // to dispatch it for routing.

        if ( ! PreProcessRouted7kMessage( pbyData, rulNumBytes, rulTimeStamp ) )
        {
            TRACE( _T( "CLogger::Queue7kMessageForRouting() failed\n" ) );
        }

        // Next, we deterine whether a sensor has been selected as the routing default and that it is
        // capable of accepting 7k records as input. If there is, we queue the record and signal the
        // main thread to process, otherwise, we simply ignore it.

        if ( IsRoutingSensorSet() )
        {
            // Queue the record.

            m_CriticalRoutedMessageQueue.Enter();

            try
            {
                CQueued7kMessage Message( rulTimeStamp, riSocketIndex );

                bSuccess = m_RoutingQueue.Add(  const_cast<BYTE *>( pbyData ), rulNumBytes, Message );
            }
            catch ( ... )
            {
                bSuccess = false;
            }

            m_CriticalRoutedMessageQueue.Leave();

            // Signal the main thread there is data ready to route to the sensor configured for routing.

            if ( bSuccess )
            {
                bSuccess = ( PostThreadMessage( messageRouted7kRecord ) != FALSE );
            }
        }
        else
        {
            bSuccess = true;
        }
    }
    else
    {
        bSuccess = false;
    }

    return bSuccess;
}

bool CLogger::DispatchToSubscribingClients( const BYTE             *pbyData,
                                            const unsigned long    &rulSize,
                                            const unsigned long    &rulTimestamp,
                                            const int              &riSensorIndex )
{
    // Dispatch data to connected clients directly.
    // NB: this method is invoked as a callback from the by the secondary processing thread; specifically,
    // the m_pSecondaryHandler object. This means that the m_pClients will be a shared resource and needs to 
    // synchonize threads accordingly.

    UNREFERENCED_PARAMETER( rulTimestamp );

    bool bSuccess = false;      // Assume failure for now.

    ASSERT( m_pClients != NULL );

    if ( ( m_pClients != NULL ) && ( rulSize > 0UL ) && ( pbyData != NULL ) )
    {
        // Given a sensor index and record type, send the data to subscribing clients.

        const unsigned long ulRecordType = C7kProtocol::RecordType( pbyData, rulSize );

        // Dispatch the message to each subscribing client's socket. Note that we use the list that's
        // maintained within the m_pClients object. 

        if ( ulRecordType == 0UL )
        {
            bSuccess = false;
            TRACE( _T( "CLogger::DispatchToSubscribingClients(), Invalid record or record type\n" ) );
        }
        else if ( m_pClients->SendToSubscribingClients( riSensorIndex, ulRecordType, pbyData, rulSize ) )
        {
            bSuccess = true;
        }
        else
        {
            bSuccess = false;
            TRACE( _T( "CLogger::DispatchToSubscribingClients(), Can't dispatch to subscribing client(s)\n" ) );
        }
    }

    return bSuccess;
}

void CLogger::SplitPath(    LPCTSTR     lpszFullName,
                            CString    &rPath,
                            CString    &rFileName )
{
    char    szDrive [ _MAX_DRIVE ],
            szPath  [ _MAX_DIR   ],
            szFile  [ _MAX_FNAME ],
            szExt   [ _MAX_EXT   ];

    _splitpath( lpszFullName, szDrive, szPath, szFile, szExt );

    rPath.Format( "%s\\%s", szDrive, szPath );
    rFileName = szFile;
}

void CLogger::MakePath(     CString    &rFullName,
                            LPCTSTR     lpszDrive,
                            LPCTSTR     lpszPath,
                            LPCTSTR     lpszFile,
                            LPCTSTR     lpszExtension )
{
    _makepath( rFullName.GetBuffer( _MAX_PATH ), lpszDrive, lpszPath, lpszFile, lpszExtension );
    rFullName.ReleaseBuffer();
}

bool CLogger::SetApplicationPath( CString &rAppPath )
{
    bool bSuccess = false;

    try
    {
        rAppPath.Empty();

        LPSTR pszAppPath = rAppPath.GetBuffer( _MAX_PATH );

        ::GetModuleFileName( NULL, pszAppPath, _MAX_PATH );

        int iLength = 0;

        if ( ( iLength = static_cast<int>( strlen( pszAppPath ) ) ) > 0 )
        {
            while ( --iLength )
            {
                if ( ( pszAppPath[ iLength ] == '\\' ) ||
                     ( pszAppPath[ iLength ] == ':' )   )
                {
                    pszAppPath[ iLength + 1 ] = '\0';
                    break;
                }
            }
        }

        rAppPath.ReleaseBuffer();

        bSuccess = ( rAppPath.GetLength() > 0 );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::SetApplicationPath(), Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

//////////////////////////////////////////////////////////////////////
// Private members.

BOOL CLogger::ProcessMessage( MSG *psMsg )
{
    // Override for CBaseThread::ProcessMessage()

    BOOL bMessageProcessed = FALSE;

    //TRACE( _T( "CLogger::ProcessMessage(), Message %u received.\n" ), psMsg->message );

    switch ( psMsg->message )
    {
        case messageCommand:

            ProcessQueuedCommands();
            bMessageProcessed = TRUE;
            break;

        case messageStatus:

            ProcessStatusMessage( static_cast<int>( psMsg->wParam ), static_cast<int>( psMsg->lParam ) );
            bMessageProcessed = TRUE;
            break;

        case messageData:

            // Obsolete.

            bMessageProcessed = TRUE;
            break;

        case messageShutdown:

            PrepareForShutdown( static_cast<int>( psMsg->wParam ));
            bMessageProcessed = TRUE;
            break;

        case messageRouted7kRecord:

            Route7kMessagesToSensor();
            bMessageProcessed = TRUE;
            break;

        case messageAlarmStateChange:

            ProcessAlarmMessage( static_cast<EALARMID>( psMsg->wParam ), static_cast<int>( psMsg->lParam ) );
            bMessageProcessed = TRUE;
            break;

        case messageKillClientConnection:

            TerminateClientConnection( static_cast<int>( psMsg->wParam ) );
            bMessageProcessed = TRUE;
            break;

        case messageQuerySensorSettings:

            QuerySensorSettings();
            bMessageProcessed = TRUE;
            break;

        default:

            // Give the base class the chance to handle the unknown or uninteresting message.

            bMessageProcessed = CBaseThread::ProcessMessage( psMsg );
            break;
    }

    return bMessageProcessed;
}

void CLogger::WatchCycle( void )
{
    // Base class override for main thread processing.

    __try
    {
        // Thread object creation and initialization.

        if ( ! InitializeThreadObjects() )
        {
            TRACE( _T( "CLogger::WatchCycle(), InitializeThreadObjects() failed\n" ) );
            __leave;
        }

        // Setup our waitable kernel object handle array and its size.

        HANDLE  ahEventList[] = {
                                    static_cast<HANDLE>( m_SystemMonitor ),             // Scheduled system health check (waitable timer).
                                    static_cast<HANDLE>( m_SensorScheduler ),           // Scheduled sensor startup (waitable timer).
                                    static_cast<HANDLE>( m_DongleCheckScheduler ),      // Scheduled dongle check (waitable timer).
                                    static_cast<HANDLE>( *m_pSensorData ),              // Event for sensor data being available in the shared memory pool (named event).
                                    static_cast<HANDLE>( m_TimeEmitter )                // Scheduled time message emission to connected hosts (waitable timer).

                                    // Add new handles here and their id's (element index in this array)
                                    // in the EEVENTLISTINDEX enumeration list at the top of this file.
                                };

        const DWORD dwEventCount  = static_cast<DWORD>( DimOf_m( ahEventList ) );

        // Check for event array dimension / enum mismatch.

        ASSERT( dwEventCount == static_cast<DWORD>( eventMaxEvents ) );

        while ( IsActive() )
        {
            // Check to see if there are queued data in the shared memory pool incase we've missed
            // its associated pulsed event.

            if ( ! HandleSensorData() )
            {
                TRACE( _T( "CLogger::WatchCycle(), HandleSensorData() failed!\n" ) );
                ASSERT( false );
            }

            // Block this thread and wait for any significant event or message to occur.

            DWORD dwSignalState = ::MsgWaitForMultipleObjects(  dwEventCount,           // Number of waitable kernel objects (events, timers etc) to wait for.
                                                                &ahEventList[ 0 ],      // Handle array.
                                                                FALSE,                  // Wait for any one of the specified event types.
                                                                m_dwDwellPeriod,        // Timeout processing period in ms; see constructor initializer list for CBaseThread().
                                                                QS_ALLINPUT );          // Unblock on any message in the thread's queue.

            // The following priority scheme applies:
            //
            //  1) Termination request.
            //  2) Queued thread messages.
            //  3) Win32 kernel object events.
            //  4) Timeout processing (not relevant here).

            if ( IsInactive() )
            {
                break;
            }
            else if ( dwSignalState == ( WAIT_OBJECT_0 + dwEventCount ) )
            {
                PumpMessages();
            }
            else if ( ( dwEventCount  >  0 )             &&
                      ( dwSignalState >= WAIT_OBJECT_0 ) &&
                      ( dwSignalState <= ( WAIT_OBJECT_0 + dwEventCount - 1 ) ) )
            {
                ProcessEvents( &ahEventList[ 0 ], dwSignalState - WAIT_OBJECT_0 );
            }
            else if ( dwSignalState == WAIT_TIMEOUT )
            {
                // ::MsgWaitForMultipleObjects() must have timed out.
                // For now, we shouldn't get here because the timeout period is INFINITE.
                // See CBaseThread initializer list of CLogger::CLogger().

                TRACE( _T( "CLogger::WatchCycle(), MsgWaitForMultipleObjects() timed out!\n" ) );
            }
            else
            {
                // Must be an abandoned mutex.  We shouldn't get here unless things are hideously pair shaped.

                TRACE( _T( "CLogger::WatchCycle(), Abandoned mutex !!!\n" ) );
                ASSERT( false );
            }
        }
    }
    __finally
    {
        // Ensure we always attempt to clean up.

        if ( ! TerminateThreadObjects() )
        {
            TRACE( _T( "CLogger::WatchCycle(), TerminateThreadObjects() failed" ) );
        }
    }

    TRACE( _T( "CLogger::WatchCycle(), About to terminate \n" ) );
}

inline
bool CLogger::InitializeThreadObjects( void )
{
    bool bSuccess = false;

    try
    {
        // Set the application's.

        SetApplicationPath( m_AppPath );

        // Start up the survey event log.

        if ( ! m_SurveyEventLog.Start( m_AppPath ) )
        {
            ThrowMessage_m( "Survey event log failed to start" );
        }

        // Check that a valid dongle key is present (terminate's program if not found).

        if ( ! ValidateDongle() )
        {
            TRACE( _T( "CLogger::InitializeThreadObjects(), Dongle is invalid; application will terminate.\n" ) );
        }

        // Create the various internal objects necessary for data collection.

        if ( ! CreateDataCollectionObjects() )
        {
            ThrowMessage_m( "CreateDataCollectionObjects() failed" );
        }

        // Load the possible sensor module list and load those chosen against our runlist.

        if ( ! LoadConfiguration( lpszPLCConfigurationFileName_c ) )
        {
            ThrowMessage_m( "LoadConfiguration(), Unable to (correctly) load configuration file" );
        }

        if ( ! LoadModules() )
        {
            ThrowMessage_m( "LoadModules() failed" );
        }

        // In auto-start mode, we start logging and engage active sensors on startup.
        // First though, we must get the state in order to setup the sensor info structures
        // (used to know whether sensor is to go active during its startup). Only then can we build
        // the runlist.

        bool bActivateOnStart = IsActivateOnStart();

        m_bActiveOnStart = bActivateOnStart;

        TRACE( _T( "Activate on start: %d\n" ), ( bActivateOnStart ? 1 : 0 ) );

        // Set miscellaneous PLC attribtues from the registry. E.g., session id, user name, user notes etc.
        // For now, 

        Set7kFileHeaderAttributesFromRegistry();

        // Build the sensor run list.

        if ( ! BuildRunList() )
        {
            ThrowMessage_m( "Failed to build runlist" );
        }

        // Set the time sync mode from the registry, if possible.

        SetTimeSynchronizationMode();

        // Start the sensors in the runlist and schedule those with delayed startup periods to come online.

        if ( ! StartSensors() )
        {
            ThrowMessage_m( "Failed to start sensors in runlist" );
        }

        // Restore the previous sensor command routing from the registry, if possible.

        RestoreMessageRoutingFromRegistry();

        // Now switch on recording after the sensors have started, if flagged to do so from the registry settings (see
        // also IsActivateOnStart() called above). Note that the sensors will pass back various device id information 
        // after they've started that we need to build the 7k file header.

        if ( bActivateOnStart )
        {
            if ( ! Record( true ) )
            {
                ThrowMessage_m( "Failed to activate logging mode at startup" );
            }
        }

        // Configure the auto-health checking object. PerformSystemCheck() is the callback
        // to be periodically executed by this object.

        if ( ! m_SystemMonitor.Configure( PerformSystemCheck, this ) )
        {
            ThrowMessage_m( "Failed to configure system monitor" );
        }

        // Start the dongle checking scheduler.

        m_DongleCheckScheduler.Start( m_ulDongleCheckPeriodInMilliseconds );

        // Start the socket server to permit remote connections.

        if ( ! EnableClientConnections() )
        {
            ThrowMessage_m( "Failed to start socket server for top-side connections" );
        }

        // Start the connected host time message emission scheduler.

        m_TimeEmitter.Start( m_ulHostTimeNotificationPeriod );

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::InitializeThreadObjects(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::InitializeThreadObjects(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::TerminateThreadObjects( void )
{
    bool bSuccess = false;      // Assume failure for now.

    try
    {
        DisableClientConnections( true );

        ShutdownRunListItems();

        UnloadModules();

        DestroyDataCollectionObjects();

        m_SurveyEventLog.Stop();

        Shutdown();

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::TerminateThreadObjects(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::TerminateThreadObjects() unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::InitializeSensorDataPool( void )
{
    bool bSuccess = false;

    try
    {
        ASSERT( m_pSensorData == NULL );

        if ( ( m_pSensorData = new CPLCReadSensorData() ) == NULL )
        {
            ThrowMessage_m( "Can't construct sensor data pool object" );
        }

        if ( ! m_pSensorData->IsInitialized() )
        {
            ThrowMessage_m( "Sensor data pool object wasn't constructed correctly" );
        }

        if ( ! m_pSensorData->ResetMemoryPool() )
        {
            ThrowMessage_m( "Can't initialize sensor data pool" );
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::InitializeSensorDataPool(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::InitializeSensorDataPool(), Unspecified exception caught." ) );
    }

    return bSuccess;
}

inline
bool CLogger::CreateDataCollectionObjects( void )
{
    bool bSuccess = false;

    try
    {
        if ( ( m_pby7kRecordBuffer = new BYTE [ m_ulMaxLogBufferSize ] ) == NULL )
        {
            ThrowMessage_m( "m_pbyCollection buffer is NULL" );
        }

        if ( ! InitializeSensorDataPool() )
        {
            ThrowMessage_m( "Can't construct or initialize sensor data pool" );
        }

        if ( ( m_pSecondaryHandler = new CSecondaryHandling( this ) ) == NULL )
        {
            ThrowMessage_m( "m_pSecondaryHandler is NULL" );
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CreateDataCollectionObjects(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CreateDataCollectionObjects(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::DestroyDataCollectionObjects( void )
{
    bool bSuccess = false;

    __TRY
    {
        if ( m_pSecondaryHandler != NULL )
        {
            delete m_pSecondaryHandler;
            m_pSecondaryHandler = NULL;
        }

        if ( m_pSensorData != NULL )
        {
            delete m_pSensorData;
            m_pSensorData = NULL;
        }

        if ( m_pby7kRecordBuffer != NULL )
        {
            delete [] m_pby7kRecordBuffer;
            m_pby7kRecordBuffer = NULL;
        }

        bSuccess = true;
    }
    __FINALLY
    {
        ASSERT( m_pSecondaryHandler == NULL );
        ASSERT( m_pSensorData       == NULL );
        ASSERT( m_pby7kRecordBuffer == NULL );

        m_pSecondaryHandler = NULL;
        m_pSensorData       = NULL;
        m_pby7kRecordBuffer = NULL;
    }
    __ENDFINALLY

    return bSuccess;
}

inline
bool CLogger::LoadConfiguration( const LPCTSTR lpszPLCConfigFileName )
{
    bool bLoaded = false;

    try
    {
        CString FileName( lpszPLCConfigFileName );
        bLoaded = ( ( FileName.IsEmpty() ) ? false : m_Config.ParseFile( m_AppPath + FileName ) );

        if ( ! bLoaded )
        {
            if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                     eventActionAlarmSet,
                                                         alarmIdLoadConfigurationFailed,
                                                             "The PLC could lot load a valid configuration" ) )
            {
                TRACE( _T( "CLogger::LoadConfiguration(), Can't report load configuration failed alarm" ) );
            }
        }
    }
    catch ( ... )
    {
        bLoaded = false;
        TRACE( _T( "CLogger::LoadConfiguration(), Unspecified exception caught.!\n" ) );
    }

    ASSERT( bLoaded );

    return bLoaded;
}

inline
bool CLogger::LoadModules( void )
{
    // Load the known module list then locate thoes in the .exe file's folder. The modules don't get loaded
    // until we're going to run the corresponding sensor.

    bool bSuccess = false;

    try
    {
        m_ModuleList.clear();

        // Firstly, load the known module list. A default list is used if one isn't found at runtime.

        LoadKnownModuleList();

        // Next, scan the app path for candicate dll's and see which out of those available are to be loaded.

        CFileFind FileFind;
        CString   FullSearchPattern( m_AppPath + _T( "*.dll" ) );
        BOOL      bWorking = FileFind.FindFile( FullSearchPattern );

        while ( bWorking )
        {
            bWorking = FileFind.FindNextFile();

            // Skip "." and ".." files otherwise, we'd recurse infinitely.

            if ( ( ! FileFind.IsDots()      ) &&
                 ( ! FileFind.IsDirectory() ) &&
                 ( ! FileFind.IsReadOnly()  ) &&
                 ( ! FileFind.IsHidden()    ) &&
                 ( ! FileFind.IsSystem()    )  )
            {
                CString FileName = FileFind.GetFileName();

                if ( ! FileName.IsEmpty() )
                {
                    FileName.MakeLower();

                    int iIndex = -1;

                    if ( IsRecognizedModule( FileName, iIndex ) )
                    {
                        CModuleItem ModuleItem( m_KnownModules[ iIndex ] );
                        m_ModuleList.push_back( ModuleItem );
                    }
                }
            }
        }

        FileFind.Close();

        ASSERT( ! m_ModuleList.empty() );       // Module list shouldn't be empty.

        if ( ! m_ModuleList.empty() )
        {
            // Sort the list by index.

            m_ModuleList.sort();

            // Now load the respective DLLs and map their interfaces.

            ModuleListIterator_t pModule = m_ModuleList.begin();

            while ( pModule != m_ModuleList.end() )
            {
                if ( ! pModule->Load( m_AppPath ) )
                {
                    TRACE( _T( "Can't load module (%d), %s\n" ), pModule->m_iModuleNumber, pModule->m_szModuleName );
                    m_ModuleList.erase( pModule );
                }

                pModule++;
            }
        }

        bSuccess = true;
    }
    catch ( ... )
    {
        bSuccess = false;
    }

    return bSuccess;
}

inline
bool CLogger::IsRecognizedModule( const CString &rFileName, int &riIndex ) const
{
    bool bRecognized = false;

    riIndex = -1;

    int iListIndex = 0;

    for ( KnownModulesIterator_t pModule = m_KnownModules.begin(); pModule != m_KnownModules.end(); pModule++, iListIndex++ )
    {
        CString SubString( pModule->m_szFileName );

        SubString.MakeLower();

        if ( rFileName.Find( SubString, 0 ) != -1 )
        {
            riIndex = iListIndex;
            bRecognized = true;
            break;
        }
    }

    return bRecognized;
}

inline
void CLogger::LoadKnownModuleList( void )
{
    try
    {
        m_KnownModules.clear();

        // Load the modules dynamically from the external source else use a default if that fails.

        if ( ! LoadModulesFromConfiguration() )
        {
            TRACE( _T( "CLogger::LoadKnownModuleList(), Using default module list!\n" ) );

            const MODULE asKnownModules[] =
            {
                { 0, 0, "EdgeTechFSDW.dll",     "EdgeTech FS-DW System"      },
                { 1, 0, "RESON7kSonar.dll",     "RESON 7k Sonar"             },
                { 2, 0, "BlueFinVC.dll",        "BlueFin Vehicle Controller" },
                { 3, 0, "RESON7kGeneric.dll",   "RESON 7k Generic Interface" }

#pragma CompileMessage_m( "MAINTENANCE NOTE: DEFAULT MODULE LIST SHOULD REFLECT THE CURRENTLY PUBLISHED EXTERNAL LIST." )

            };

            for ( int iModule = 0; iModule < DimOf_m( asKnownModules ); iModule++ )
            {
                m_KnownModules.push_back( asKnownModules[ iModule ] );
            }

            ASSERT( ! m_KnownModules.empty() );
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::LoadKnownModuleList(), Unspecified exception caught!\n" ) );
    }
}

inline
bool CLogger::LoadModulesFromConfiguration( void )
{
    bool bLoaded = false;

#pragma CompileMessage_m( "TODO: NEED TO TIE MODULES TO DONGLE CAPABILITIES ONCE DONGLE ISSUE IS SETTLED" )

    try
    {
        ASSERT( m_KnownModules.empty() );

        for ( unsigned long ulIndex = 0UL; ulIndex < m_Config.NumberOfModules(); ulIndex++ )
        {
            MODULE sModule = { 0 };

            sModule.m_iModuleNumber = static_cast<int>( m_Config.ModuleNumber( ulIndex ) );

            strncpy( sModule.m_szFileName,   m_Config.ModuleName( ulIndex ),        _MAX_FNAME - 1 );
            strncpy( sModule.m_szModuleName, m_Config.ModuleDescription( ulIndex ), _MAX_PATH  - 1 );

            if ( strlen( sModule.m_szFileName ) > 0 )
            {
                m_KnownModules.push_back( sModule );
            }
        }

        bLoaded = ! m_KnownModules.empty();
    }
    catch ( ... )
    {
        bLoaded = false;
        TRACE( _T( "CLogger::LoadModulesFromConfiguration(), Unspecified exception caught.!\n" ) );
    }

    return bLoaded;
}

inline
bool CLogger::EnableClientConnections( void )
{
    bool bSuccess = false;

    if ( m_pClients != NULL )
    {
        delete m_pClients;
        m_pClients = NULL;
    }

    // Get the remote control port from the registry, if not then default to 6046.

    unsigned int uiRemoteListenPort = m_uiDefaultListenPort;

    CRegKey Key;
        
    Key.Attach( hKey6046_c );

    if ( Key.Open( hKey6046_c, lpsz6046RegistryKey_c ) == ERROR_SUCCESS )
    {
        DWORD dwValue = 0UL;

        if ( Key.QueryValue( dwValue, lpszRemoteControlPortKey_c ) == ERROR_SUCCESS )
        {
            uiRemoteListenPort = static_cast<unsigned int>( dwValue );
        }

        Key.Close();
    }

    m_uiListenPort = uiRemoteListenPort;

    m_pClients = new CClientConnections( this, m_ulMaxMessageLength, m_uiListenPort );

    if ( m_pClients != NULL )
    {
        // Permit the secondary handler to start processing data records now that the socket
        // server is constructed etc.

        ASSERT( m_pSecondaryHandler != NULL );

        if ( m_pSecondaryHandler != NULL )
        {
            m_pSecondaryHandler->ThreadProcessing( true );
        }

        bSuccess = true;
    }

    return bSuccess;
}

inline
bool CLogger::DisableClientConnections( const bool &rbNotifyAllClients )
{
    if ( rbNotifyAllClients )
    {
        if ( ! NotifyClientsOfTermination() )
        {
            TRACE( _T( "CLogger::DisableClientConnections(), NotifyClientsOfTermination() failed\n" ) );
        }
    }

    // Stop the secondary handler from processing sensor data thus accessing a client connection
    // that is about to be destroyed.

    ASSERT( m_pSecondaryHandler != NULL );

    if ( m_pSecondaryHandler != NULL )
    {
        m_pSecondaryHandler->ThreadProcessing( false );
    }

    if ( m_pClients != NULL )
    {
        delete m_pClients;
        m_pClients = NULL;
    }

    return true;
}

inline
bool CLogger::BuildRunList( void )
{
    // Here we clear the runlist and construct an new one.

    bool bSuccess = false;

    try
    {
        m_RunList.clear();
        m_SensorScheduler.Reset();

        const unsigned long ulRunListSize = m_Config.NumberOfSensors();

        TRACE( _T( "CLogger::BuildRunList(), Run list items: %lu\n" ), ulRunListSize );

        // Note: here we're re-assigning the sensor indicies from scratch so we need not 
        // invoke NextAvailableSensorIndex() although we certainly could.

        for ( unsigned long ulEntry = 0UL; ulEntry < ulRunListSize; ulEntry++ )
        {
            const unsigned long  ulModule        = m_Config.SensorModuleId( ulEntry );
            const unsigned long  ulSubsystemId   = m_Config.SubsystemId( ulEntry );
            const int            iStartDelay     = m_Config.Delay( ulEntry );
            const unsigned long  ulDeviceId      = m_Config.DeviceId( ulEntry );
            const unsigned short unEnumeration   = m_Config.Enumerator( ulEntry );
            const LPCTSTR        lpszDescription = m_Config.SensorDescription( ulEntry );

            TRACE( _T( "Entry %lu, Module: %lu, Subsystem: %lu, StartDelay: %d, DeviceId: %lu, Enum: %d\"%s\"\n" ), ulEntry,
                                                                                                                    ulModule,
                                                                                                                    ulSubsystemId,
                                                                                                                    iStartDelay,
                                                                                                                    ulDeviceId,
                                                                                                                    unEnumeration,
                                                                                                                    lpszDescription );

            // NB:  1) Constructor for SENSORINFO will zero out its contents.
            //      2) Subordinate sensor object should assign items not set here.

            SENSORINFO sSensorInfo;

            strncpy( sSensorInfo.m_szName, lpszDescription, processInfoNameLength - 1 );

            sSensorInfo.m_iSensorIndex       = static_cast<int>( ulEntry );             // Sensor index (dynamic list of mapped sensors).
            sSensorInfo.m_iSubsystemId       = static_cast<int>( ulSubsystemId );       // Sensor subsystem id.
            sSensorInfo.m_ulDeviceId         = ulDeviceId;
            sSensorInfo.m_unSystemEnumerator = unEnumeration;
            sSensorInfo.m_iStartDelay        = iStartDelay;                             // Kept so that the PLC can write out when requested.
            sSensorInfo.m_iActivateOnStart   = ( m_bActiveOnStart ? 1 : 0 );            // 0 => Sensor does NOT go active when it starts, active otherwise.

            if ( iStartDelay == 0 )
            {
                CRunItem RunListItem( static_cast<int>( ulModule ), &sSensorInfo );
                m_RunList.push_back( RunListItem );
            }
            else if ( iStartDelay > 0 )
            {
                m_SensorScheduler.Queue( iStartDelay, static_cast<int>( ulModule ), sSensorInfo );
            }
            else
            {
                TRACE( _T( "Sensor entry ignored\n" ) );                // (-)ve, therefore ignored.
            }
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::BuildRunList(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::BuildRunList(), Unspecified exception caught!!\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::StartSensors( void )
{
    if ( m_RunList.empty() )
    {
        TRACE( _T( "CLogger::StartSensors(), No sensors in runlist to start\n" ) );
    }
    else
    {
        m_RunList.sort();

        for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
        {
            CComponent *pComponent = NULL;
            CSensor    *pSensor    = NULL;

            if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
                 SensorFromRunListItem   ( pRunListItem, pSensor    )  )
            {
                SENSORINFO *psSensorInfo = static_cast<SENSORINFO *>( *pSensor );

                const int iSensorIndex = psSensorInfo->m_iSensorIndex;

                if ( pComponent->Startup( psSensorInfo, m_dwThreadID, m_uiDataReadyMessageId, m_uiReplyReadyMessageId, m_SurveyEventLog.ReportEvent, NULL /*&m_CriticalParameterTable*/ ) == ERROR_CODE_FAILURE )
                {
                    TRACE( _T( "CLogger::StartSensors(), Sensor %d failed to start\n" ), iSensorIndex );

                    if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                            eventActionAlarmSet,
                                                                alarmIdSensorFailedToStart,
                                                                    "%d, Sensor failed to correctly start", iSensorIndex ) )
                    {
                        TRACE( _T( "CLogger::StartSensors(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                    }

                    const int iFailRestartPeriod = m_Config.FailRestart( iSensorIndex );

                    if (  iFailRestartPeriod > 0 )
                    {
                        TRACE( _T( "CLogger::StartSensors(), Sensor %d restart to be attmpted (%ds).\n" ), iSensorIndex, iFailRestartPeriod );

                        m_SensorScheduler.Queue( iFailRestartPeriod, m_Config.SensorModuleId( iSensorIndex ), *psSensorInfo );
                    }
                }
            }
        }

        SetFileHeaderAndEnumerationInfo();
    }

    m_SensorScheduler.Start();

    return true;
}

inline
bool CLogger::ShutdownRunListItems( void )
{
    // Shut down the sensors (in the runlist) in the reverse order to which they were started.

    for ( RunListReverseIterator_t pRunListItem = m_RunList.rbegin(); pRunListItem != m_RunList.rend(); pRunListItem++ )
    {
        CComponent *pComponent = NULL;
        CSensor    *pSensor    = NULL;

        if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
             SensorFromRunListItem   ( pRunListItem, pSensor    )  )
        {
            if ( pComponent->Shutdown( pSensor->m_iSensorIndex ) == ERROR_CODE_FAILURE )
            {
                TRACE( _T( "CLogger::ShutdownRunListItems(), Sensor %d failed to shutdown correctly\n" ), pSensor->m_iSensorIndex );

                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                        eventActionAlarmSet,
                                                            alarmIdSensorFailedToShutdown,
                                                                "%d, Sensor failed to shut down", pSensor->m_iSensorIndex ) )
                {
                    TRACE( _T( "CLogger::ShutdownRunListItems(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                }
            }
        }
    }

    // Finally, destroy the run list.

    m_RunList.clear();

    return true;
}

inline
bool CLogger::ModuleFromRunListItem( const RunListIterator_t pRunListItem, ModuleListIterator_t &rpModule ) const
{
    rpModule = std::find( m_ModuleList.begin(), m_ModuleList.end(), pRunListItem->m_iModuleNumber );

    return ( rpModule != m_ModuleList.end() );
}

inline
bool CLogger::ComponentFromRunListItem( const RunListIterator_t pRunListItem, CComponent * &rpComponent ) const
{
    rpComponent = NULL;

    ModuleListIterator_t pModule;

    if ( ModuleFromRunListItem( pRunListItem, pModule ) )
    {
        rpComponent = pModule->m_pComponent;
    }

    return ( rpComponent != NULL );
}

inline
bool CLogger::ComponentFromRunList( const int   &riSensorIndex,
                                    CComponent *&rpComponent )
{
    // Search through the run list and find the sensor with SENSORINFO::iSensorIndex
    // element == riSensorIndex. Then, look up correspinding module index and find
    // corresponding module item with same module index from module list.

    bool bSuccess = false;

    RunListIterator_t pRunListItem;

    for ( pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
    {
        if ( pRunListItem->m_Sensor.m_iSensorIndex == riSensorIndex )
        {
            bSuccess = ComponentFromRunListItem( pRunListItem, rpComponent );
            break;
        }
    }

    return bSuccess;
}

inline
bool CLogger::SensorFromRunListItem( const RunListIterator_t pRunListItem, CSensor * &rpSensor ) const
{
    rpSensor = NULL;

    if ( pRunListItem != m_RunList.end() )
    {
        rpSensor = &(pRunListItem->m_Sensor);
    }

    return ( rpSensor != NULL );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Overloads to handle reverse iteration.

inline
bool CLogger::ModuleFromRunListItem( const RunListReverseIterator_t pRunListItem, ModuleListIterator_t &rpModule ) const
{
    rpModule = find( m_ModuleList.begin(), m_ModuleList.end(), pRunListItem->m_iModuleNumber );

    return ( rpModule != m_ModuleList.end() );
}

inline
bool CLogger::ComponentFromRunListItem( const RunListReverseIterator_t pRunListItem, CComponent * &rpComponent ) const
{
    rpComponent = NULL;

    ModuleListIterator_t pModule;

    if ( ModuleFromRunListItem( pRunListItem, pModule ) )
    {
        rpComponent = pModule->m_pComponent;
    }

    return ( rpComponent != NULL );
}

inline
bool CLogger::SensorFromRunListItem( const RunListReverseIterator_t pRunListItem, CSensor * &rpSensor ) const
{
    rpSensor = NULL;

    if ( pRunListItem != m_RunList.rend() )
    {
        rpSensor = &(pRunListItem->m_Sensor);
    }

    return ( rpSensor != NULL );
}

inline
bool CLogger::SensorAndComponentFromIndex(  const int      &riSensorIndex,
                                            CComponent *   &rpComponent,
                                            CSensor    *   &rpSensor    )   const
{
    bool bFound = false;

    rpComponent = NULL;
    rpSensor    = NULL;

    for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
    {
        if ( ComponentFromRunListItem( pRunListItem, rpComponent ) &&
             SensorFromRunListItem   ( pRunListItem, rpSensor    )  )
        {
            if ( ( static_cast<SENSORINFO *>( *rpSensor ) )->m_iSensorIndex == riSensorIndex )
            {
                bFound = true;
                break;
            }
        }
    }

    return bFound;
}

inline
bool CLogger::UnloadModules( void )
{
    bool bSuccess = true;

    for ( ModuleListIterator_t pModule = m_ModuleList.begin(); pModule != m_ModuleList.end(); pModule++ )
    {
        if ( ! pModule->Unload() )
        {
            TRACE( _T( "Can't unload module (%d), %s\n" ), pModule->m_iModuleNumber, pModule->m_szModuleName );
            bSuccess = false;
        }
    }

    m_ModuleList.clear();

    return bSuccess;
}

inline
bool CLogger::HandleSensorData( void )
{
    // Sensor data collection handler; executed whenever the sensor data pool write event object is triggered.

    bool bSuccess = true;

    try
    {
        // Periodically check that a valid dongle is present.

        const unsigned long ulDongleCheckCycles = 50UL;

        m_ulSensorCycleDongleCheck = ( m_ulSensorCycleDongleCheck + 1 ) % ulDongleCheckCycles;

        if ( m_ulSensorCycleDongleCheck == 0UL )
        {
            if ( ! ValidateDongle() )
            {
                TRACE( _T( "CLogger::HandleSensorData(), Dongle is invalid. The PLC will soon terminate.\n" ) );
                return true;
            }
        }

        ASSERT( m_pby7kRecordBuffer != NULL );

        unsigned long ulTotalRecordBytes = 0UL;
        unsigned long ulTimeStamp        = 0UL;
        int           iSensorIndex       = 0;

        // Loop through all unread data in the shared memory pool and get a local copy of each record in turn.

        while ( m_pSensorData->Read( m_ulNextSensorRecordToRead, 
                                        m_pby7kRecordBuffer,
                                            ulTotalRecordBytes,
                                                iSensorIndex,
                                                    ulTimeStamp,
                                                        m_ulMaxLogBufferSize ) )
        {

#ifdef _DEBUG

            DumpRecordInfo( iSensorIndex, m_ulNextSensorRecordToRead, m_pby7kRecordBuffer, ulTotalRecordBytes );

#endif

            // Ensure we're dealing with valid 7k format records.

            if ( C7kProtocol::IsValid7kRecord( m_pby7kRecordBuffer, ulTotalRecordBytes ) )
            {
                // Adjust the 7k data record header as necessary for logging of the integrated sensor suite.

                Adjust7kRecordHeader( m_pby7kRecordBuffer, ulTotalRecordBytes, iSensorIndex );

                // If logging is active, verify available storage space and write to the logging data file(s).

                try
                {
                    const unsigned long ulMaxWriteFailures = 5UL;

                    if ( m_DiskDataLog.IsRecording() )
                    {
                        CheckAvailableDiskSpace();

                        if ( m_DiskDataLog.Write( m_pby7kRecordBuffer, ulTotalRecordBytes ) )
                        {
                            if ( m_ulDiskWriteFailCount == 0UL )
                            {
                                if ( m_Alarm.IsAlarmActive( alarmIdDiskFailure ) )
                                {
                                    if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                                            eventActionCleared,
                                                                                alarmIdDiskFailure,
                                                                                    "Failure writing to disk storage" ) )
                                    {
                                        TRACE( _T( "CLogger::HandleSensorData(), Unable to recind disk failure alarm" ) );
                                    }
                                }
                            }
                            else
                            {
                                --m_ulDiskWriteFailCount;
                            }
                        }
                        else
                        {
                            TRACE( _T( "CLogger::HandleSensorData(), error writing to disk!!!\n" ) );

                            bSuccess = false;

                            if ( ! m_Alarm.IsAlarmActive( alarmIdDiskFailure ) )
                            {
                                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                                        eventActionAlarmSet,
                                                                            alarmIdDiskFailure,
                                                                                "Failure writing to disk storage" ) )
                                {
                                    TRACE( _T( "CLogger::HandleSensorData(), Unable to raise disk failure alarm" ) );
                                }
                            }

                            if ( m_ulDiskWriteFailCount == ulMaxWriteFailures )
                            {
                                if ( ! Record( false ) )
                                {
                                    TRACE( _T( "CLogger::HandleSensorData(), Record( false ) failed.\n" ) );
                                }

                                if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                                        iUnspecified_c,
                                                                            iUnspecified_c,
                                                                                "Successive failure writing to disk storage -- logging has been disabled." ) )
                                {
                                    TRACE( _T( "CLogger::HandleSensorData(), Unable to write advisory logging disabled during write failure\n" ) );
                                }

                                m_ulDiskWriteFailCount = 0UL;
                            }
                            else
                            {
                                ++m_ulDiskWriteFailCount;
                            }
                        }
                    }
                }
                catch ( ... )
                {
                    bSuccess = false;
                    TRACE( _T( "CLogger::HandleSensorData(), Unspecified exception caught trying to write data to disk!!!\n" ) );
                }

                m_ulRecordCounter++;
            }
            else
            {
                bSuccess = false;
                TRACE( _T( "CLogger::HandleSensorData(), Invalid 7k sensor record in memory pool.\n" ) );
            }
        }
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::HandleSensorData(), Unspecified exception caught trying to write data to disk!!!\n" ) );
    }

    return bSuccess;
}

inline
void CLogger::Adjust7kRecordHeader( const BYTE          *pby7kRecord,
                                    const unsigned long &rulTotalRecordBytes,
                                    const unsigned int  &riSensorIndex )
{
#if defined ( _DEBUG )

    ASSERT( pby7kRecord != NULL );
    ASSERT( rulTotalRecordBytes > 0UL );
    ASSERT( C7kProtocol::IsValid7kRecord( pby7kRecord, rulTotalRecordBytes ) );

#else

    UNREFERENCED_PARAMETER( rulTotalRecordBytes );

#endif

    try
    {
        DATARECORDFRAME *ps7kDataRecordFrame  = reinterpret_cast<DATARECORDFRAME *>( const_cast<BYTE *>( pby7kRecord ) );
        ps7kDataRecordFrame->m_ulRecordNumber = m_ulRecordCounter;

        CComponent *pComponent = NULL;
        CSensor    *pSensor    = NULL;

        if ( SensorAndComponentFromIndex( riSensorIndex, pComponent, pSensor ) )
        {
            ASSERT( pSensor != NULL );

            // Adjust the system enumeration based on the device id's in the system.

            ps7kDataRecordFrame->m_unSystemEnumerator = static_cast<SENSORINFO *>( *pSensor )->m_unSystemEnumerator;
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::Adjust7kRecordHeader(), Unspecified exception caught!\n" ) );
    }
}

inline
bool CLogger::ProcessStatusMessage( const int &riSensorIndex, const int &riSocketIndex )
{
    // Retrieve sensor supplied status messages etc.

    bool        bSuccess   = true;
    CComponent *pComponent = NULL;

    if ( ComponentFromRunList( riSensorIndex, pComponent ) )
    {
        unsigned long ulDataSize   = 0UL;
        unsigned long ulTimestamp  = 0UL;
        int           iClientIndex = -1;

        ASSERT( pComponent != NULL );
        ASSERT( m_pby7kRecordBuffer != NULL );

        // Retrieve the status records and dispatch to the requesting client(s). Note also that 
        // we re-use m_pby7kRecordBuffer since these messages are handled synchronously (i.e., by the same thread).

        ASSERT( m_pClients != NULL );

        while ( pComponent->RetrieveStatus( m_pby7kRecordBuffer, &ulDataSize, &ulTimestamp, &iClientIndex, riSensorIndex ) )
        {
            bool bValidRecord = C7kProtocol::IsValid7kRecord( m_pby7kRecordBuffer, ulDataSize );

            ASSERT( bValidRecord );

            if ( bValidRecord )
            {
                // Use the retrieved client index in preference to the posted socket index (client and socket indicies 
                // are synonymous).

                if ( iClientIndex == -1 )
                {
                    iClientIndex = riSocketIndex;
                }

                if ( IsAlarmMessage( m_pby7kRecordBuffer, ulDataSize ) )
                {
                    HandleAlarmMessage( m_pby7kRecordBuffer, ulDataSize, ulTimestamp, riSensorIndex );
                }
                else if ( iClientIndex != -1 )
                {
                    if ( ! m_pClients->Send( iClientIndex, m_pby7kRecordBuffer, ulDataSize ) )
                    {
                        TRACE( _T( "CLogger::ProcessStatusMessage(), m_pClients->Send() failed\n" ) );
                        bSuccess = false;
                        continue;
                    }
                }
            }
        }
    }
    else
    {
        bSuccess = false;
        TRACE( _T( "CLogger::ProcessStatusMessage(), specified sensor index not in runlist !!!\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::ProcessQueuedCommands( void )
{
    bool         bSuccess = true;
    unsigned int uiSize   = 0;

    __TRY
    {
        m_CriticalCommandQueue.Enter();
        uiSize = m_CommandQueue.size();
    }
    __FINALLY
    {
        m_CriticalCommandQueue.Leave();
    }
    __ENDFINALLY

    for ( unsigned int uiCommand = 0U; uiCommand < uiSize; uiCommand++ )
    {
        m_CriticalCommandQueue.Enter();
        CQueuedCommand Command( m_CommandQueue.front() );
        m_CommandQueue.pop_front();
        m_CriticalCommandQueue.Leave();

        if ( ! ExecuteCommand( Command ) )
        {
            bSuccess = false;
        }
    }

    return bSuccess;
}

inline
bool CLogger::ExecuteCommand( const CQueuedCommand &rCommand )
{
    const int iSocketIndex = rCommand.SocketIndex();

    if ( ! m_CommandHandler( rCommand.Command(), rCommand.Options(), iSocketIndex, rCommand.SensorIndex(), rCommand.Action() ) )
    {
        if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                eventActionAlarmSet,
                                                    alarmIdInvalidCommand,
                                                        "%d, %s, Command failed",
                                                            iSocketIndex,
                                                                rCommand.Command() ) )
        {
            TRACE( _T( "CLogger::ExecuteCommand(), m_SurveyEventLog.ReportEvent() failed\n" ) );
        }

        return false;
    }

    return true;
}

inline
bool CLogger::Record(   const bool &rbRecord,
                        LPCTSTR     lpszPath,
                        LPCTSTR     lpszFileName )
{
    // Close or otherwise switch to a new logging file using the specified name and path.

    bool bSuccess = false;

    if ( rbRecord )
    {
        m_ulDiskWriteFailCount = 0;
        m_ulRecordCounter     = 0UL;

        bSuccess = m_DiskDataLog.Switch( lpszPath, lpszFileName );

        if ( bSuccess && ( m_DiskDataLog.IsRecording() ) )
        {
            PostThreadMessage( messageQuerySensorSettings );
        }
    }
    else
    {
        bSuccess = m_DiskDataLog.Close();
    }

    return bSuccess;
}

inline
bool CLogger::IsValidSensorId( const int &riSensorId ) const
{
    return ( riSensorId > m_iInvalidSensorIndex );
}

inline
bool CLogger::IsValidModuleId( const int &riModuleId ) const
{
    bool bValid = false;

    if ( riModuleId >= 0 )
    {
        for ( KnownModulesIterator_t pModule = m_KnownModules.begin(); pModule != m_KnownModules.end(); pModule++ )
        {
            if ( pModule->m_iModuleNumber == riModuleId )
            {
                bValid = true;
                break;
            }
        }
    }

    return bValid;
}

inline
bool CLogger::IsValidPort( const int &riPort ) const
{
    return ( riPort >= 0 );
}

inline
int CLogger::NextAvailableSensorIndex( void ) const
{
    // Scan the sorted run list and determine the next available sensor index.

    int iSensorIndex = 0;

    if ( m_RunList.empty() )
    {
        iSensorIndex = 0;
    }
    else
    {
        // Firstly, build a list of sensor indexes to be sorted.

        std::list<int>  Assigned;
        CSensor        *pSensor;

        for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
        {
            pSensor = NULL;

            if ( SensorFromRunListItem( pRunListItem, pSensor ) )
            {
                Assigned.push_back( (static_cast<SENSORINFO *>( *pSensor ))->m_iSensorIndex );
            }
        }

        ASSERT( ! Assigned.empty() );

        if ( ! Assigned.empty() )
        {
            // Sort the list of sensor indicies in assending order.

            Assigned.sort();

            // Run through the list and determine the next available sensor index.

            std::list<int>::iterator pItem = Assigned.begin();

            while ( pItem != Assigned.end() )
            {
                int iIndex1 = *pItem;

                pItem++;

                if ( pItem != Assigned.end() )
                {
                    if ( *pItem > ( iIndex1 + 1 ) )
                    {
                        iSensorIndex = iIndex1 + 1;
                        break;
                    }
                }
                else
                {
                    iSensorIndex = iIndex1 + 1;
                    break;
                }
            }
        }
    }

    return iSensorIndex;
}

inline
bool CLogger::IsAlarmMessage( const BYTE * const pby7kRecord, const unsigned long &rulDataSize )
{
    const unsigned long ulSize = tagRECORDHEADER7K::Size();

    if ( rulDataSize > ulSize )
    {
        return ( (  reinterpret_cast<const DATARECORDFRAME * const>( pby7kRecord )->m_ulRecordType == 11002UL ) && 
                 ( *reinterpret_cast<const int *>( pby7kRecord + ulSize ) == 1 ) );
    }

    return false;
}

inline
void CLogger::HandleAlarmMessage(   const BYTE * const      pby7kRecord,
                                    const unsigned long    &rulDataSize,
                                    unsigned long          &rulTimeStamp,
                                    const int              &riSensorIndex )
{
    UNREFERENCED_PARAMETER( rulTimeStamp );
    UNREFERENCED_PARAMETER( riSensorIndex );

    if ( ( rulDataSize > 0UL ) && ( pby7kRecord != NULL ) )
    {
        C7kStatus::EMESSAGETYPE eMessageType;
        C7kStatus::EMODE        eMode;
        int                     iMessageId;

        C7kStatus               Decoder;

        if ( Decoder.Decode( static_cast<BYTE *>( const_cast<BYTE *>( pby7kRecord ) ),
                                rulDataSize,
                                    false,
                                        eMessageType,
                                            eMode,
                                                iMessageId ) )
        {
            if ( ( eMessageType == C7kStatus::messageTypeAlarm ) && 
                 ( ( eMode == C7kStatus::modeAlarmActive ) || ( eMode == C7kStatus::modeAlarmClear ) ) )
            {
                const int      iAction    = ( ( eMode == C7kStatus::modeAlarmActive ) ? eventActionAlarmSet : eventActionCleared );
                const CString  sMessage   = Decoder.Message();
                const char    *pszMessage = (( sMessage.IsEmpty() ) ? NULL : static_cast<const char *>( static_cast<LPCTSTR>( sMessage ) ) );

                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                         iAction,
                                                             iMessageId,
                                                                 const_cast<char *>( pszMessage ) ) )
                {
                    TRACE( _T( "CLogger::HandleAlarmMessage(), m_SurveyEventLog.ReportEvent() failied.\n" ) );
                }
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
// Remote command handlers.

inline
bool CLogger::CommandGeneric(   char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;                      // Assume failure for now.

    try
    {
        RunListIterator_t   pRunListItem;
        CComponent         *pComponent  = NULL;
        CSensor            *pSensor     = NULL;

        TRACE( _T( "CLogger::CommandGeneric(), Sensor: %d, Command: %s\n" ), riSensorIndex, pszOptions );

        if ( riSensorIndex == C7kCommand::sensorIndexPLC )
        {
            if ( HandlePLCASCIICommand( rulAction, pszOptions ) )
            {
                bSuccess = true;
            }
            else if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                        iUnspecified_c,
                                                            iUnspecified_c,
                                                                "PLC command failed.\n" ) )
            {
                TRACE( _T( "CLogger::CommandGeneric(), m_SurveyEventLog.ReportEvent() failed\n" ) );
            }
        }
        else if ( riSensorIndex >= 0 )
        {
            // Locate sensor in run list by index and dispatch command to it.

            if ( SensorAndComponentFromIndex( riSensorIndex, pComponent, pSensor ) )
            {
                if ( pComponent->SendCommand( riSensorIndex, riSocketIndex, pszOptions ) == ERROR_CODE_SUCCESS )
                {
                    bSuccess = true;
                }
                else if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                             eventActionAlarmSet,
                                                                 alarmIdSensorCommandFailed,
                                                                     "%d, %d, Command failed",
                                                                         riSensorIndex,
                                                                             riSocketIndex ) )
                {
                    TRACE( _T( "CLogger::CommandGeneric(), m_SurveyEventLog.ReportEvent() failed.\n" ) );
                }
            }
            else if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                        iUnspecified_c,
                                                            iUnspecified_c,
                                                                "Sensor (%d) not ready to accept command.", riSensorIndex ) )
            {
                TRACE( _T( "CLogger::CommandGeneric(), m_SurveyEventLog.ReportEvent() failed.\n" ) );
            }
        }
        else if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                   iUnspecified_c,
                                                       iUnspecified_c,
                                                           "Invalid sensor command index detected (%d)", riSensorIndex ) )
        {
            TRACE( _T( "CLogger::CommandGeneric(), m_SurveyEventLog.ReportEvent() failed.\n" ) );
        }
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandGeneric(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex, 
                            bSuccess, 
                                static_cast<unsigned long>( C7kCommand::commandGeneric ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandLogging(   char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( riSensorIndex == C7kCommand::sensorIndexPLC )
        {
            CString Options( pszOptions );

            if ( ! Options.IsEmpty() )
            {
                bool bEnable = ( ::atoi( (LPCTSTR) Options ) != 0 );
                TRACE( _T( "CLogger::CommandLogging(), Logging request, flag: %d\n" ), bEnable );
                bSuccess = Record( bEnable );
            }
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandLogging ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandSwitch(    char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        CString FileName( pszOptions );
        CString Original( FileName );

        FileName.MakeLower();

        if ( FileName.IsEmpty() || ( FileName.Find( "auto", 0 ) != -1 ) )
        {
            bSuccess = Record( true, NULL, NULL );
        }
        else
        {
            bSuccess = Record( true, NULL, Original );
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandSwitch ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandPath(      char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )

{
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    bool bSuccess = false;        // Assume failure for now.
    bool bChanged = false;

    try
    {
        // Only if the requested path is valid (i.e. exists) and logging is inactive will we change the path.

        string sPath( pszOptions );
        string sAlarmMessage;

        if ( m_DiskDataLog.IsRecording() )
        {
            sAlarmMessage = "Path may not be changed when recording is active.";
        }
        else if ( sPath.empty() )
        {
            sAlarmMessage = "The specified path is empty.";
        }
        else if ( ! PathFileExists( pszOptions ) )
        {
            sAlarmMessage = "The specified path is invalid or does not exist";
        }
        else if ( ! m_DiskDataLog.Path( static_cast<LPCTSTR>( pszOptions ) ) )
        {
            sAlarmMessage = "Unable to change logging path.";
        }

        bool bChanged = sAlarmMessage.empty();

        if ( ! bChanged )
        {
            if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                    iUnspecified_c,
                                                        iUnspecified_c,
                                                            const_cast<char *>( sAlarmMessage.c_str() ) ) )
            {
                TRACE( _T( "CLogger::CommandPath(), Failed to report alarm/advisory message" ) );
            }
        }

        bSuccess = true;
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), Unspecified exception caught\n" ) );
        ASSERT( false );
    }

    if ( ! bSuccess )
    {
        if ( ! m_SurveyEventLog.ReportEvent(    eventLogDiagnostic,
                                                    iUnspecified_c,
                                                        iUnspecified_c,
                                                            "Internal error CLogger::CommandPath() failed" ) )
        {
            TRACE( _T( "CLogger::CommandPath(), Failed to report alarm/advisory condition" ) );
        }
    }

    // Send an ACK/NAK as/if necessary.

    AcknowlegeCommand( riSocketIndex,
                            ( bSuccess && bChanged ),
                                static_cast<unsigned long>( C7kCommand::commandPath ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandVersion(   char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        CString Version;

        Version.Format( _T( "%.4f" ), m_fVersion );

        Reply(  riSocketIndex,
                    C7kStatus::messageTypeStatus,
                        C7kStatus::modeUnspecified,
                            C7kStatus::messageIdVersion,
                                Version );

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand(  riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandVersion ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandLoad(  char                *pszOptions,
                            const int           &riSocketIndex,
                            const int           &riSensorIndex,
                            const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( riSocketIndex );
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
        {
            CComponent *pComponent = NULL;
            CSensor    *pSensor    = NULL;

            if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
                 SensorFromRunListItem   ( pRunListItem, pSensor    )  )
            {
                if ( pComponent->Shutdown( pSensor->m_iSensorIndex ) == ERROR_CODE_FAILURE )
                {
                    TRACE( _T( "CLogger::CommandLoad(), Sensor %d failed to shutdown correctly\n" ), pSensor->m_iSensorIndex );

                    if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                            eventActionAlarmSet,
                                                                alarmIdSensorFailedToShutdown,
                                                                    "%d, Sensor failed to shut down", pSensor->m_iSensorIndex ) )
                    {
                        TRACE( _T( "CLogger::CommandLoad(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                    }
                }
            }
        }

        // Finally, destroy the run list and then re-build it based on the saved config file.

        m_RunList.clear();

        if ( ! BuildRunList() )
        {
            ThrowMessage_m( "Can't build the run list" );
        }

        if ( ! StartSensors() )
        {
            ThrowMessage_m( "Can't restart sensors." );
        }

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandGeneric ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandSave(  char                *pszOptions,
                            const int           &riSocketIndex,
                            const int           &riSensorIndex,
                            const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    CPLCConfiguration::SENSORINFO *psSensorInfo = NULL;

    try
    {
        CString FileName( lpszPLCConfigurationFileName_c );

        if ( FileName.IsEmpty() )
        {
            ThrowMessage_m( "Invalid file name" );
        }

        FileName = m_AppPath + FileName;

        const unsigned long ulSensors = static_cast<unsigned long>( m_RunList.size() );

        if ( ulSensors == 0 )
        {
            bSuccess = m_Config.WriteFile( FileName, NULL, ulSensors );
        }
        else
        {
            psSensorInfo = new CPLCConfiguration::SENSORINFO[ ulSensors ];

            if ( psSensorInfo == NULL )
            {
                ThrowMessage_m( "Can't allocate temporary storage for sensor info" );
            }

            int iSensor = 0;

            for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
            {
                ModuleListIterator_t    pModule;
                CSensor                *pSensor = NULL;

                if (  SensorFromRunListItem( pRunListItem, pSensor ) &&
                      ModuleFromRunListItem( pRunListItem, pModule )  )
                {
                    SENSORINFO *psSensor = static_cast<SENSORINFO *>( pSensor );

                    psSensorInfo[ iSensor ].m_iDelay             = psSensor->m_iStartDelay;
                    psSensorInfo[ iSensor ].m_ulSubsystemId      = psSensor->m_iSubsystemId;
                    psSensorInfo[ iSensor ].m_SensorDescription  = psSensor->m_szName;
                    psSensorInfo[ iSensor ].m_ulDeviceId         = psSensor->m_ulDeviceId;
                    psSensorInfo[ iSensor ].m_unSystemEnumerator = psSensor->m_unSystemEnumerator;
                    psSensorInfo[ iSensor ].m_ulModuleNumber     = pModule->m_iModuleNumber;

                    iSensor++;
                }
            }

            bSuccess = m_Config.WriteFile( FileName, &psSensorInfo[ 0 ], static_cast<unsigned long>( ulSensors ) );
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    if ( psSensorInfo != NULL )
    {
        delete [] psSensorInfo;
        psSensorInfo = NULL;
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandSave ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandSubscribe( char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( ( pszOptions == NULL ) || ( strlen( pszOptions ) == 0 ) )
        {
            ThrowMessage_m( "Invalid options list string" );
        }

        int           iSensorIndex   = 0;
        unsigned long ulRecordNumber = 0UL;

        if ( ::sscanf( pszOptions, "%d, %lu", &iSensorIndex, &ulRecordNumber ) == 2 )
        {
            bSuccess = m_pClients->Subscribe( riSocketIndex, iSensorIndex, ulRecordNumber, true );
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandSubscribe(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandSubscribe(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandSubscribe ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandUnsubscribe(   char                *pszOptions,
                                    const int           &riSocketIndex,
                                    const int           &riSensorIndex,
                                    const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( rulAction );
    UNREFERENCED_PARAMETER( riSensorIndex );

    try
    {
        if ( ( pszOptions == NULL ) || ( strlen( pszOptions ) == 0 ) )
        {
            ThrowMessage_m( "Invalid options list string" );
        }

        int           iSensorIndex   = 0;
        unsigned long ulRecordNumber = 0UL;

        if ( sscanf( pszOptions, "%d, %lu", &iSensorIndex, &ulRecordNumber ) == 2 )
        {
            bSuccess = m_pClients->Subscribe( riSocketIndex, iSensorIndex, ulRecordNumber, false );
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandUnsubscribe ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandAlarm( char                *pszOptions,
                            const int           &riSocketIndex,
                            const int           &riSensorIndex,
                            const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( ( pszOptions == NULL ) || ( ::strlen( pszOptions ) == 0 ) )
        {
            ThrowMessage_m( "Invalid options list string" );
        }

        int iAlarmId     = -1;
        int iAlarmAction = -1;

        if ( ::sscanf( pszOptions, "%d, %d", &iAlarmId, &iAlarmAction ) != 2 )
        {
            ThrowMessage_m( "Invalid parameters" );
        }

        bSuccess = m_pClients->AlarmMode( riSocketIndex, static_cast<EALARMID>( iAlarmId ), iAlarmAction );

        if ( bSuccess )
        {
            CString AlarmMessage;

            AlarmMessage.Format( "%d, Alarm %s", iAlarmId, ( iAlarmAction == 0 ) ? _T( "disabled" ) : _T( "enabled" ) );

            if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                    iUnspecified_c,
                                                        iUnspecified_c,
                                                            const_cast<char *>( static_cast<LPCTSTR>( AlarmMessage ) ) ) )
            {
                TRACE( _T( "CLogger::CommandAlarm(), Can't annotate event log\n" ) );
            }
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAlarm(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAlarm(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandAlarm ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandReportAlarms(  char                 *pszOptions,
                                    const int            &riSocketIndex,
                                    const int            &riSensorIndex,
                                    const unsigned long  &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );
    UNREFERENCED_PARAMETER( pszOptions );

    try
    {
        ASSERT( m_pClients != NULL );

        bSuccess = m_pClients->ReportActiveAlarms( riSocketIndex, m_Alarm );
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandReportAlarms(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandReportAlarms(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandReportAlarms ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandStatus(    char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    // NB: No longer used -- presently, just validates the this pointer, replys with a NAK and then
    // returns true. Although the latter two conditions seem contradictory, the NAK is because
    // the command isn't implemented (thus rejected) but returns true because the command execution 
    // did not fail.

    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandStatus(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandStatus(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            false,                          // NAK because this command not implemented thus rejected.
                                static_cast<unsigned long>( C7kCommand::commandStatus ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandVerbose(   char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( riSensorIndex != C7kCommand::sensorIndexPLC )
        {
            ThrowMessage_m( "Verbose mode only relevant to PLC" );
        }

        int iVerbose = 0;

        if ( sscanf( pszOptions, "%d", &iVerbose ) != 1 )
        {
            ThrowMessage_m( "Can't decode verbose mode flag" );
        }

        bSuccess = m_pClients->Verbose( riSocketIndex, ( iVerbose != 0 ) );
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand(  riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandVerbose ),
                                    riSensorIndex );


    return bSuccess;
}

inline
bool CLogger::CommandReset( char                *pszOptions,
                            const int           &riSocketIndex,
                            const int           &riSensorIndex,
                            const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now, until we know otherwise.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( ( pszOptions == NULL ) || ( ::strlen( pszOptions ) == 0 ) )
        {
            ThrowMessage_m( "Invalid options list string" );
        }

        int iSensor = 0;

        if ( ::sscanf( pszOptions, "%d", &iSensor ) == 1 )
        {
            bSuccess = ( ResetSensor( riSensorIndex ) != ERROR_CODE_FAILURE );

            if ( ! bSuccess )
            {
                if ( ! m_SurveyEventLog.ReportEvent(    eventLogAdvisory,
                                                            iUnspecified_c,
                                                                iUnspecified_c,
                                                                    "%d, Sensor reset failed",
                                                                        riSensorIndex ) )
                {
                    TRACE( _T( "CLogger::CommandReset(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                }
            }
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandReset ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandShutdown(  char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        CString Mode( pszOptions );

        if ( Mode.IsEmpty() )
        {
            ThrowMessage_m( "Mode unspecified" );
        }

        unsigned int uiMode = static_cast<unsigned int>( ::atoi( static_cast<const char *>( static_cast<LPCTSTR>( pszOptions ) ) ) );

        TRACE( _T( "CLogger::CommandShutdown(), Shutdown mode is : %u\n" ), uiMode );

        bSuccess = ( PostThreadMessage( CLogger::messageShutdown, uiMode ) != FALSE );
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandShutdown ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandModules(   char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( riSocketIndex );
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        CString ReplyString;

        ReplyString.Format( "Modules: %d", m_ModuleList.size() );
        
        for ( ModuleListIterator_t pModule = m_ModuleList.begin(); pModule != m_ModuleList.end(); pModule++ )
        {
            CComponent *pComponent = pModule->m_pComponent;
            ASSERT( pComponent != NULL );

            if ( pComponent != NULL )
            {
                CString ModuleInfo;
                ModuleInfo.Format( ", Module = %d, %s, %.4lf",  pModule->m_iModuleNumber,
                                                                pModule->m_szFileName,
                                                                pComponent->GetModuleVersion() );

                ReplyString += ModuleInfo;
            }
        }

        Reply(  riSocketIndex,
                    C7kStatus::messageTypeStatus,
                        C7kStatus::modeUnspecified,
                            C7kStatus::messageIdModules,
                                ReplyString );

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand(  riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandModules ),
                                    riSensorIndex );


    return bSuccess;
}

inline
bool CLogger::CommandRunList(   char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        CString     ReplyString;
        CString     SensorEntry;
        SENSORINFO *psSensorInfo;

        ReplyString.Format( "Sensors: %d", m_RunList.size() );

        for ( RunListIterator_t pItem = m_RunList.begin(); pItem != m_RunList.end(); pItem++ )
        {
            if ( ( psSensorInfo = static_cast<SENSORINFO *>(*pItem) ) != NULL )
            {
                SensorEntry.Format( ", Sensor = %d, %d, %d, %d, %d, %d, %s",    psSensorInfo->m_iSensorIndex,
                                                                                psSensorInfo->m_iSensorId,
                                                                                psSensorInfo->m_iSensorType,
                                                                                psSensorInfo->m_iMediaPort,
                                                                                psSensorInfo->m_iMediaType,
                                                                                psSensorInfo->m_iSubsystemId,
                                                                                psSensorInfo->m_szName );
                ReplyString += SensorEntry;
            }
        }

        bSuccess = ! ReplyString.IsEmpty();

        if ( bSuccess )
        {
            Reply(  riSocketIndex,
                        C7kStatus::messageTypeStatus,
                            C7kStatus::modeUnspecified,
                                C7kStatus::messageIdRunList,
                                    ReplyString );
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandRunList ),
                                    riSensorIndex );
 
    return bSuccess;
}

inline
bool CLogger::CommandAdd(   char                *pszOptions,
                            const int           &riSocketIndex,
                            const int           &riSensorIndex,
                            const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    CString ErrorMessage;

    try
    {
        // Decode module id, port number and name from optional parameters and validate them.

        int     iPort,
                iModuleNumber,
                iSubsystemId,
                iNumberOfParameters;

        CString SensorName;
        iNumberOfParameters = ::sscanf( pszOptions, "%d, %d, %d, %s", &iModuleNumber, &iSubsystemId, &iPort, SensorName.GetBuffer( _MAX_PATH ) );
        SensorName.ReleaseBuffer();

        if ( iNumberOfParameters != 4 )
        {
            ThrowMessage_m( "Invalid number of parameters specified" );
        }

        if ( ! IsValidModuleId( iModuleNumber ) )
        {
            ErrorMessage.Format( _T( "Module id (%d) is invalid"), iModuleNumber );
            throw ErrorMessage;
        }

        if ( ! IsValidPort( iPort ) )
        {
            ErrorMessage.Format( _T( "Port number (%d) is invalid"), iPort );
            throw ErrorMessage;
        }

        if ( ! AddAndStartSensor( iModuleNumber, iPort, iSubsystemId, SensorName ) )
        {
            ThrowMessage_m( "Failed to add or start specified sensor" );
        }

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandAdd ),
                                    riSensorIndex );


    return bSuccess;
}

inline
bool CLogger::CommandRemove(    char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        int iSensorIndex = 0;

        if ( sscanf( pszOptions, "%d", &iSensorIndex ) != 1 )
        {
            ThrowMessage_m( "Invalid sensor index." );
        }

        if ( ! ShutdownAndRemoveSensor( iSensorIndex ) )
        {
            ThrowMessage_m( "Failed to remove or shutdown sensor" );
        }

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandRemove ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandReport(    char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{
    bool bSuccess = false;      // Assume failure for now.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        CString FileDetails;

        if ( m_DiskDataLog.IsRecording() )
        {
            FileDetails.Format( "Logging = 1, %s, %.3lf",   m_DiskDataLog.FullFileName(),
                                                            m_DiskDataLog.FileSize() );
        }
        else
        {
            FileDetails.Format( "Logging = 0, %s, %.3lf",   "", 0.0 );
        }

        Reply(  riSocketIndex,
                    C7kStatus::messageTypeStatus,
                        C7kStatus::modeUnspecified,
                            C7kStatus::messageIdFileDetails,
                                FileDetails );

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Command (), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandReport ),
                                    riSensorIndex );


    return bSuccess;
}

inline
bool CLogger::CommandPort(  char                *pszOptions,
                            const int           &riSocketIndex,
                            const int           &riSensorIndex,
                            const unsigned long &rulAction )
{
    bool bSuccess = false;              // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    CString ErrorMessage;

    try
    {
        int     iPort,
                iId;

        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        if ( ::sscanf( pszOptions, "%d, %d", &iId, &iPort ) != 2 )
        {
            ThrowMessage_m( "Can't decode command parameters" );
        }

        if ( ! IsValidSensorId( iId ) )
        {
            ErrorMessage.Format( _T("Id (%d) is invalid"), iId );
            throw ((LPCTSTR) ErrorMessage);
        }

        if ( ! IsValidPort( iPort ) )
        {
            ErrorMessage.Format( _T("Port (%d) is invalid"), iPort );
            throw ((LPCTSTR) ErrorMessage);
        }

        bool bComponentFound = false;

        for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
        {
            CComponent *pComponent = NULL;
            CSensor    *pSensor    = NULL;

            if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
                 SensorFromRunListItem   ( pRunListItem, pSensor    )  )
            {
                SENSORINFO *pSensorInfo = static_cast<SENSORINFO *> (*pSensor);

                if ( pSensorInfo->m_iSensorId == iId )
                {
                    bComponentFound = true;

                    int iOldPort = pSensorInfo->m_iMediaPort;
                    pSensorInfo->m_iMediaPort       = iPort;
                    pSensorInfo->m_iActivateOnStart = ( m_bActiveOnStart ? 1 : 0 );

                    if ( pComponent->Shutdown( pSensorInfo->m_iSensorIndex ) == ERROR_CODE_FAILURE )
                    {
                        TRACE( _T( "CLogger::CommandPort(), Sensor %d failed to shutdown correctly\n" ), pSensor->m_iSensorIndex );

                        if ( ! m_SurveyEventLog.ReportEvent(    eventLogAlarm,
                                                                    eventActionAlarmSet,
                                                                        alarmIdSensorFailedToShutdown,
                                                                            "%d, Sensor failed to shut down", pSensor->m_iSensorIndex ) )
                        {
                            TRACE( _T( "CLogger::CommandPort(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                        }
                    }

                    if ( pComponent->Startup( pSensorInfo, m_dwThreadID, m_uiDataReadyMessageId, m_uiReplyReadyMessageId, m_SurveyEventLog.ReportEvent, NULL /*&(m_CriticalParameterTable)*/ ) == ERROR_CODE_SUCCESS )
                    {
                        bSuccess = true;
                    }
                    else
                    {
                        // Failed to start new component so try to re-instate with old port number.

                        TRACE( _T( "CLogger::CommandPort(), Failed to start new component... attempting to use old port assisnment" ) );

                        if ( ! m_SurveyEventLog.ReportEvent(    eventLogAlarm,
                                                                    eventActionAlarmSet,
                                                                        alarmIdSensorFailedToStart,
                                                                            "%d, Sensor failed to correctly start", pSensorInfo->m_iSensorIndex ) )
                        {
                            TRACE( _T( "CLogger::CommandPort(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                        }

                        pSensorInfo->m_iMediaPort = iOldPort;

                        if ( pComponent->Startup( pSensorInfo, m_dwThreadID, m_uiDataReadyMessageId, m_uiReplyReadyMessageId, m_SurveyEventLog.ReportEvent, NULL /*&m_CriticalParameterTable*/ ) == ERROR_CODE_FAILURE )
                        {
                            ThrowMessage_m( "Failed to re-instate old port number" );
                        }
                    }

                    SetFileHeaderAndEnumerationInfo();

                    break;
                }
            }
        }

        if ( ! bComponentFound )
        {
            ThrowMessage_m( "Component with specified id not found" );
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandPort(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandPort(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandPort ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandTimeSync(  char                *pszOptions,
                                const int           &riSocketIndex,
                                const int           &riSensorIndex,
                                const unsigned long &rulAction )
{

    DWORD dwTimestamp = CSystemTime::GetTickCount();

    UNREFERENCED_PARAMETER( rulAction );

    bool bSuccess = false;              // Assume failure for now.

    try
    {
        if ( m_eTimeSyncMode == timesyncModeStandAlone )
        {
            if ( pszOptions == NULL )
            {
                ThrowMessage_m( "pszOptions is NULL" );
            }

            double      dSeconds     = 0.0;
            SYSTEMTIME  sSystemTime  = { 0 };

            if ( ::sscanf( pszOptions, "%hu/%hu/%hu,%hu:%hu:%lf",   &sSystemTime.wYear,
                                                                    &sSystemTime.wMonth,
                                                                    &sSystemTime.wDay,
                                                                    &sSystemTime.wHour,
                                                                    &sSystemTime.wMinute,
                                                                    &dSeconds ) != 6 )
            {
                ThrowMessage_m( "Can't decode time parameters" );
            }

            double dWholeSeconds       = 0.0;
            double dFractionsOfSeconds = ::modf( dSeconds, &dWholeSeconds );

            sSystemTime.wSecond       = static_cast<WORD>( dWholeSeconds );
            sSystemTime.wMilliseconds = static_cast<WORD>( dFractionsOfSeconds * 1000.0 );

            CSystemTime::UpdateSystemTime( sSystemTime, dwTimestamp );
            bSuccess = true;
        }
        else
        {
            bSuccess = false;
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandTimeSync(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandTimeSync(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandTimeSync ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandHealth(    char                            *pszOptions,
                                const int                       &riSocketIndex,
                                const int                       &riSensorIndex,
                                const unsigned long             &rulAction )
{
    UNREFERENCED_PARAMETER( rulAction );
    UNREFERENCED_PARAMETER( pszOptions );

    bool bSuccess = true;       // Assume success until we know otherwise.

    try
    {
        CString HealthMessage;

        // Report on the system health (do not attempt recovery).

        if ( ! PerformSystemCheck( this, &HealthMessage, true ) )
        {
            TRACE( _T( "System check failed.\n" ) );
            bSuccess = false;
        }
        else if ( ! HealthMessage.IsEmpty() )
        {
            if ( ! Reply(   riSocketIndex,
                                C7kStatus::messageTypeStatus,
                                    C7kStatus::modeUnspecified,
                                        C7kStatus::messageIdHealth,
                                            HealthMessage ) )
            {
                TRACE( _T( "Can't encode or send health status message\n" ) );
                bSuccess = false;
            }
        }
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandHealth(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand(  riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandHealth ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandAutoStart( char                            *pszOptions,
                                const int                       &riSocketIndex,
                                const int                       &riSensorIndex,
                                const unsigned long             &rulAction )
{
    bool bSuccess = false;              // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        DWORD dwMode = 0UL;

        if ( ::sscanf( pszOptions, "%lu", &dwMode ) != 1 )
        {
            ThrowMessage_m( "Can't decode command parameter" );
        }

        bSuccess = AutoStartMode( dwMode );
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAutoStart(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAutoStart(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandAutoStart ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandAutoHealthCheck(   char                    *pszOptions,
                                        const int               &riSocketIndex,
                                        const int               &riSensorIndex,
                                        const unsigned long     &rulAction )
{
    bool bSuccess = false;                                          // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        int             iEnable              = 0;                   // Enable or disable Auto-health checking flag.
        unsigned long   ulPeriodMilliSeconds = 0UL;                 // Period in ms between end sensor check cycles (end of last to start of first in active run-list).

        if ( ::sscanf( pszOptions, "%d,%lu", &iEnable, &ulPeriodMilliSeconds ) != 2 )
        {
            ThrowMessage_m( "Can't decode command parameter(s)" );
        }

        // Set the system monitor object to either start scheduling periodic callbacks 
        // (c.f. CLogger::PerformSystemCheck()) to check system status or cancel its waitable timer object.

        m_SystemMonitor.Set( static_cast<bool>( iEnable != 0 ), ulPeriodMilliSeconds, true );

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAutoHealthCheck(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAutoHealthCheck(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandAutoHealthCheck ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandRouteMessages( char                    *pszOptions,
                                    const int               &riSocketIndex,
                                    const int               &riSensorIndex,
                                    const unsigned long     &rulAction )
{
    bool bSuccess = false;                                          // Assume failure for now.

    UNREFERENCED_PARAMETER( rulAction );

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        int iRoutingSensorIndex = m_iInvalidSensorIndex;

        if ( ::sscanf( pszOptions, "%d", &iRoutingSensorIndex ) != 1 )
        {
            ThrowMessage_m( "Can't decode command parameter(s)" );
        }

        SetRoutingSensor( iRoutingSensorIndex );

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAutoHealthCheck(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandAutoHealthCheck(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandRouteMessages ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandLoggingSetup(  char                    *pszOptions,
                                    const int               &riSocketIndex,
                                    const int               &riSensorIndex,
                                    const unsigned long     &rulAction )
{
    bool bSuccess = false;                                          // Assume failure for now.

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        ASSERT( riSensorIndex == C7kCommand::sensorIndexPLC );

        switch ( rulAction )
        {
            case C7kCommand::commandActionSet:

                {
                    // Decode the parameters and set as appropriate.

                    unsigned long ulMaxFileSize   = 0UL;
                    unsigned long ulRecordOverlap = 0UL;

                    if ( ::sscanf( pszOptions, "%lu, %lu", &ulMaxFileSize, &ulRecordOverlap ) != 2 )
                    {
                        ThrowMessage_m( "Can't decode command parameters" );
                    }

                    if ( ! m_DiskDataLog.MaxFileSize( ulMaxFileSize ) )
                    {
                        ThrowMessage_m( "Can't set max file size" );
                    }

                    if ( ! m_DiskDataLog.RecordOverlap( ulRecordOverlap ) )
                    {
                        ThrowMessage_m( "Can't set record overlap as specified" );
                    }
                }

                bSuccess = true;

                break;

            case C7kCommand::commandActionGet:

                {
                    // Format up a response and send back to the requesting client.

                    CString LoggingSetupDetails;

                    LoggingSetupDetails.Format( "LoggingSetup = %lu, %lu, %s",  m_DiskDataLog.MaxFileSize(),
                                                                                    m_DiskDataLog.RecordOverlap(),
                                                                                        static_cast<LPCTSTR>( m_DiskDataLog.Path() ) );

                    ASSERT( ! LoggingSetupDetails.IsEmpty() );

                    if ( ! Reply(   riSocketIndex,
                                        C7kStatus::messageTypeStatus,
                                            C7kStatus::modeUnspecified,
                                                C7kStatus::messageIdLoggingSetup,
                                                    LoggingSetupDetails ) )
                    {
                        ThrowMessage_m( "Can't reply to host with logging setup status info" );
                    }
                }

                bSuccess = true;

                break;

            default:

                bSuccess = false;
                break;
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandLoggingSetup(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandLoggingSetup(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand( riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandLoggingSetup ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandSimulate(  char                    *pszOptions,
                                const int               &riSocketIndex,
                                const int               &riSensorIndex,
                                const unsigned long     &rulAction )
{
    bool bSuccess = false;                                          // Assume failure for now.

    UNREFERENCED_PARAMETER( riSocketIndex );
    UNREFERENCED_PARAMETER( riSensorIndex );

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        CString Command( pszOptions );

        switch ( rulAction )
        {
            case C7kCommand::commandActionSet:

                bSuccess = HandleSimulateCommand( Command );
                break;

            case C7kCommand::commandActionGet:

                bSuccess = true;
                break;
        
            default:
        
                bSuccess = false;
                break;
        }
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandSimulate(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandSimulate(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::CommandTimeMessage(   char                    *pszOptions,
                                    const int               &riSocketIndex,
                                    const int               &riSensorIndex,
                                    const unsigned long     &rulAction )
{
    bool bSuccess = false;                                          // Assume failure for now.

    try
    {
        if ( pszOptions == NULL )
        {
            ThrowMessage_m( "pszOptions is NULL" );
        }

        if ( rulAction != C7kCommand::commandActionSet )
        {
            ThrowMessage_m( "Message is a SET only message" );
        }

        unsigned long ulEnable = 0UL;

        if ( ::sscanf( pszOptions, "%lu", &ulEnable ) != 1 )
        {
            ThrowMessage_m( "Invalid option(s)" );
        }

        m_TimeEmitter.Enable( ( ulEnable != 0UL ) );

        bSuccess = true;
    }
    catch( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandTimeMessage(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::CommandTimeMessage(), unspecified exception caught\n" ) );
    }

    AcknowlegeCommand(  riSocketIndex,
                            bSuccess,
                                static_cast<unsigned long>( C7kCommand::commandTimeMessage ),
                                    riSensorIndex );

    return bSuccess;
}

inline
bool CLogger::CommandIsAlive(   char                    *pszOptions,
                                const int               &riSocketIndex,
                                const int               &riSensorIndex,
                                const unsigned long     &rulAction )
{
    // Reply with an ACK regardless of whether we're in verbose mode or not so that
    // remote clients can know whether the PLC and the communications channel are responsive.

    UNREFERENCED_PARAMETER( pszOptions );
    UNREFERENCED_PARAMETER( rulAction  );

    AcknowlegeCommand(  riSocketIndex,
                            true,
                                static_cast<unsigned long>( C7kCommand::commandCommandIsAlive ),
                                    riSensorIndex,
                                        true );

    return true;
}

///////////////////////////////////////////////////////////////////////////////
/// General helpers.

inline
bool CLogger::AddAndStartSensor(    const int       &riModuleNumber,
                                    const int       &riPort,
                                    const int       &riSubsystemId,
                                    const CString   &rSensorName )
{
    // Check that the module id is in list of known modules. If so, add the sensor and start it 
    // otherwise it's an unkown type and therefore an error.

    bool bSuccess = false;      // Assume failure until we know otherwise.

    for ( ModuleListIterator_t pModule = m_ModuleList.begin(); pModule != m_ModuleList.end(); pModule++ )
    {
        if ( pModule->m_iModuleNumber == riModuleNumber )
        {
            SENSORINFO  sSensorInfo;

            sSensorInfo.m_iMediaType       = 0;                               // 0 = TCP/UDP, 1 = COM port.
            sSensorInfo.m_iMediaPort       = riPort;                          // TCP/UDP or serial port for data.
            sSensorInfo.m_iSubsystemId     = riSubsystemId;                   // Subsustem id, if applicable.
            sSensorInfo.m_iSensorIndex     = NextAvailableSensorIndex();      // Sensor index number used by PLC to identify sensors.
            sSensorInfo.m_iActivateOnStart = ( m_bActiveOnStart ? 1 : 0 );    // 0 => Sensor does NOT go active when it starts, active otherwise.

            strncpy( &(sSensorInfo.m_szName[ 0 ]), (LPCTSTR) rSensorName, processInfoNameLength - 1 );

            CRunItem RunListItem( riModuleNumber, &sSensorInfo );
            m_RunList.push_back( RunListItem );

            m_RunList.sort();

            // Now, scan the run list to locate the address of the SENSORINFO struct for that sensor (since the startup
            // interface method may alter it) then start the sensor.

            CComponent *pComponent = NULL;
            CSensor    *pSensor    = NULL;

            if ( SensorAndComponentFromIndex( sSensorInfo.m_iSensorIndex, pComponent, pSensor ) )
            {
                if ( pComponent->Startup( static_cast<SENSORINFO *>( *pSensor ), m_dwThreadID, m_uiDataReadyMessageId, m_uiReplyReadyMessageId, m_SurveyEventLog.ReportEvent, NULL /*&m_CriticalParameterTable*/ ) == ERROR_CODE_SUCCESS )
                {
                    bSuccess = true;
                }
                else
                {
                    TRACE( _T( "CLogger::AddAndStartSensor(), Sensor %d failed to start\n" ), sSensorInfo.m_iSensorIndex );

                    if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                            eventActionAlarmSet,
                                                                alarmIdSensorFailedToStart,
                                                                    "%d, Sensor failed to correctly start", sSensorInfo.m_iSensorIndex ) )
                    {
                        TRACE( _T( "CLogger::AddAndStartSensor(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                    }
                }
            }

            SetFileHeaderAndEnumerationInfo();

            break;
        }
    }

    return bSuccess;
}

inline
bool CLogger::ShutdownAndRemoveSensor( const int &riSensorIndex )
{
    bool bSuccess = false;      // Assume failure for now until we know otherwise.

    for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
    {
        CComponent *pComponent = NULL;
        CSensor    *pSensor    = NULL;

        if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
             SensorFromRunListItem   ( pRunListItem, pSensor    )  )
        {
            SENSORINFO *pSensorInfo = static_cast<SENSORINFO *> (*pSensor);

            if ( pSensorInfo->m_iSensorIndex == riSensorIndex )
            {
                if ( pComponent->Shutdown( pSensorInfo->m_iSensorIndex ) == ERROR_CODE_FAILURE )
                {
                    TRACE( _T( "CLogger::ShutdownAndRemoveSensor(), Sensor %d failed to shutdown correctly\n" ), pSensor->m_iSensorIndex );

                    if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                            eventActionAlarmSet,
                                                                alarmIdSensorFailedToShutdown,
                                                                    "%d, Sensor failed to shut down", pSensor->m_iSensorIndex ) )
                    {
                        TRACE( _T( "CLogger::ShutdownAndRemoveSensor(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                    }
                }

                m_RunList.erase( pRunListItem );

                bSuccess = true;

                break;
            }
        }
    }

    if ( ! m_RunList.empty() )
    {
        m_RunList.sort();
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// Top-side client helpers.

bool CLogger::AcknowlegeCommand(    const int           &riSocketIndex,
                                    const bool          &rbSuccess,
                                    const unsigned long &rulCommandId,
                                    const int           &riSensorId,
                                    const bool          &rbForceSend    )   // = false
{
    bool bSuccess = false;

    try
    {
        // If we're in verbose mode and given a client idenfified by it's socket index, send an ACK/NAK message.

        ASSERT( m_pClients != NULL );

        if ( m_pClients != NULL )
        {
            // If in verbose mode, format and send an acknowlegement message to the top-side as appropriate otherwise
            // do nothing.

            if ( rbForceSend || m_pClients->Verbose( riSocketIndex ) )
            {
                C7kAcknowlege Message;

                if ( Message.Encode( ( rbSuccess ? C7kAcknowlege::statusAccepted : C7kAcknowlege::statusRejected ), rulCommandId, riSensorId, false ) )
                {
                    bSuccess = m_pClients->Send( riSocketIndex, static_cast<BYTE *>( Message ), static_cast<unsigned long>( Message ) );

                    if ( bSuccess )
                    {
                        CString Message;

                        Message.Format( "Command: %lu, %sed", rulCommandId, rbSuccess ? _T( "ACK" ) : _T( "NAK" ) );

                        if ( ! m_SurveyEventLog.ReportEvent( eventLogDiagnostic,
                                                                eventActionRemoteCommandACKNAK,
                                                                       iUnspecified_c,
                                                                           "%s",
                                                                               const_cast<char *>( static_cast<LPCTSTR>( Message ) ) ) )
                        {
                            TRACE( _T( "CLogger::AcknowlegeCommand(), Can't make entry to Survey Event Log\n" ) );
                        }
                    }
                    else
                    {
                        TRACE( _T( "CLogger::AcknowlegeCommand(), failed to send ACK/NAK\n" ) );
                    }
                }
            }
            else
            {
                bSuccess = true;
            }
        }
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::AcknowlegeCommand(), Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CLogger::Reply(    const int   &riSocketIndex,
                        const int   &riMessageType,
                        const int   &riMode,
                        const int   &riMessageId,
                        LPCTSTR     lpszFormat, ... )
{
    // Format a 7k reply message and send to host.

    bool bSuccess = false;      // Assume failure for now.

    CString Message;

    va_list pArgumentList;
    va_start( pArgumentList, lpszFormat );
    Message.FormatV( lpszFormat,  pArgumentList );
    va_end( pArgumentList );

    if ( Message.IsEmpty() )
    {
        bSuccess = true;
    }
    else
    {
        C7kStatus Status;

        if ( Status.Encode( static_cast<C7kStatus::EMESSAGETYPE>( riMessageType ),
                                static_cast<C7kStatus::EMODE>( riMode ),
                                    riMessageId, 
                                        Message, 
                                            false ) )
        {
            bSuccess = m_pClients->Send( riSocketIndex, static_cast<BYTE *>( Status ), static_cast<unsigned long>( Status ) );
        }
    }

    return bSuccess;
}

inline
bool CLogger::HandlePLCASCIICommand(    const unsigned long &rulAction,
                                        const char          *pszOptions )
{
    // Commands explicitly related to the PLC only.

    UNREFERENCED_PARAMETER( rulAction );
    UNREFERENCED_PARAMETER( pszOptions );

    return true;
}

inline
void CLogger::PrepareForShutdown( const int &riMode )
{
    m_iShutdownMode = riMode;
    PostThreadMessage( CBaseThread::threadMessageTerminate );
}

inline
void CLogger::Shutdown( void ) const
{
    if ( m_iShutdownMode == 1 )
    {
        ShutdownWindows( false );
    }

    try
    {
        if ( m_pfnTerminate != NULL )
        {
            ( m_pfnTerminate )();
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::Shutdown(), caught unspecified exception during execution of m_pfnTerminate\n" ) );
    }
}

inline
bool CLogger::IsActivateOnStart( void ) const
{
    CRegKey Key;
    bool    bActivate = false;
        
    Key.Attach( hKey6046_c );

    if ( Key.Open( hKey6046_c, lpsz6046RegistryKey_c ) == ERROR_SUCCESS )
    {
        DWORD dwValue = 0UL;

        if ( Key.QueryValue( dwValue, lpszActivateOnStartKey_c ) == ERROR_SUCCESS )
        {
            bActivate = ( dwValue != 0UL );
        }

        Key.Close();
    }

    return bActivate;
}

inline
bool CLogger::AutoStartMode( const DWORD &rdwValue ) const
{
    bool    bSuccess = false;
    CRegKey Key;
        
    Key.Attach( hKey6046_c );

    if ( Key.Open( hKey6046_c, lpsz6046RegistryKey_c ) == ERROR_SUCCESS )
    {
        bSuccess = ( Key.SetValue( rdwValue, lpszActivateOnStartKey_c ) == ERROR_SUCCESS );

        Key.Close();
    }

    return bSuccess;
}

inline
void CLogger::Set7kFileHeaderAttributesFromRegistry( void )
{
    // Read and set the various 7k file header attributes from the registry. For now, these include the following:
    //
    //      a)  Session id
    //      b)  User name
    //      c)  User notes

    CRegKey Key;

    Key.Attach( hKey6046_c );

    if ( Key.Open( hKey6046_c, lpsz6046RegistryKey_c ) == ERROR_SUCCESS )
    {
        DWORD   dwValue     = 0UL;
        DWORD   dwLength    = _MAX_PATH;
        long    lResult     = ERROR_SUCCESS;

        CString StringValue;

        // Session id.

        if ( Key.QueryValue( dwValue, lpszSessionIdKey_c ) == ERROR_SUCCESS )
        {
            m_ulSessionId = static_cast<unsigned long>( dwValue );
        }

        // User name.

        dwLength = _MAX_PATH;
        lResult = Key.QueryValue( StringValue.GetBuffer( _MAX_PATH ), lpszUserNameKey_c, &dwLength );
        StringValue.ReleaseBuffer();

        if ( lResult == ERROR_SUCCESS )
        {
            m_UserName = StringValue;
        }

        // User notes.

        dwLength = _MAX_PATH;
        lResult = Key.QueryValue( StringValue.GetBuffer( _MAX_PATH ), lpszUserNotesKey_c, &dwLength );
        StringValue.ReleaseBuffer();

        if ( lResult == ERROR_SUCCESS )
        {
            m_UserNotes = StringValue;
        }

        Key.Close();
    }
}

inline
void CLogger::ProcessEvents( const HANDLE *phEventHandles, const DWORD &rdwEventIndex )
{
    // Process the event or other waitable kernel object and then set or othewise re-activate it as
    // necessary thereafter.

    const DWORD dwMaxEvents = static_cast<DWORD>( eventMaxEvents );

    ASSERT( rdwEventIndex < dwMaxEvents );
    ASSERT( phEventHandles[ rdwEventIndex ] != NULL );

    if ( ( rdwEventIndex < dwMaxEvents ) && ( phEventHandles[ rdwEventIndex ] != NULL ) )
    {
        switch ( rdwEventIndex )
        {
            case eventHealthCheckTimer:

                m_SystemMonitor.CheckSystemAndReSchedule();
                break;
            
            case eventScheduleSensorOnline:

                StartDeferredSensorAndReschedule();
                break;

            case eventDongleCheck:

                CheckDongleAndReschedule();
                break;

            case eventSensorDataAvailable:

                if ( ! HandleSensorData() )
                {
                    TRACE( _T( "CLogger::ProcessEvents(), HandleSensorData() failed.\n" ) );
                }

                break;

            case eventTimeTransmission:

                EmitTimeToHostsAndReschedule();
                break;
        
            default:

                TRACE( _T( "CLogger::ProcessEvents(), Unhandled notitication, Index: %lu.\n" ), rdwEventIndex );
                break;
        }
    }
}

bool CLogger::PerformSystemCheck( void *pvParam, CString *pMessage, const bool &rbReportOnly )
{
    // Static member invoked in one of two ways:
    //
    // 1)   As a callback by m_SystemMonitor object when a sensor check has been scheduled or
    // 2)   directly by the health query command (viz. CommandHealth()) in response to a client query.
    //
    // Note: here we perform a round-robbin check of all sensors in the run-list and generate an alarm if any are
    //       detected to have failed. Additionally, a recovery attempt is made, where possible and a message
    //       formatted in order to reply to the client with a status message.

    bool bSuccess = false;          // Assume failure for now.

    try
    {
        CLogger *pthis = static_cast<CLogger *>( pvParam );
        ASSERT( pthis != NULL );

        if ( pthis != NULL )
        {
            bool bFormatMessage = ( pMessage != NULL );

            if ( bFormatMessage )
            {
                pMessage->Empty();
                pMessage->Format( "Sensors: %u", pthis->m_RunList.size() );
            }

            CString SensorHealth;

            for ( RunListIterator_t pRunListItem = pthis->m_RunList.begin(); pRunListItem != pthis->m_RunList.end(); pRunListItem++ )
            {
                CComponent *pComponent = NULL;
                CSensor    *pSensor    = NULL;

                if ( pthis->ComponentFromRunListItem( pRunListItem, pComponent ) &&
                     pthis->SensorFromRunListItem   ( pRunListItem, pSensor    )  )
                {
                    bool bHealthy = pthis->CheckHealthOfSensor( pComponent, pSensor, rbReportOnly );

                    if ( ! bHealthy )
                    {
                        TRACE( _T( "CLogger::PerformSystemCheck(), pthis->CheckHealthOfSensor(), returned false\n" ) );
                    }

                    if ( bFormatMessage )
                    {
                        SensorHealth.Format( ", %d, %d", static_cast<SENSORINFO *>( *pSensor )->m_iSensorIndex, (( bHealthy )? 1 : 0 ) );
                        (*pMessage ) += SensorHealth;
                    }
                }
            }

            bSuccess = (( bFormatMessage ) ? ( ! pMessage->IsEmpty() ) : true );
        }
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::PerformSystemCheck(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::CheckHealthOfSensor( CComponent * const   pComponent,
                                   CSensor    * const   pSensor,
                                   const        bool   &rbReportOnly )
{
    // Overload to check sensor's health.
    // Note: the return code doesn't refer to the health of the sensor, rather whether the 
    // query was successfull. If the sensor is unhealthy an alarm will result and
    // a recovery attempt made at that time.

    bool bSuccess = true;       // Assume all is well until we know otherwise.

    if ( pComponent != NULL )
    {
        SENSORINFO * const pSensorInfo = static_cast<SENSORINFO *>( *pSensor );
        const int iSensorIndex         = pSensorInfo->m_iSensorIndex;

        // Check the health of the specified sensor using the sensor's own interface method.

        bSuccess = true;

        // Here we check the sensor's health status. If it's healthy then clear its fail count and 
        // rescind any previous alarms. Otherwise, attempt to reset the sensor but if it is a succisive
        // failure, raise the appropriate alarm.

        bool bHealthy = ( pComponent->IsSensorHealthy( iSensorIndex ) != 0 );

        TRACE( _T( "CLogger::CheckHealthOfSensor(), Sensor %d is %s\n" ), iSensorIndex, ( bHealthy ? _T( "healthy" ) : _T( "unhealthy !!!" ) ) );

        if ( rbReportOnly )
        {
            bSuccess = bHealthy;
        }
        else if ( bHealthy )
        {
            // Sensor is healthy so clear its fail count...

            pSensor->ClearFailCount();

            // and rescind its alarm, if set.

            if ( pSensor->IsFailedAlarmActive() )
            {
                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                        eventActionCleared,
                                                            alarmIdSensorNotResponding,
                                                                "%d, Sensor now responding",
                                                                    iSensorIndex ) )
                {
                    TRACE( _T( "CLogger::CheckHealthOfSensor(), Can't rescind alarm(%d) for sensor %d\n" ), alarmIdSensorNotResponding, iSensorIndex );
                }

                pSensor->FailAlarm( false );
            }
        }
        else if ( pSensor->IsMaxFailCount() )
        {
            // The sensor is still not responding after successive reset attempts but do nothing for now.
            // Previously, an alarm would have been raised after the maximum reset attempts. Now, however, this
            // has been moved to the block below so that the first failure will notify attached clients of this
            // non-responsive state.

            TRACE( _T( "CLogger::CheckHealthOfSensor(), Sensor %d remains unhealthy and will not be reset\n" ), iSensorIndex );
        }
        else
        {
            // Set the not responding alarm, if necessary.

            if ( ! pSensor->IsFailedAlarmActive() )
            {
                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                        eventActionAlarmSet,
                                                            alarmIdSensorNotResponding,
                                                                "%d, Sensor not responding",
                                                                    iSensorIndex ) )
                {
                    TRACE( _T( "CLogger::CheckHealthOfSensor(), Can't generate alarm(%d) for sensor %d\n" ), alarmIdSensorNotResponding, iSensorIndex );
                }

                pSensor->FailAlarm( true );
            }

            // Attempt to reset the sensor.

            bool bResetFailed = ( pComponent->ResetSensor( iSensorIndex ) == ERROR_CODE_FAILURE );

            // Annotate the event log that a sensor reset was attempted.

            if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                    iUnspecified_c,
                                                        iUnspecified_c,
                                                            "%d, Sensor reset attempted", iSensorIndex ) )
            {
                TRACE( _T( "CLogger::CheckHealthOfSensor(), Can't annotate event log\n" ) );
            }

            // If the reset failed then notify the survey event log.

            if ( bResetFailed )
            {
                if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                        iUnspecified_c,
                                                            iUnspecified_c,
                                                                "%d, Sensor reset failed",
                                                                    iSensorIndex ) )
                {
                    TRACE( _T( "CLogger::CheckHealthOfSensor(), Can't generate advisory notice for sensor %d, sensor reset failed\n" ), iSensorIndex );
                }
            }

            // Finally, if the reset failed or the sensor remains unhealthy, then increment the sensor's fail count ahead of
            // next iteration.

            if ( bResetFailed || ( ! pComponent->IsSensorHealthy( iSensorIndex ) ) )
            {
                pSensor->IncrementFailCount();
            }
        }
    }

    return bSuccess;
}

inline
bool CLogger::CheckHealthOfSensor( const int &riSensorIndex, const bool &rbReportOnly )
{
    bool bSuccess = false;

    CComponent *pComponent = NULL;
    CSensor    *pSensor    = NULL;

    if ( SensorAndComponentFromIndex( riSensorIndex, pComponent, pSensor ) )
    {
        bSuccess = CheckHealthOfSensor( pComponent, pSensor, rbReportOnly );
    }

    return bSuccess;
}

inline
bool CLogger::ResetSensor( const int &riSensorIndex )
{
    bool bSuccess = false;      // Assume failure for now.

    CComponent *pComponent = NULL;
    CSensor    *pSensor    = NULL;

    if ( SensorAndComponentFromIndex( riSensorIndex, pComponent, pSensor ) )
    {
        bSuccess = ( pComponent->ResetSensor( riSensorIndex ) != ERROR_CODE_FAILURE );

        // Annotate the Survey Event Log that a manual sensor reset was attempted. If, however, the 
        // attempt was unsuccessful, then it's an alarm.

        if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                iUnspecified_c,
                                                    iUnspecified_c,
                                                        "%d, Sensor reset attempted", riSensorIndex ) )
        {
            TRACE( _T( "CLogger::ResetSensor(), Can't annotate event log\n" ) );
        }

        if ( ! bSuccess )
        {
            if ( ! m_SurveyEventLog.ReportEvent( eventLogAdvisory,
                                                    iUnspecified_c,
                                                        iUnspecified_c,
                                                            "%d, Sensor reset failed",
                                                                riSensorIndex ) )
            {
                TRACE( _T( "CLogger::ResetSensor(), m_SurveyEventLog.ReportEvent() failed\n" ) );
            }
        }
    }

    return bSuccess;
}

inline
void CLogger::StartDeferredSensorAndReschedule( void )
{
    // Retrieve the scheduled sensor info from the queue then remove it.

    SENSORINFO  sSensorInfo;
    int         iModule = 0;

    if ( m_SensorScheduler.ScheduledSensorInfo( sSensorInfo, iModule, true ) )
    {
        // Add it to the run-list and sort the list.

        CRunItem RunListItem( iModule, &sSensorInfo );
        m_RunList.push_back( RunListItem );

        m_RunList.sort();

        // Locate the sensor in the runlist and start it up.

        CComponent *pComponent = NULL;
        CSensor    *pSensor    = NULL;

        if ( SensorAndComponentFromIndex( sSensorInfo.m_iSensorIndex, pComponent, pSensor ) )
        {
            SENSORINFO *psSensorInfo = static_cast<SENSORINFO *>( *pSensor );

            if ( pComponent->Startup( psSensorInfo, m_dwThreadID, m_uiDataReadyMessageId, m_uiReplyReadyMessageId, m_SurveyEventLog.ReportEvent, NULL /*&m_CriticalParameterTable*/ ) == ERROR_CODE_FAILURE )
            {
                TRACE( _T( "CLogger::StartDeferredSensorAndReschedule(), Sensor %d failed to start\n" ), psSensorInfo->m_iSensorIndex );

                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                        eventActionAlarmSet,
                                                            alarmIdSensorFailedToStart,
                                                                "%d, Sensor failed to correctly start", psSensorInfo->m_iSensorIndex ) )
                {
                    TRACE( _T( "CLogger::StartDeferredSensorAndReschedule(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                }
            }
        }

        SetFileHeaderAndEnumerationInfo();
    }
    
    // Give the scheduler a chance to reschedule the next sensor for startup.

    m_SensorScheduler.ScheduleNext();
}

inline
void CLogger::SetFileHeaderAndEnumerationInfo( void )
{
    // Format the sensor subsystem table and related header information so that the 7k file header (record #7200)
    // can be written first when the logged data file is openned; see also m_DiskDataLog object's class internals.
    // Additionaly, run through the enumeration id's in the sensor info to ensure multiple sensors with the same
    // 7k device id's are unique.

    try
    {
        CDisk::SUBSYSTEMENTRY *psSensorTable = NULL;

        try
        {
            const unsigned long ulNumberOfSensors = static_cast<unsigned long>( m_RunList.size() );

            if ( ulNumberOfSensors > 0 )
            {
                // Define a map<> container to be used to do our instance counting of device id's. Here, the device
                // id is the key and the instance count the associated data item.

                typedef unsigned short                                  SysEnum_t;
                typedef std::map<unsigned long, CCounter<SysEnum_t> >   DeviceTable_t;

                DeviceTable_t       DeviceTable;

                CSensor            *pSensor;
                SENSORINFO         *pSensorInfo;
                RunListIterator_t   pRunListItem;

                // Assign unique system enumerator values to sensors with the same device id's (i.e. their instance count).

                for ( pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
                {
                    if ( SensorFromRunListItem( pRunListItem, pSensor ) )
                    {
                        // Get the sensor specific info.

                        pSensorInfo = static_cast<SENSORINFO *>( *pSensor );
                        ASSERT( pSensorInfo != NULL );

                        // Increment the instance count for the encountered device id then extract that count and assign to the system enumerator.

                        pSensorInfo->m_unSystemEnumerator = static_cast<SysEnum_t>( DeviceTable[ pSensorInfo->m_ulDeviceId ]++ );

                        // Use zero indexing.

                        pSensorInfo->m_unSystemEnumerator--;
                    }
                }

                // Build the sensor table and format the sensor summary table from the runlist.

                psSensorTable = new CDisk::SUBSYSTEMENTRY[ ulNumberOfSensors ];

                ::memset( psSensorTable, 0x00, ulNumberOfSensors * CDisk::SUBSYSTEMENTRY::Size() );

                unsigned long ulSensor = 0UL;

                for ( pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
                {
                    if ( SensorFromRunListItem( pRunListItem, pSensor ) )
                    {
                        pSensorInfo = static_cast<SENSORINFO *>( *pSensor );
                        ASSERT( pSensorInfo != NULL );

                        psSensorTable[ ulSensor ].m_ulDeviceId            = pSensorInfo->m_ulDeviceId;
                        psSensorTable[ ulSensor ].m_unSubsystemEnumerator = pSensorInfo->m_unSystemEnumerator;

                        ++ulSensor;
                    }
                }

                // Now set the 7k file header info.

                CString Version;

                Version.Format( "%.4f", m_fVersion );

                if ( ! m_DiskDataLog.Set7kFileHeaderInfo(   ulNumberOfSensors,
                                                                psSensorTable,
                                                                    m_ulSessionId,
                                                                        Version,
                                                                            m_UserName,
                                                                                m_UserNotes ) )
                {
                    TRACE( _T( "CLogger::SetFileHeaderAndEnumerationInfo(), m_DiskDataLog.Set7kFileHeaderInfo() failed\n" ) );
                }
            }
        }
        catch ( ... )
        {
                TRACE( _T( "CLogger::SetFileHeaderAndEnumerationInfo(), unspecified exception caught\n" ) );
        }

        // Deallocate the sensor table.

        if ( psSensorTable != NULL )
        {
            delete [] psSensorTable;
            psSensorTable = NULL;
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::SetFileHeaderAndEnumerationInfo(), unspecified exception caught\n" ) );
    }
}

inline
void CLogger::SetRoutingSensor( const int &riRoutingSensorIndex )
{
    // Sets the sensor index for routing of non-command oriented 7k records.
    // The requested sensor index must be valid and the SENSORINFO::m_iAccepts7kRecords flag non-zero.

    // NB: this member should be called after the run-list has been created and the sensors started
    // since both m_RunList and SENSORINFO::m_iAccepts7kRecords need to have been previously set.

    m_CriticalRoutedMessageQueue.Enter();
    m_iRoutingSensorIndex = m_iInvalidSensorIndex;
    m_CriticalRoutedMessageQueue.Leave();

    if ( riRoutingSensorIndex != m_iInvalidSensorIndex )
    {
        for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
        {
            CSensor *pSensor = NULL;

            if ( SensorFromRunListItem( pRunListItem, pSensor ) )
            {
                SENSORINFO *pSensorInfo = static_cast<SENSORINFO *>( *pSensor );

                if ( ( pSensorInfo                      != NULL )                 && 
                     ( pSensorInfo->m_iSensorIndex      == riRoutingSensorIndex ) &&
                     ( pSensorInfo->m_iAccepts7kRecords != 0 )                     )
                {
                    m_CriticalRoutedMessageQueue.Enter();
                    m_iRoutingSensorIndex = riRoutingSensorIndex;
                    m_CriticalRoutedMessageQueue.Leave();
                    break;
                }
            }
        }
    }
}

inline
bool CLogger::IsRoutingSensorSet( void ) const
{
    m_CriticalRoutedMessageQueue.Enter();
    bool bIsSet = ( m_iRoutingSensorIndex != m_iInvalidSensorIndex );
    m_CriticalRoutedMessageQueue.Leave();

    return bIsSet;
}

inline
int CLogger::RoutingSensorIndex( void ) const
{
    m_CriticalRoutedMessageQueue.Enter();
    int iIndex = m_iRoutingSensorIndex;
    m_CriticalRoutedMessageQueue.Leave();

    return iIndex;
}

inline
bool CLogger::Route7kMessagesToSensor( void )
{
    // Locates the routing index specified sensor and dispatches all queued 7k records to it.

    bool bSuccess = false;          // Assume faiure for now.

    try
    {
        if ( IsRoutingSensorSet() )
        {
            int iRoutingIndex = RoutingSensorIndex();

            for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
            {
                CComponent *pComponent = NULL;
                CSensor    *pSensor    = NULL;

                if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
                     SensorFromRunListItem   ( pRunListItem, pSensor    )  )
                {
                    if ( (static_cast<SENSORINFO *>( pSensor ))->m_iSensorIndex == iRoutingIndex )
                    {
                        // Route all queued 7k records to the specified sensor.

                        bSuccess = true;

                        m_CriticalRoutedMessageQueue.Enter();

                        try
                        {
                            unsigned long ulQueuedRecords = m_RoutingQueue.Items();

                            if ( ulQueuedRecords > 0UL )
                            {
                                TRACE( _T( "Routing %lu records to sensor %d\n" ), ulQueuedRecords, iRoutingIndex );
                            }

                            while ( ulQueuedRecords > 0 )
                            {
                                int iIndex = m_RoutingQueue.Front();

                                if ( iIndex != m_RoutingQueue.m_iInvalidIndex )
                                {
                                    CQueued7kMessage     *psHeader = NULL;
                                    CDynamicBuffer<BYTE> *pBuffer  = NULL;

                                    if ( m_RoutingQueue.Get( iIndex, psHeader, pBuffer ) )
                                    {
                                        const unsigned long ulSize = sizeof( BYTE ) * pBuffer->Size();

                                        ASSERT( ulSize > 0UL );

                                        if ( ulSize > 0UL )
                                        {
                                            int iSuccess = pComponent->RouteMessage( pBuffer->GetAt( 0 ), 
                                                                                        ulSize,
                                                                                            psHeader->m_ulTimeStamp,
                                                                                                psHeader->m_iSocketIndex,
                                                                                                    iRoutingIndex );

                                            bSuccess = ( iSuccess != ERROR_CODE_FAILURE );

                                            if ( ! bSuccess )
                                            {
                                                TRACE( _T( "CLogger::Route7kMessagesToSensor(), Routing to sensor failed\n" ) );

                                                if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                                                        eventActionAlarmSet,
                                                                                            alarmIdSensorFailedToRoute7kMessage,
                                                                                                "%d, %d, 7k message routing failure", iRoutingIndex, psHeader->m_iSocketIndex ) )
                                                {
                                                    TRACE( _T( "CLogger::Route7kMessagesToSensor(), m_SurveyEventLog.ReportEvent() failed\n" ) );
                                                }


                                            }
                                        }

                                        m_RoutingQueue.Remove( iIndex );
                                    }
                                }

                                ulQueuedRecords--;
                            }
                        }
                        catch ( ... )
                        {
                            bSuccess = false;
                            TRACE( _T( "CLogger::Route7kMessagesToSensor(), unspecified exception caught\n" ) );
                        }

                        // If all went well, there should be no items remaining queued.

                        ASSERT( m_RoutingQueue.Items() == 0UL );

                        m_CriticalRoutedMessageQueue.Leave();

                        // Ok, we're done, so bail out.

                        break;
                    }
                }
            }
        }
        else
        {
            bSuccess = true;        // No routing sensor set... just emit a debug message but it's not a failure.
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Route7kMessagesToSensor(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::Route7kMessagesToSensor(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
void CLogger::RestoreMessageRoutingFromRegistry( void )
{
    CRegKey Key;
    DWORD   dwValue;
    int     iRoutingSensorIndex = m_iInvalidSensorIndex;

    Key.Attach( hKey6046_c );

    if ( Key.Open( hKey6046_c, lpsz6046RegistryKey_c ) == ERROR_SUCCESS )
    {
        dwValue = 0UL;

        if ( Key.QueryValue( dwValue, lpszRoutingIndexKey_c ) == ERROR_SUCCESS )
        {
            iRoutingSensorIndex = static_cast<int>( dwValue );
        }

        dwValue = 0UL;

        if ( Key.QueryValue( dwValue, lpszRoutingEnableKey_c ) == ERROR_SUCCESS )
        {
            if ( dwValue == 0 )
            {
                iRoutingSensorIndex = m_iInvalidSensorIndex;
            }
        }

        Key.Close();
    }

    SetRoutingSensor( iRoutingSensorIndex );
}

inline
bool CLogger::ProcessAlarmMessage(  const EALARMID  &reAlarmId,
                                    const int       &riSecondaryIndex )

{
    bool bSuccess = false;              // Assume failure for now.

    try
    {
        ASSERT( m_pClients != NULL );

        if ( m_pClients == NULL )
        {
            ThrowMessage_m( "m_pClients is NULL" );
        }

        CDynamicBuffer<BYTE> AlarmRecord( g_dwMaxAlarmMessageLength );

        if ( ! m_Alarm.Retrieve7kAlarmRecord( reAlarmId, riSecondaryIndex, AlarmRecord ) )
        {
            ThrowMessage_m( "m_Alarm.Retrieve7kAlarmRecord() failed" );
        }

        const unsigned long ulSize = AlarmRecord.Size();

        if ( ulSize > 0UL )
        {
            if ( ! m_pClients->SendAlarm( reAlarmId, AlarmRecord.GetAt( 0 ), ulSize ) )
            {
                ThrowMessage_m( "m_pClients->SendAlarm() failed" );
            }
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "CLogger::ProcessAlarmMessage(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::ProcessAlarmMessage(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
void CLogger::SetTimeSynchronizationMode( void )
{
    CRegKey Key;
    DWORD   dwValue;

    bool bSet = false;

    m_eTimeSyncMode = timesyncModeStandAlone;

    Key.Attach( hKey6046_c );

    if ( Key.Open( hKey6046_c, lpsz6046RegistryKey_c ) == ERROR_SUCCESS )
    {
        dwValue = 0UL;

        if ( Key.QueryValue( dwValue, lpszTimeSyncModeKey_c ) == ERROR_SUCCESS )
        {
            m_eTimeSyncMode = static_cast<ETIMESYNCMODE>( dwValue );
            bSet = true;
        }

        Key.Close();
    }

    TRACE( _T( "TimeSyncMode set to %lu (%s)\n" ),  static_cast<int>( m_eTimeSyncMode ), ( bSet ? _T( "registry" ) : _T( "default" ) ) );
}

inline
void CLogger::TerminateClientConnection( const int &riSocketIndex )
{
    if ( m_pClients != NULL )
    {
        m_pClients->Disconnect( riSocketIndex );
    }
}

inline
bool CLogger::PreProcessRouted7kMessage(    const BYTE              *pby7kRecord,
                                            const unsigned long     &rulNumBytes,
                                            const unsigned long     &rulTimeStamp )
{
    // Here we handle general pre-processing of routed 7k messages.

    bool bSuccess = false;

    try
    {
        DBG_UNREFERENCED_PARAMETER( pby7kRecord );
        DBG_UNREFERENCED_PARAMETER( rulNumBytes );
        DBG_UNREFERENCED_PARAMETER( rulTimeStamp );

        // Give the trigger controller the chance to handle trigger messages.

//        if ( ! m_TriggerController.Process7kMessage( pby7kRecord, rulNumBytes, rulTimeStamp ) )
//        {
//            ThrowMessage_m( "m_TriggerController.Process7kMessage() failed" );
//        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::PreProcessRouted7kMessage(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::PreProcessRouted7kMessage(), Unspecified exception caught.\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::HandleSimulateCommand( const CString &rCommand )
{
    bool bSuccess = true;       // Assume success for now.

    try
    {
        unsigned long ulCommandIndex = 0UL;

        if ( rCommand.IsEmpty() )
        {
            ThrowMessage_m( "Command string is empty" );
        }

        const char *pszCommand = static_cast<LPCTSTR>( rCommand );
        ASSERT( pszCommand != NULL );

        if( ::sscanf( pszCommand, "%lu", &ulCommandIndex ) != 1 )
        {
            ThrowMessage_m( "Can't decode the command index" );
        }

        const ESIMULATECOMMAND eCommand = static_cast<const ESIMULATECOMMAND>( ulCommandIndex );

        switch ( eCommand )
        {
            case simulateCommandAlarms:

                {
                    int     iAlarmIndex = -1;
                    int     iActivation = -1;
                    CString Description;

                    if ( ParseAlarmCommandString( rCommand, ulCommandIndex, iAlarmIndex, iActivation, Description ) )
                    {
                        bSuccess = ( m_SurveyEventLog.ReportEvent(  eventLogAlarm,
                                                                        ( iActivation == 0 ) ? eventActionCleared : eventActionAlarmSet,
                                                                            iAlarmIndex,
                                                                                const_cast<char *>( static_cast<LPCTSTR>( Description ) ) ) != 0 );
                    }
                }

                break;

            default:

                // For now, ignore unhandled commands... i.e., they're not an error.

                bSuccess = true;
                break;
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::HandleSimulateCommand(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CLogger::HandleSimulateCommand(), Unspecified exception caught.\n" ) );
    }

    return bSuccess;
}

inline
bool CLogger::ValidateDongle( void )
{
    bool bTerminate = false;

#ifndef DISABLE_DONGLE

    try
    {
        C6046HaspOptions HaspDongle;

        if ( ! HaspDongle.IsKeyPresent() )
        {
            bTerminate = true;
            ASSERT( false );
        }
        else if ( ! HaspDongle.IsPayloadControllerEnabled() )
        {
            bTerminate = true;
            ASSERT( false );
        }
    }
    catch ( const CHaspException &reHaspException )
    {
        bTerminate = true;
        TRACE( _T( "CLogger::ValidateDongle(), Hasp exception caught -- code: %d\n" ), reHaspException.GetStatus() );
    }
    catch ( ... )
    {
        bTerminate = true;
        TRACE( _T( "CLogger::ValidateDongle(), Unspecified exception caught\n" ) );
    }

#endif

    if ( bTerminate )
    {
        try
        {
            // We're about to terminate so raise an alarm then cause the PLC to terminate.
            // NB: we use PrepareForShutdown( 0 ) to post the terminate pending message thus allowing 
            // the alarm message (from alarmIdKeyMissing) to be processed. Failure to do this will also mean
            // connected clients will not be sent termination nofitication.

            if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                     eventActionAlarmSet,
                                                         alarmIdKeyMissing,
                                                             "No valid RESON HASP key was detected... the PLC will terminate!" ) )
            {
                TRACE( _T( "CLogger::ValidateDongle(), m_SurveyEventLog.ReportEvent() failed\n" ) );
            }

            PrepareForShutdown( 0 );
        }
        catch ( ... )
        {
            TRACE( _T( "CLogger::ValidateDongle(), Unspecified exception caught\n" ) );
        }
    }

    return ( ! bTerminate );
}

inline
void CLogger::CheckDongleAndReschedule( void )
{
    if ( ValidateDongle() )
    {
        m_DongleCheckScheduler.ScheduleNext();
    }
}

inline
void CLogger::EmitTimeToHostsAndReschedule( void )
{
    try
    {
        if ( m_TimeEmitter.IsEnabled() )
        {
            if ( ( m_pClients != NULL ) && ( m_TimeEmitter.GenerateMessage() ) )
            {
                if ( ! m_pClients->SendToAllClients( static_cast<BYTE *>( m_TimeEmitter ), static_cast<unsigned long>( m_TimeEmitter ) ) )
                {
                    TRACE( _T( "CLogger::EmitTimeToHostsAndReschedule(),  m_pClients->SendToAllClients() failed\n" ) );
                }
            }
        }

        m_TimeEmitter.ScheduleNext();
    }
    catch( ... )
    {
        TRACE( _T( "CLogger::EmitTimeToHostsAndReschedule(), Unspecified exception caught.\n" ) );
    }
}

inline
bool CLogger::ParseAlarmCommandString(  const CString   &rCommand,
                                        unsigned long   &rulCommandIndex,
                                        int             &riAlarmIndex,
                                        int             &riActivation,
                                        CString         &rDescription )
{
    bool bSuccess = false;

    try
    {
        ASSERT( ! rCommand.IsEmpty() );

        rDescription.Empty();

        if ( ::sscanf( static_cast<LPCTSTR>( rCommand ), "%lu, %d, %d", &rulCommandIndex, &riAlarmIndex, &riActivation ) == 3 )
        {
            bool bFound        = true;
            int  iLastPosition = 0;

            for ( int iComma = 0; iComma < 3; iComma++ )
            {
                if ( ( iLastPosition = rCommand.Find( ",", iLastPosition ) ) == -1 )
                {
                    bFound = false;
                    break;
                }

                iLastPosition++;
            }

            if ( bFound )
            {
                rDescription = rCommand.Right( rCommand.GetLength() - iLastPosition );

                if ( ! rDescription.IsEmpty() )
                {
                    rDescription.TrimLeft();
                }
            }

            bSuccess = true;
        }
    }
    catch ( ... )
    {
        bSuccess = false;
    }

    return bSuccess;
}

inline
void CLogger::CheckAvailableDiskSpace( void )
{
    // During recording, periodically check to see whether the disk space becomes critical
    // and if so, raise the appropriate alarm. Conversly, if the condition clears, rescind the
    // alarm. The latter condition could concievably occur either by another process (or user) moving
    // the files during the survey or if the logging has been switched to another disk etc with 
    // with available space (in percentage terms).

    if ( m_DiskDataLog.IsRecording() && ( ( m_ulRecordCounter % 10 ) == 0 ) )
    {
        bool bSpaceCritical = ( m_DiskDataLog.PercentDiskSpaceFree() <= fCriticalDiskspacePercent_c );
        bool bAlarmActive   = m_Alarm.IsAlarmActive( alarmIdDiskSpaceCritical );

        if ( bSpaceCritical && ( ! bAlarmActive ) )
        {
            // Raise the alarm.

            if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                    eventActionAlarmSet,
                                                        alarmIdDiskSpaceCritical,
                                                            "Storage at or below critical capacity" ) )
            {
                TRACE( _T( "CLogger::CheckAvailableDiskSpace(), Unable to raise critical disk space alarm\n" ) );
            }
        }
        else if ( ( ! bSpaceCritical ) && bAlarmActive )
        {
            // Clear the alarm.

            if ( ! m_SurveyEventLog.ReportEvent( eventLogAlarm,
                                                    eventActionCleared,
                                                        alarmIdDiskSpaceCritical,
                                                            "Storage capacity no longer at critical level" ) )
            {
                TRACE( _T( "CLogger::CheckAvailableDiskSpace(), Unable to rescind critical disk space alarm\n" ) );
            }
        }
    }
}

inline
bool CLogger::NotifyClientsOfTermination( void )
{
    bool bSuccess = false;

    ASSERT( m_pClients != NULL );

    if ( m_pClients != NULL )
    {
        C7kStatus Status;

        if ( Status.Encode( C7kStatus::messageTypeStatus,
                                C7kStatus::modeUnspecified,
                                    C7kStatus::messageIdShutdown,
                                        NULL,
                                            false ) )
        {
            bSuccess = m_pClients->SendToAllClients( static_cast<BYTE *>( Status ), static_cast<unsigned long>( Status ) );
            ASSERT( bSuccess );
        }
    }

    return bSuccess;
}

inline
void CLogger::QuerySensorSettings( void )
{
    // Solicit the current settings from each sensor, if applicable.

    for ( RunListIterator_t pRunListItem = m_RunList.begin(); pRunListItem != m_RunList.end(); pRunListItem++ )
    {
        CComponent *pComponent = NULL;
        CSensor    *pSensor    = NULL;

        if ( ComponentFromRunListItem( pRunListItem, pComponent ) &&
             SensorFromRunListItem   ( pRunListItem, pSensor    )  )
        {
            SENSORINFO *psSensorInfo = static_cast<SENSORINFO *>( *pSensor );

            if ( pComponent->SendCommand( psSensorInfo->m_iSensorIndex, -1, _T( "QueryParameters" ) ) == ERROR_CODE_FAILURE )
            {
                // A fail code may simply indicate the query command is not implmeneted therefore no alarm is raised here.

                TRACE( _T( "CLogger::QuerySensorSettings(), SendCommand( \"QueryParameters\") failed.\n" ) );
            }
        }
    }
}

inline
void CLogger::DumpRecordInfo(   const int           &riSensorIndex,
                                const unsigned long &rulNextSensorRecordToRead,
                                const BYTE * const   pby7kRecordBuffer,
                                const unsigned long &rulTotalRecordBytes )
{
#if 0

    try
    {
        if ( C7kProtocol::IsValid7kRecord( pby7kRecordBuffer, rulTotalRecordBytes ) )
        {
            const DATARECORDFRAME * const pDRF = reinterpret_cast<const DATARECORDFRAME *>( pby7kRecordBuffer );

            TRACE( _T( "Data record received, type: %lu, index: %d, next: %lu, bytes: %lu.\n" ),    pDRF->m_ulRecordType,
                                                                                                    riSensorIndex,
                                                                                                    rulNextSensorRecordToRead,
                                                                                                    rulTotalRecordBytes );
        }
        else
        {
            TRACE( _T( "Invalid data frame received, index: %d, next: %lu, bytes: %lu !!!\n" ),     riSensorIndex,
                                                                                                    rulNextSensorRecordToRead,
                                                                                                    rulTotalRecordBytes );
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::DumpRecordInfo(), Unspecified exception caught.\n" ) );
    }

#else

    UNREFERENCED_PARAMETER( riSensorIndex );
    UNREFERENCED_PARAMETER( rulNextSensorRecordToRead );
    UNREFERENCED_PARAMETER( pby7kRecordBuffer );
    UNREFERENCED_PARAMETER( rulTotalRecordBytes );

#endif
}

///////////////////////////////////////////////////////////////////////////////
// CLogger::CCommandHandler internal helper.

#ifdef USE_OPTIMAL_COMMAND_INDEXING
#define MAP_HANDLER( Index, Command )   m_Map.push_back( Command );
#else
#define MAP_HANDLER( Index, Command )   m_Map[ Index ] = Command
#endif

inline
CLogger::CCommandHandler::CCommandHandler( void )
{
    m_pParent = NULL;

    try
    {
        MAP_HANDLER( 0UL,   CLogger::CommandGeneric );
        MAP_HANDLER( 1UL,   CLogger::CommandLogging );
        MAP_HANDLER( 2UL,   CLogger::CommandSwitch );
        MAP_HANDLER( 3UL,   CLogger::CommandPath );
        MAP_HANDLER( 4UL,   CLogger::CommandVersion );
        MAP_HANDLER( 5UL,   CLogger::CommandLoad );
        MAP_HANDLER( 6UL,   CLogger::CommandSave );
        MAP_HANDLER( 7UL,   CLogger::CommandSubscribe );
        MAP_HANDLER( 8UL,   CLogger::CommandUnsubscribe );
        MAP_HANDLER( 9UL,   CLogger::CommandAlarm );
        MAP_HANDLER( 10UL,  CLogger::CommandStatus );
        MAP_HANDLER( 11UL,  CLogger::CommandVerbose );
        MAP_HANDLER( 12UL,  CLogger::CommandReset );
        MAP_HANDLER( 13UL,  CLogger::CommandShutdown );
        MAP_HANDLER( 14UL,  CLogger::CommandModules );
        MAP_HANDLER( 15UL,  CLogger::CommandRunList );
        MAP_HANDLER( 16UL,  CLogger::CommandAdd );
        MAP_HANDLER( 17UL,  CLogger::CommandRemove );
        MAP_HANDLER( 18UL,  CLogger::CommandReport );
        MAP_HANDLER( 19UL,  CLogger::CommandPort );
        MAP_HANDLER( 20UL,  CLogger::CommandTimeSync );
        MAP_HANDLER( 21UL,  CLogger::CommandHealth );
        MAP_HANDLER( 22UL,  CLogger::CommandAutoStart );
        MAP_HANDLER( 23UL,  CLogger::CommandAutoHealthCheck );
        MAP_HANDLER( 24UL,  CLogger::CommandRouteMessages );
        MAP_HANDLER( 25UL,  CLogger::CommandReportAlarms );
        MAP_HANDLER( 26UL,  CLogger::CommandLoggingSetup );
        MAP_HANDLER( 27UL,  CLogger::CommandSimulate );
        MAP_HANDLER( 28UL,  CLogger::CommandTimeMessage );
        MAP_HANDLER( 29UL,  CLogger::CommandIsAlive );
    }
    catch ( ... )
    {
        TRACE( _T( "CCommandHandler::CCommandHandler(), Can't create handler map." ) );
    }
}

inline
CLogger::CCommandHandler::~CCommandHandler( void )
{
    try
    {
        m_Map.clear();
        m_pParent = NULL;
    }
    catch ( ... )
    {
        TRACE( _T( "CCommandHandler::CCommandHandler(), Can't clear map." ) );
    }
}

inline
void CLogger::CCommandHandler::SetParent( CLogger *pParent )
{
    ASSERT( pParent != NULL );
    m_pParent = pParent;
}

inline
bool CLogger::CCommandHandler::operator ()  (   const unsigned long &rulCommand,
                                                char                *pszOptions,
                                                const int           &riSocketIndex,
                                                const int           &riSensorIndex,
                                                const unsigned long &rulAction      )
{
    bool bSuccess = false;

    try
    {
        ASSERT( m_pParent != NULL );

#ifdef USE_OPTIMAL_COMMAND_INDEXING

        if ( ( ! m_Map.empty() ) && ( rulCommand < static_cast<unsigned long>( m_Map.size() ) ) )
        {
            ASSERT( m_Map[ rulCommand ] != NULL );
            bSuccess = ( m_pParent->*(m_Map[ rulCommand ]) ) ( pszOptions, riSocketIndex, riSensorIndex, rulAction );
        }

#else

        HandlerMapIterator_t pHandler = m_Map.find( rulCommand );
        
        if ( pHandler != m_Map.end() )
        {
            ASSERT( pHandler->second != NULL );
            bSuccess = ( m_pParent->*((pHandler->second))) ( pszOptions, riSocketIndex, riSensorIndex, rulAction );
        }

#endif

    }
    catch ( ... )
    {
        bSuccess = false;
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// CLogger::CModuleItem internal helper.

inline
CLogger::CModuleItem::CModuleItem( void )
    : m_iInvalidModuleNumber( -1 )
{
    m_iModuleNumber     = m_iInvalidModuleNumber;
    m_iMediaType        = mediaTypeNIC;
    m_szFileName[ 0 ]   = '\0';
    m_szModuleName[ 0 ] = '\0';
    m_pComponent        = NULL;
}

inline
CLogger::CModuleItem::CModuleItem( const CModuleItem &rRhs )
    : m_iInvalidModuleNumber( -1 )
{
    m_iModuleNumber     = rRhs.m_iModuleNumber;
    m_iMediaType        = rRhs.m_iMediaType;
    m_szFileName[ 0 ]   = '\0';
    m_szModuleName[ 0 ] = '\0';

    if ( strlen( rRhs.m_szFileName ) > 0 )
    {
        strncpy( m_szFileName, rRhs.m_szFileName, _MAX_FNAME - 1 );
    }

    if ( strlen( rRhs.m_szModuleName ) > 0 )
    {
        strncpy( m_szModuleName, rRhs.m_szModuleName, _MAX_PATH - 1 );
    }

    m_pComponent = rRhs.m_pComponent;
}

inline
CLogger::CModuleItem::CModuleItem( const MODULE &rsModule, CComponent *pComponent )
    : m_iInvalidModuleNumber( -1 )
{
    m_iMediaType    = rsModule.m_iMediaType;
    m_iModuleNumber = rsModule.m_iModuleNumber;

    strncpy( m_szFileName,   rsModule.m_szFileName,   _MAX_FNAME - 1 );
    strncpy( m_szModuleName, rsModule.m_szModuleName, _MAX_PATH  - 1  );

    m_pComponent = pComponent;
}

inline
CLogger::CModuleItem::~CModuleItem( void )
{
    m_iModuleNumber     = m_iInvalidModuleNumber;
    m_iMediaType        = mediaTypeNIC;
    m_szFileName[ 0 ]   = '\0';
    m_szModuleName[ 0 ] = '\0';

    m_pComponent        = NULL;                 // Don't destroy object, external client will do that.
}

inline
bool CLogger::CModuleItem::operator < ( const CModuleItem &rRhs ) const
{
    return ( m_iModuleNumber < rRhs.m_iModuleNumber );
}

inline
bool CLogger::CModuleItem::operator == ( const int &riModuleNumber ) const
{
    return ( m_iModuleNumber == riModuleNumber );
}

inline
bool CLogger::CModuleItem::Load( LPCTSTR lpszPath )
{
    bool bLoaded = false;

    CString FileName( lpszPath );
    
    FileName += m_szFileName;

    if ( m_pComponent == NULL )
    {
        m_pComponent = new CComponent( FileName );
    }

    if ( m_pComponent != NULL )
    {
        if ( ( bLoaded = m_pComponent->IsLoaded() ) == false )
        {
            bLoaded = m_pComponent->LoadDLL( FileName );
        }
    }

    return bLoaded;
}

inline
bool CLogger::CModuleItem::Unload( void )
{
    bool bUnloaded = true;

    if ( m_pComponent != NULL )
    {
        delete m_pComponent;
        m_pComponent = NULL;
    }

    return bUnloaded;
}

///////////////////////////////////////////////////////////////////////////////
// CLogger::CRunItem internal helper.

inline
CLogger::CRunItem::CRunItem(    const int          &riModuleNumber,
                                const SENSORINFO   *psSensorInfo )
{
    m_iModuleNumber = riModuleNumber;

    if ( psSensorInfo != NULL )
    {
        m_Sensor.Set( *psSensorInfo );
    }
}

inline
CLogger::CRunItem::~CRunItem( void )
{
}

inline
bool CLogger::CRunItem::operator < ( const CRunItem &rRhs ) const
{
    return ( m_iModuleNumber < rRhs.m_iModuleNumber );
}

inline
bool CLogger::CRunItem::operator == ( const CRunItem &rRhs ) const
{
    return ( m_iModuleNumber == rRhs.m_iModuleNumber );
}

inline
CLogger::CRunItem::operator SENSORINFO * ( void ) const
{
    return ( (SENSORINFO *) m_Sensor );
}

///////////////////////////////////////////////////////////////////////////////
// CLogger::CQueuedCommand internal helper.

inline
CLogger::CQueuedCommand::CQueuedCommand(    const unsigned long             &rulCommand,
                                            const int                       &riSensorIndex,
                                            const unsigned long             &rulAction,
                                            LPCTSTR                         lpszOptions,
                                            const unsigned long             &rulTimeStamp,
                                            const int                       &riSocketIndex )
{
    try
    {
        m_ulCommand    = rulCommand;
        m_iSensorIndex = riSensorIndex;
        m_ulAction     = rulAction;
        m_ulTimeStamp  = rulTimeStamp;
        m_iSocketIndex = riSocketIndex;

        ::strncpy( m_szOptions, lpszOptions, g_ulMaxOptionsLength - 1 );
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::CQueuedCommand::CQueuedCommand(), Unspecified exception caught.\n" ) );
    }
}

inline
CLogger::CQueuedCommand::CQueuedCommand( const CQueuedCommand &rRhs )
{
    try
    {
        Copy( rRhs );
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::CQueuedCommand::~CQueuedCommand(), Unspecified exception caught.\n" ) );
    }
}

inline
CLogger::CQueuedCommand::~CQueuedCommand( void )
{
}

inline
CLogger::CQueuedCommand & CLogger::CQueuedCommand::operator = ( const CQueuedCommand &rRhs )
{
    if ( this != &rRhs )
    {
        Copy( rRhs );
    }

    return *this;
}

inline
void CLogger::CQueuedCommand::Copy( const CQueuedCommand &rRhs )
{
    try
    {
        m_ulCommand     = rRhs.m_ulCommand;
        m_iSensorIndex  = rRhs.m_iSensorIndex;
        m_ulAction      = rRhs.m_ulAction;
        m_ulTimeStamp   = rRhs.m_ulTimeStamp;
        m_iSocketIndex  = rRhs.m_iSocketIndex;

        ::strcpy( m_szOptions, rRhs.m_szOptions );
    }
    catch ( ... )
    {
        TRACE( _T( "CLogger::CQueuedCommand::Copy(), Unspecified exception caught.\n" ) );
    }
}

inline
int CLogger::CQueuedCommand::SensorIndex( void ) const
{
    return m_iSensorIndex;
}

inline
unsigned long CLogger::CQueuedCommand::Command( void ) const
{
    return m_ulCommand;
}

inline
char * CLogger::CQueuedCommand::Options( void ) const
{
    return const_cast<char *>(&m_szOptions[ 0 ]);
}

inline
unsigned long CLogger::CQueuedCommand::Action( void ) const
{
    return m_ulAction;
}

inline
int CLogger::CQueuedCommand::SocketIndex( void ) const
{
    return m_iSocketIndex;
}

inline
CLogger::CQueuedCommand::operator unsigned long ( void ) const
{
    return m_ulTimeStamp;
}

///////////////////////////////////////////////////////////////////////////////
// CLogger::CQueueud7kMessage internal helper.

inline
CLogger::CQueued7kMessage::CQueued7kMessage(    const unsigned long &rulTimeStamp,
                                                const int           &riSocketIndex )
    :tagQUEUEDMESSAGE()
{
    m_ulTimeStamp  = rulTimeStamp;
    m_iSocketIndex = riSocketIndex;
}

inline
CLogger::CQueued7kMessage::~CQueued7kMessage( void )
{
}

///////////////////////////////////////////////////////////////////////////////
// CCounter internal helper. See definition in anonymous namespace at top of this
// file. Used, in part, to simplify assigning the enumeration constant to the 
// devices using STLs map<>.

template<typename tCountType>
inline CCounter<tCountType>::CCounter( void )
{
    m_tCount = 0;
}

template<typename tCountType>
inline CCounter<tCountType>::CCounter( const CCounter<tCountType> &rRhs )
{
    m_tCount = rRhs.m_tCount;
}

template<typename tCountType>
inline CCounter<tCountType> & CCounter<tCountType>::operator = ( const CCounter<tCountType> &rRhs )
{
    if ( this != &rRhs )
    {
        m_tCount = rRhs.m_tCount;
    }

    return *this;
}

template<typename tCountType>
inline CCounter<tCountType>::~CCounter( void )
{
}

template<typename tCountType>
inline CCounter<tCountType> & CCounter<tCountType>::operator ++ ( int )
{
    m_tCount++;

    return *this;
}

template<typename tCountType>
inline CCounter<tCountType> & CCounter<tCountType>::operator ++ ( void )
{
    ++m_tCount;
}

template<typename tCountType>
inline CCounter<tCountType>::operator tCountType ( void ) const
{
    return m_tCount;
}

