// Copyright (c) 2025 Metrolab Technology S.A., Geneva, Switzerland (www.metrolab.com)
// See the included file LICENSE.txt for the licensing conditions.

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief Code for Metrolab THM1176/TFM1186 Instrument Manager.

#include "CTHM1176InstrumentManager.h"
#include "OSDefines.h"
#include "Helpers.h"
#include "Exception.h"

using namespace MTL;
using namespace MTL::Instrument;
using namespace MTL::Instrument::THM1176Types;



//////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------//
//	THM1176 Instrument Scanner											//
//----------------------------------------------------------------------//
void CTHM1176InstrumentScanner::HandleError					(QString						Description,
															 QString						Context)
{
	// Create the error structure.
	sError		l_Error;
	l_Error.Code		= CTHM1176InstrumentManager::THM1176_INST_SCANNER_ERROR;
	l_Error.Description	= Description.toStdString();
	l_Error.Context		= Context.toStdString();

	// Emit the update.
	CErrorList	l_ErrorList;
	l_ErrorList.push_back(l_Error);
	emit UpdateErrorList(l_ErrorList);

} // CTHM1176InstrumentScanner::HandleError

void CTHM1176InstrumentScanner::Start						(THM1176_RSRC_MGR_CLS *         pResourceManager)
{
	m_pResourceManager = pResourceManager;
	m_TimerID = startTimer(THM1176_SCAN_INTERVAL);

} // CTHM1176InstrumentScanner::Start

void CTHM1176InstrumentScanner::Stop						(void)
{
	killTimer(m_TimerID);
	m_pResourceManager = nullptr;

    // Quit the event loop.
    QThread::currentThread()->quit();

} // CTHM1176InstrumentScanner::Stop

void	CTHM1176InstrumentScanner::timerEvent				(QTimerEvent *					Event)
{
	MTL_Unused(Event)

	// Get the current list, and if it has changed, emit the signal.
	if (nullptr != m_pResourceManager)
	{
		CResourceList l_InstrumentList;
        m_pResourceManager->FindResources(l_InstrumentList, THM1176_RSRC_FILTER);

		if (m_InstrumentList != l_InstrumentList)
		{
			m_InstrumentList = l_InstrumentList;
			emit UpdateInstrumentList(m_InstrumentList);
		}
	}

	// Gripe if the Resource Manager is not up.
	else
		HandleError("Resource Manager not running", __func__);

} // CTHM1176InstrumentScanner::timerEvent



//////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------//
//	THM1176 Instrument Controller: Error reporting						//
//----------------------------------------------------------------------//
void CTHM1176InstrumentController::HandleError				(QString							Description,
															 QString							Context)
{
	// Get the THM1176 error list, if the THM1176 is connected.
	CErrorList	l_ErrorList;
	if (nullptr != m_pTHM1176)
    {
		l_ErrorList = m_pTHM1176->CurrentErrorList();
        m_pTHM1176->ClearErrorList();
    }

	// Create the error structure for the header message.
	sError		l_Error;
	l_Error.Code		= CTHM1176InstrumentManager::THM1176_INST_CTLR_ERROR;
	l_Error.Description	= Description.toStdString();
	l_Error.Context		= Context.toStdString();

	// Insert the header message at the front of the list.
	l_ErrorList.insert(l_ErrorList.begin(), l_Error);

	// Emit the error list update.
	emit UpdateErrorList(l_ErrorList);

} // CTHM1176InstrumentController::HandleError

//----------------------------------------------------------------------//
//	THM1176 Instrument Controller: Initialize / Shutdown				//
//----------------------------------------------------------------------//
void	CTHM1176InstrumentController::Start					(THM1176_RSRC_MGR_CLS *             pResourceManager)
{
	m_pResourceManager = pResourceManager;

} // CTHM1176InstrumentController::Start

void	CTHM1176InstrumentController::Stop					(void)
{
	// Disconnect the THM1176 and deallocate the object, if it exists.
	if (nullptr != m_pTHM1176)
	{
		m_pTHM1176->Disconnect();
		delete m_pTHM1176;
	}

	// Reset the object pointers to NULL, just to be sure.
	m_pResourceManager	= nullptr;
	m_pTHM1176			= nullptr;
	
    // Quit the event loop.
	QThread::currentThread()->quit();

} // CTHM1176InstrumentController::Stop

//----------------------------------------------------------------------//
//	THM1176 Instrument Controller slots: Connection						//
//----------------------------------------------------------------------//
void	CTHM1176InstrumentController::ClearInstrumentInfo	(void)
{
	m_CurrentInstrument.clear();

	m_Identification.clear();
	m_RangeList.clear();
	m_UnitsList.clear();
	m_DivisorList.clear();
	m_AveragingParmBounds.clear();
	m_TriggerParmBounds.clear();
	m_RangeParmBounds.clear();

	m_AveragingParms.clear();
	m_TriggerParms.clear();
	m_OutputSelect.clear();
	m_SleepParm = false;
	m_Units = kT;
	m_RangeParms.clear();
	m_CommFormat = kComFormatAscii;

} // CTHM1176InstrumentController::ClearInstrumentInfo

void	CTHM1176InstrumentController::GetInstrumentInfo		(std::string					Context)
{
    // Get the parsed identification string.
    if (!m_pTHM1176->GetIdentification(m_Identification))
        throw CException<CTHM1176InstrumentManager>("Cannot get identification string", Context);

    // Get the range list.
    if (!m_pTHM1176->GetAllRanges(m_RangeList))
        throw CException<CTHM1176InstrumentManager>("Cannot get range list", Context);

    // Get the list of units supported by this instrument, and associated divisors.
    MakeUnitsList(Context);

    // Get the Averaging parameter bounds.
    if (!m_pTHM1176->ParmAveragingGet(m_AveragingParmBounds))
        throw CException<CTHM1176InstrumentManager>("Cannot get Averaging parameter bounds", Context);

    // Get the Trigger parameter bounds.
    if (!m_pTHM1176->ParmTriggerInputGet(m_TriggerParmBounds))
        throw CException<CTHM1176InstrumentManager>("Cannot get Trigger parameter bounds", Context);

    // Get the Range parameter bounds.
    if (!m_pTHM1176->ParmRangeGet(m_RangeParmBounds))
        throw CException<CTHM1176InstrumentManager>("Cannot get Range parameter bounds", Context);

    // Get the Averaging parameters.
    m_AveragingParms.NoPoints		= m_AveragingParmBounds.NoPoints.Val;

    // Get the Trigger parameters.
    m_TriggerParms.Source			= m_TriggerParmBounds.Source;
    m_TriggerParms.Period_s			= m_TriggerParmBounds.Period_s.Val;
    m_TriggerParms.Count			= m_TriggerParmBounds.Count.Val;

    // Reset the output selection.
    m_OutputSelect.clear();
    m_OutputSelect.NoMeasurements	= m_TriggerParmBounds.Count.Val;

    // Get the sleep parameter.
    if (!m_pTHM1176->ParmSleepGet(m_SleepParm))
        throw CException<CTHM1176InstrumentManager>("Cannot get Sleep parameter", Context);

    // Get the units.
    GetCurrentUnits(Context);

    // Get the Range parameters.
    m_RangeParms.Auto				= m_RangeParmBounds.Auto;
    m_RangeParms.Range				= m_RangeParmBounds.Range.Val;

    // Get the Communication Format.
    if (!m_pTHM1176->GetFormat(m_CommFormat))
        throw CException<CTHM1176InstrumentManager>("Cannot get Communication Format", Context);

} // CTHM1176InstrumentController::GetInstrumentInfo

void	CTHM1176InstrumentController::GetInstrumentParameters	(std::string					Context)
{
    // Get the Averaging parameters.
    if (!m_pTHM1176->ParmAveragingGet(m_AveragingParms))
        throw CException<CTHM1176InstrumentManager>("Cannot get Averaging parameters", Context);

    // Get the Trigger parameters.
    if (!m_pTHM1176->ParmTriggerInputGet(m_TriggerParms))
        throw CException<CTHM1176InstrumentManager>("Cannot get Trigger parameters", Context);

    // Reset the output selection.
    m_OutputSelect.clear();
    m_OutputSelect.NoMeasurements	= m_TriggerParmBounds.Count.Val;

    // Get the sleep parameter.
    if (!m_pTHM1176->ParmSleepGet(m_SleepParm))
        throw CException<CTHM1176InstrumentManager>("Cannot get Sleep parameter", Context);

    // Get the units.
    GetCurrentUnits(Context);

    // Get the Range parameters.
    if (!m_pTHM1176->ParmRangeGet(m_RangeParms))
        throw CException<CTHM1176InstrumentManager>("Cannot get Range parameters", Context);

    // Get the Communication Format.
    if (!m_pTHM1176->GetFormat(m_CommFormat))
        throw CException<CTHM1176InstrumentManager>("Cannot get Communication Format", Context);

} // CTHM1176InstrumentController::GetInstrumentParameters

void	CTHM1176InstrumentController::PublishInstrumentInfo	(void)
{
	emit UpdateCurrentInstrument(m_CurrentInstrument);

	emit UpdateIdentification(m_Identification);
	emit UpdateRangeList(m_RangeList);
	emit UpdateUnitsList(m_UnitsList);
	emit UpdateDivisorList(m_DivisorList);
	emit UpdateAveragingParmBounds(m_AveragingParmBounds);
	emit UpdateTriggerParmBounds(m_TriggerParmBounds);
	emit UpdateRangeParmBounds(m_RangeParmBounds);

	emit UpdateOutputSelect(m_OutputSelect);
	emit UpdateSleepParm(m_SleepParm);
	emit UpdateUnits(m_Units);
	emit UpdateCommFormat(m_CommFormat);

} // CTHM1176InstrumentController::PublishInstrumentInfo

void	CTHM1176InstrumentController::PublishInstrumentParameters	(void)
{
	emit UpdateAveragingParms(m_AveragingParms);
	emit UpdateTriggerParms(m_TriggerParms);
	emit UpdateOutputSelect(m_OutputSelect);
	emit UpdateSleepParm(m_SleepParm);
	emit UpdateUnits(m_Units);
	emit UpdateRangeParms(m_RangeParms);
	emit UpdateCommFormat(m_CommFormat);

} // CTHM1176InstrumentController::PublishInstrumentParameters

void	CTHM1176InstrumentController::UpdateInstrumentList	(CResourceList					InstrumentList)
{
	// Save the instrument list.
	m_InstrumentList = InstrumentList;

	// All is well if we have no active instrument, or if the active instrument is still in the list.
	if (m_CurrentInstrument.empty() && m_pTHM1176 == nullptr) return;

	for (auto l_pTHM1176ResourceID = InstrumentList.begin(); l_pTHM1176ResourceID < InstrumentList.end(); l_pTHM1176ResourceID++)
		if (m_CurrentInstrument == *l_pTHM1176ResourceID) return;

    // If the active instrument is no longer in the list, stop the continuous-measurement timer.
    if (m_TimerID)
        killTimer(m_TimerID);
    m_TimerID = 0;

    // Disconnect the instrument.
	if (m_pTHM1176 != nullptr)
		m_pTHM1176->Disconnect();
	m_pTHM1176 = nullptr;
	emit UpdateInstrumentPointer(m_pTHM1176);

	// Flag that we are not connected.
	m_OperatingMode = kTHM1176NotConnected;
	emit UpdateOperatingMode(m_OperatingMode);

	// Clear the information on the current instrument.
	// Note: instrument in Reset mode forces Instrument Manager to emit all parameter-value notifications.
	ClearInstrumentInfo();
	PublishInstrumentInfo();

} // CTHM1176InstrumentController::UpdateInstrumentList

void	CTHM1176InstrumentController::SetCurrentInstrument(tResourceName					CurrentInstrument)
{
	// Prepare to catch general administrative inconsistencies.
	try
	{
		// Make sure the resource name is in the current resource list.
		auto l_pResource = m_InstrumentList.begin();
		for (; l_pResource < m_InstrumentList.end(); l_pResource++)
			if (*l_pResource == CurrentInstrument) break;
		if (l_pResource >= m_InstrumentList.end())
			throw CException<CTHM1176InstrumentManager>("Resource name not in resource list", __func__);

		// If the resource name is the currently selected one, there is nothing to do.
		if (CurrentInstrument == m_CurrentInstrument)
			return;

		// Disconnect the current instrument, if it exists.
		if (nullptr != m_pTHM1176)
		{
			m_pTHM1176->Disconnect();
			delete m_pTHM1176;
			m_pTHM1176 = nullptr;
			emit UpdateInstrumentPointer(m_pTHM1176);

			// Flag that we are not connected.
			m_OperatingMode = kTHM1176NotConnected;
			emit UpdateOperatingMode(m_OperatingMode);

			// Clear the information on the current instrument.
			// Note: instrument in Reset mode forces Instrument Manager to emit all parameter-value notifications.
			ClearInstrumentInfo();
			PublishInstrumentInfo();
		}

		// Make sure we have the pointer to the Resource Manager.
		if (nullptr == m_pResourceManager)
			throw CException<CTHM1176InstrumentManager>("Resource Manager not running", __func__);

        // Create the new THM1176 object.
        m_pTHM1176 = new CTHM1176Instrument<THM1176_INSTR_CLS, THM1176_RSRC_MGR_CLS>(*m_pResourceManager, CurrentInstrument);
		if (nullptr == m_pTHM1176)
			throw CException<CTHM1176InstrumentManager>("Cannot create THM1176 object", __func__);
		emit UpdateInstrumentPointer(m_pTHM1176);
	}
	catch (CException<CTHM1176InstrumentManager> & rE)
	{
		// Notify Instrument Manager that SetCurrentInstrument failed.
		emit UpdateCurrentInstrument(m_CurrentInstrument);

		// Emit the error message(s).
		HandleError(rE.message(), rE.context());
		return;
	}

	// Prepare to catch errors that involve actual instrument I/O.
	try
	{
		// Connect to the THM1176.
        std::string ErrorMessage;
        if (!m_pTHM1176->Connect(THM1176_CONNECT_TIMEOUT, true, &ErrorMessage))
            throw CException<CTHM1176InstrumentManager>("Cannot connect to THM1176: " + ErrorMessage, __func__);

		// Retrieve the instrument information from the instrument.
		GetInstrumentInfo(__func__);
		m_CurrentInstrument = CurrentInstrument;

		// Now we have all the information, we can publish it.
		// Note: instrument in Reset mode forces Instrument Manager to emit all parameter-value notifications.
		PublishInstrumentInfo();

		// Set the operating mode to Idle.
		m_OperatingMode = kTHM1176Idle;
		emit UpdateOperatingMode(m_OperatingMode);
	}
	catch (CException<CTHM1176InstrumentManager> & rE)
    {
        // Emit the error message(s).
        HandleError(rE.message(), rE.context()); //Must be donde before delete m_pTHM1176 otherwise we loose errors.
		delete m_pTHM1176;
		m_pTHM1176 = nullptr;

		// Notify Instrument Manager that SetCurrentInstrument failed.
		ClearInstrumentInfo();
		emit UpdateCurrentInstrument(m_CurrentInstrument);

		return;
    } catch (const std::exception& ex) {
        // Handle standard exceptions.
        HandleError(ex.what(), __func__);
    } catch (...) {
        // Handle unknown exceptions.
        HandleError("Unknown error occurred during THM1176 connection", __func__);
    }

} // CTHM1176InstrumentController::SetCurrentInstrument

//----------------------------------------------------------------------//
//	THM1176 Instrument Controller slots: Instrument control				//
//----------------------------------------------------------------------//
void	CTHM1176InstrumentController::SetOperatingMode		(eTHM1176OperatingMode			OperatingMode)
{
	try
	{
        // Make sure we're connected, and not in the requested operating mode already.
        if (m_pTHM1176 == nullptr)
			throw CException<CTHM1176InstrumentManager>("Cannot change operating mode", __func__);
        else if (m_OperatingMode == OperatingMode)
            return;

		// If we were in continuous measurement mode, stop the timer and abort the measurements.
        if (kTHM1176MeasureContinuously == m_OperatingMode)
		{
            if (0 != m_TimerID)
            {
                killTimer(m_TimerID);
                m_TimerID = 0;
            }

			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort ", __func__);
		}

		// Handle the different cases.
		m_OperatingMode = OperatingMode;
		switch (m_OperatingMode)
		{
		case kTHM1176NotConnected:
			if (nullptr != m_pTHM1176)
			{
				// Abort any ongoing measurements.
				if (!m_pTHM1176->Abort())
					throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

				// Disconnect the instrument.
				m_pTHM1176->Disconnect();
				delete m_pTHM1176;
				m_pTHM1176 = nullptr;
				emit UpdateInstrumentPointer(m_pTHM1176);

				// Flag that we are not connected.
				m_OperatingMode = kTHM1176NotConnected;
				emit UpdateOperatingMode(m_OperatingMode);

				// Clear the information on the current instrument.
				// Note: instrument in Reset mode forces Instrument Manager to emit all parameter-value notifications.
				ClearInstrumentInfo();
				PublishInstrumentInfo();
			}
			break;

		case kTHM1176Reset:
			// Abort any ongoing measurements.
			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

			// Set the operating mode to Reset.
			m_OperatingMode = kTHM1176Reset;
			emit UpdateOperatingMode(m_OperatingMode);

			// Reset the instrument.
			if (!m_pTHM1176->Reset())
				throw CException<CTHM1176InstrumentManager>("Cannot reset", __func__);

			// Retrieve and publish the instrument parameters.
			GetInstrumentParameters(__func__);
			PublishInstrumentParameters();

			// Set the operating mode back to Idle.
			m_OperatingMode = kTHM1176Idle;
			emit UpdateOperatingMode(m_OperatingMode);
			break;

		case kTHM1176Idle:
			// Abort any ongoing measurements.
			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

			// Flag that we're idle.
			m_OperatingMode = kTHM1176Idle;
			emit UpdateOperatingMode(m_OperatingMode);
			break;

		case kTHM1176Measure:
			// Abort any ongoing measurements.
			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

			// Flag that we're Measuring.
			m_OperatingMode = kTHM1176Measure;
			emit UpdateOperatingMode(m_OperatingMode);

			// Initiate the measurement.
			if (!m_pTHM1176->Initiate(false))
				throw CException<CTHM1176InstrumentManager>("Cannot initiate measurement", __func__);

			// What we do next depends on the trigger mode.
			switch (m_TriggerParms.Source)
			{
			case kInputTrigSrcBus:
				// We will fetch the measurements after the appropriate number of triggers.
				m_TriggerCount = 0;
				break;

			default:
			{
				// Transmit the measurements.
				CMeasurement			l_Measurement;
                eUnits                  l_Units;
				sMeasurementConditions	l_MeasurementConditions;
				if (!m_pTHM1176->MeasurementsGet(m_OutputSelect,
												 l_Measurement.Bx, l_Measurement.By, l_Measurement.Bz,
                                                 l_Units, l_Measurement.Temp, l_Measurement.TimestampList,
												 &l_MeasurementConditions))
					throw CException<CTHM1176InstrumentManager>("Cannot fetch measurement", __func__);

                TranslateUnits(l_Measurement, l_Units);

				l_Measurement.AveragingParms	= l_MeasurementConditions.AveragingParms;
				l_Measurement.TriggerParms		= l_MeasurementConditions.TriggerParms;
				l_Measurement.OutputSelect		= m_OutputSelect;
				l_Measurement.SleepParm			= m_SleepParm;
				l_Measurement.RangeParms		= m_RangeParms;
                l_Measurement.RangeParms.Range  = l_MeasurementConditions.Range;
				l_Measurement.CommFormat		= m_CommFormat;

				l_Measurement.Warnings			= m_pTHM1176->CurrentErrorList();

				emit UpdateMeasurement(l_Measurement);

				// Flag that we're idle again.
				m_OperatingMode = kTHM1176Idle;
				emit UpdateOperatingMode(m_OperatingMode);
				break;
			}
			}
			break;

		case kTHM1176MeasureContinuously:
			// Abort any ongoing measurements.
			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

			// Flag that we're Measuring Continuously.
			m_OperatingMode = kTHM1176MeasureContinuously;
			emit UpdateOperatingMode(m_OperatingMode);

			// What we do next depends on the trigger mode.
			switch (m_TriggerParms.Source)
			{
			// For Immediate trigger, start a timer to initiate and read the results.
			case kInputTrigSrcImmediate:
				m_TimerID = startTimer(0);
				break;

            // For Timed trigger, initiate the measurement continuously, and start
            // a timer to read the results.
			case kInputTrigSrcTimer:
				if (!m_pTHM1176->Initiate(true))
					throw CException<CTHM1176InstrumentManager>("Cannot initiate measurement", __func__);
				m_TimerID = startTimer(0);
				break;

            // For Bus triggered, initiate the first measurement.
			case kInputTrigSrcBus:
				m_TriggerCount = 0;
				if (!m_pTHM1176->Initiate(false))
					throw CException<CTHM1176InstrumentManager>("Cannot initiate measurement", __func__);
				break;
			}
			break;

		case kTHM1176CalibrateZeroOffset:
			// Abort any ongoing measurements.
			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

			// Flag that we're calibrating the zero offset.
			m_OperatingMode = kTHM1176CalibrateZeroOffset;
			emit UpdateOperatingMode(m_OperatingMode);

			// Perform the calibration.
            if (!m_pTHM1176->CalibrateZeroOffset(m_CalibrationOverride))
				throw CException<CTHM1176InstrumentManager>("Cannot calibrate zero offset", __func__);

			// Flag that we're idle again.
			m_OperatingMode = kTHM1176Idle;
			emit UpdateOperatingMode(m_OperatingMode);
			break;

		case kTHM1176RestoreZeroOffset:
			// Abort any ongoing measurements.
			if (!m_pTHM1176->Abort())
				throw CException<CTHM1176InstrumentManager>("Cannot abort measurement", __func__);

			// Flag that we're restoring the zero offset.
			m_OperatingMode = kTHM1176RestoreZeroOffset;
			emit UpdateOperatingMode(m_OperatingMode);

			// Perform the restoration.
			if (!m_pTHM1176->RestoreZeroOffset())
				throw CException<CTHM1176InstrumentManager>("Cannot restore zero offset", __func__);

			// Flag that we're idle again.
			m_OperatingMode = kTHM1176Idle;
			emit UpdateOperatingMode(m_OperatingMode);
			break;
		} // switch
	} // try

	// If anything goes wrong, we abort the measurement, emit an error and return to idle.
	catch (CException<CTHM1176InstrumentManager> & rE)
	{
        if (kTHM1176MeasureContinuously == m_OperatingMode && 0 != m_TimerID)
        {
            killTimer(m_TimerID);
            m_TimerID = 0;
        }
        HandleError(rE.message(), rE.context());//Must be done before abort. Otherwise it clears the ErrorList in THM1176.cpp.
        m_pTHM1176->Abort();
		m_OperatingMode = kTHM1176Idle;
		emit UpdateOperatingMode(m_OperatingMode);
	}

} // CTHM1176InstrumentController::SetOperatingMode

void	CTHM1176InstrumentController::SendTrigger			(void)
{
	try
	{
		// Make sure we're good to go.
		if (m_pTHM1176 == nullptr ||
				(kTHM1176Measure != m_OperatingMode && kTHM1176MeasureContinuously != m_OperatingMode) ||
				kInputTrigSrcBus != m_TriggerParms.Source ||
				m_TriggerCount >= m_TriggerParms.Count)
			throw CException<CTHM1176InstrumentManager>("Cannot send trigger in current mode", __func__);

		// Send the trigger.
		if (!m_pTHM1176->SendBusTrigger())
			throw CException<CTHM1176InstrumentManager>("Cannot send trigger", 	__func__);

		// If the trigger count has reached the limit, read the result.
		if (++m_TriggerCount >= m_TriggerParms.Count)
		{
			// Transmit the measurements.
			CMeasurement			l_Measurement;
            eUnits                  l_Units;
            sMeasurementConditions	l_MeasurementConditions;
			if (!m_pTHM1176->MeasurementsGet(m_OutputSelect,
											 l_Measurement.Bx, l_Measurement.By, l_Measurement.Bz,
                                             l_Units, l_Measurement.Temp, l_Measurement.TimestampList,
											 &l_MeasurementConditions))
				throw CException<CTHM1176InstrumentManager>("Cannot fetch measurement", __func__);

            TranslateUnits(l_Measurement, l_Units);

			l_Measurement.AveragingParms	= l_MeasurementConditions.AveragingParms;
			l_Measurement.TriggerParms		= l_MeasurementConditions.TriggerParms;
			l_Measurement.OutputSelect		= m_OutputSelect;
			l_Measurement.SleepParm			= m_SleepParm;
			l_Measurement.RangeParms		= m_RangeParms;
            l_Measurement.RangeParms.Range  = l_MeasurementConditions.Range;
            l_Measurement.CommFormat		= m_CommFormat;

			l_Measurement.Warnings			= m_pTHM1176->CurrentErrorList();

			emit UpdateMeasurement(l_Measurement);

			// For Continuous Measurement, initiate next measurement.
			if (kTHM1176MeasureContinuously == m_OperatingMode)
			{
				m_TriggerCount = 0;
				if (!m_pTHM1176->Initiate(false))
					throw CException<CTHM1176InstrumentManager>("Cannot initiate measurement", __func__);
			}
			// Else go back to Idle.
			else
			{
				m_OperatingMode = kTHM1176Idle;
				emit UpdateOperatingMode(m_OperatingMode);
			}
		}
	}

	// If anything goes wrong, just flag the error.
	catch (CException<CTHM1176InstrumentManager> & rE)
	{
		HandleError(rE.message(), rE.context());
	}

} // CTHM1176InstrumentController::SendTrigger

void	CTHM1176InstrumentController::timerEvent			(QTimerEvent *					Event)
{
	MTL_Unused(Event)

	// This timer handler is only used for continuous measurements.
	try
	{
		// For immediate trigger, we have to initiate the measurement each time.
		if (kInputTrigSrcImmediate == m_TriggerParms.Source &&
				!m_pTHM1176->Initiate(false))
			throw CException<CTHM1176InstrumentManager>("Cannot initiate measurement", __func__);

		// Transmit the measurements.
		CMeasurement			l_Measurement;
        eUnits                  l_Units;
		sMeasurementConditions	l_MeasurementConditions;
		if (!m_pTHM1176->MeasurementsGet(m_OutputSelect,
										 l_Measurement.Bx, l_Measurement.By, l_Measurement.Bz,
                                         l_Units, l_Measurement.Temp, l_Measurement.TimestampList,
										 &l_MeasurementConditions))
			throw CException<CTHM1176InstrumentManager>("Cannot fetch measurement", __func__);

        TranslateUnits(l_Measurement, l_Units);

		l_Measurement.AveragingParms	= l_MeasurementConditions.AveragingParms;
		l_Measurement.TriggerParms		= l_MeasurementConditions.TriggerParms;
		l_Measurement.OutputSelect		= m_OutputSelect;
		l_Measurement.SleepParm			= m_SleepParm;
		l_Measurement.RangeParms		= m_RangeParms;
        l_Measurement.RangeParms.Range  = l_MeasurementConditions.Range;
        l_Measurement.CommFormat		= m_CommFormat;

		l_Measurement.Warnings			= m_pTHM1176->CurrentErrorList();

		emit UpdateMeasurement(l_Measurement);
	}

	// If anything goes wrong, just flag the error.
	catch (CException<CTHM1176InstrumentManager> & rE)
	{
		HandleError(rE.message(), rE.context());
	}

} // CTHM1176InstrumentController::timerEvent

void    CTHM1176InstrumentController::MakeUnitsList         (std::string                    Context)
{
    m_UnitsList.clear();
    CUnitsList l_UnitsList;
    if (!m_pTHM1176->GetAllUnits(l_UnitsList))
        throw CException<CTHM1176InstrumentManager>("Cannot get units list", Context);

    // Add all units supported by the instrument. First unit is always T.
    for (auto l_pUnits = l_UnitsList.begin(); l_pUnits < l_UnitsList.end(); l_pUnits++)
    {
        m_UnitsList.push_back(static_cast<eTHM1176Units>(*l_pUnits));

        U32 l_Divisor;
        if (!m_pTHM1176->GetDivisor(*l_pUnits, l_Divisor))
            throw CException<CTHM1176InstrumentManager>("Cannot get units list", Context);
        m_DivisorList.push_back(l_Divisor);
    }

    // Add the fake "A/m", "kA/m" and "mA/m" units if they fall in the instrument's range.
    F32 l_FloatDivisor = m_DivisorList[0] * MU0;
    if (l_FloatDivisor >= 1. && l_FloatDivisor <= 65535.)
    {
        m_UnitsList.push_back(kApm);
        m_DivisorList.push_back(static_cast<U32>(l_FloatDivisor + 0.5));
    }
    l_FloatDivisor = m_DivisorList[0] * MU0 * 1000.;
    if (l_FloatDivisor >= 1. && l_FloatDivisor <= 65535.)
    {
        m_UnitsList.push_back(kkApm);
        m_DivisorList.push_back(static_cast<U32>(l_FloatDivisor + 0.5));
    }
    l_FloatDivisor = m_DivisorList[0] * MU0 / 1000;
    if (l_FloatDivisor >= 1. && l_FloatDivisor <= 65535.)
    {
        m_UnitsList.push_back(kmApm);
        m_DivisorList.push_back(static_cast<U32>(l_FloatDivisor + 0.5));
    }

    // Add the fake "ADC" units.
    m_UnitsList.push_back(kADC);
    m_DivisorList.push_back(1);

} // CTHM1176InstrumentController::MakeUnitsList

void    CTHM1176InstrumentController::GetCurrentUnits       (std::string                    Context)
{
    eUnits l_Units;
    bool l_UseCalibration;
    if (!m_pTHM1176->ParmUnitsGet(l_Units) ||
            !m_pTHM1176->ParmUseCalibrationGet(l_UseCalibration))
        throw CException<CTHM1176InstrumentManager>("Cannot get Units", Context);
    m_Units = static_cast<eTHM1176Units>(l_Units);
    if (!l_UseCalibration)
        m_Units = kADC;

} // CTHM1176InstrumentController::GetCurrentUnits

void    CTHM1176InstrumentController::TranslateUnits        (CMeasurement &                 Measurement,
                                                             eUnits                         Units)
{
    F32 l_Multiplier;
    switch (m_Units)
    {
    case kADC:
        Measurement.Units = kADC;
        break;

    case kApm:
        l_Multiplier = 1.f / MU0;
        goto HandleApm;
    case kkApm:
        l_Multiplier = 0.001f / MU0;
        goto HandleApm;
    case kmApm:
        l_Multiplier = 1000.f / MU0;
        HandleApm:
        if (Units == THM1176Types::kT)
        {
            for (size_t i = 0; i < Measurement.Bx.size(); i++)
                Measurement.Bx[i] *= l_Multiplier;
            for (size_t i = 0; i < Measurement.By.size(); i++)
                Measurement.By[i] *= l_Multiplier;
            for (size_t i = 0; i < Measurement.Bz.size(); i++)
                Measurement.Bz[i] *= l_Multiplier;
            Measurement.Units = m_Units;
        }
        else
            Measurement.Units = static_cast<eTHM1176Units>(Units);
        break;

    default:
        Measurement.Units = static_cast<eTHM1176Units>(Units);
        break;
    }

} // CTHM1176InstrumentController::TranslateUnits

//----------------------------------------------------------------------//
//	THM1176 Instrument Controller slots: Parameters						//
//----------------------------------------------------------------------//
void	CTHM1176InstrumentController::SetAveragingParms		(sAveraging<uParm>				AveragingParms)
{
	if (m_AveragingParms != AveragingParms)
	{
		if (m_pTHM1176 == nullptr ||
				!m_pTHM1176->ParmAveragingSet(AveragingParms))
		{
			HandleError("Cannot change averaging parameters", __func__);
			if(m_pTHM1176 == nullptr ||
					!m_pTHM1176->ParmAveragingGet(m_AveragingParms))
				HandleError("Cannot retrieve averaging parameters", __func__);
		}
		else
		{
			m_AveragingParms = AveragingParms;

			// This change aborts any measurements in progress.
            if (kTHM1176MeasureContinuously == m_OperatingMode &&
                    kInputTrigSrcTimer == m_TriggerParms.Source)
            {
                if (0 == m_TimerID ||
                    !m_pTHM1176->Initiate(true))
                {
                    HandleError("Error restarting continous measurements", __func__);
                    m_OperatingMode = kTHM1176Idle;
                    emit UpdateOperatingMode(m_OperatingMode);
                }
            }
		}
	}
	emit UpdateAveragingParms(m_AveragingParms);

} // CTHM1176InstrumentController::SetAveragingParms

void	CTHM1176InstrumentController::SetTriggerParms		(sInputTrigger<uParm>			TriggerParms)
{
	if (m_TriggerParms != TriggerParms)
	{
		if (m_pTHM1176 == nullptr ||
				!m_pTHM1176->ParmTriggerInputSet(TriggerParms))
		{
			HandleError("Cannot change trigger parameters", __func__);
			if (m_pTHM1176 == nullptr ||
					!m_pTHM1176->ParmTriggerInputGet(m_TriggerParms))
				HandleError("Cannot retrieve trigger parameters", __func__);
		}
		else
		{
			m_TriggerParms = TriggerParms;

			// This change aborts any measurements in progress.
            if (kTHM1176MeasureContinuously == m_OperatingMode &&
                    kInputTrigSrcTimer == m_TriggerParms.Source)
            {
                if (0 == m_TimerID ||
                    !m_pTHM1176->Initiate(true))
                {
                    HandleError("Error restarting continous measurements", __func__);
                    m_OperatingMode = kTHM1176Idle;
                    emit UpdateOperatingMode(m_OperatingMode);
                }
            }
        }
	}
	emit UpdateTriggerParms(m_TriggerParms);

} // CTHM1176InstrumentController::SetTriggerParms

void	CTHM1176InstrumentController::SetOutputSelect		(sArbitraryMeasurements			OutputSelect)
{
	if (m_OutputSelect != OutputSelect)
	{
		if (m_pTHM1176 == nullptr)
			HandleError("Cannot change output selection", __func__);
		else
			m_OutputSelect = OutputSelect;
	}
	emit UpdateOutputSelect(m_OutputSelect);

} // CTHM1176InstrumentController::SetOutputSelect

void	CTHM1176InstrumentController::SetSleepParm			(bool							SleepParm)
{
	if (m_SleepParm != SleepParm)
	{
		if (m_pTHM1176 == nullptr ||
				!m_pTHM1176->ParmSleepSet(SleepParm))
		{
			HandleError("Cannot change sleep parameter", __func__);
			if (m_pTHM1176 == nullptr ||
					!m_pTHM1176->ParmSleepGet(m_SleepParm))
				HandleError("Cannot retrieve sleep parameter", __func__);
		}
		else
			m_SleepParm = SleepParm;
	}
	emit UpdateSleepParm(m_SleepParm);

} // CTHM1176InstrumentController::SetSleepParm

void	CTHM1176InstrumentController::SetUnits				(eTHM1176Units						Units)
{
	if (m_Units != Units)
	{
        try
        {
            if (m_pTHM1176 == nullptr)
                throw CException<CTHM1176InstrumentController>("", "");

            switch (Units)
            {
            case kApm:
            case kkApm:
            case kmApm:
                if (!m_pTHM1176->ParmUseCalibrationSet(true) ||
                    !m_pTHM1176->ParmUnitsSet(THM1176Types::kT))
                    throw CException<CTHM1176InstrumentController>("", "");
                break;
            case kADC:
                if (!m_pTHM1176->ParmUseCalibrationSet(false))
                    throw CException<CTHM1176InstrumentController>("", "");
                break;
            default:
                eUnits l_Units = static_cast<eUnits>(Units);
                if (!m_pTHM1176->ParmUseCalibrationSet(true) ||
                    !m_pTHM1176->ParmUnitsSet(l_Units))
                    throw CException<CTHM1176InstrumentController>("", "");
                break;
            }
            m_Units = Units;
        }
        catch(CException<CTHM1176InstrumentController> & rE)
		{
            MTL_Unused(rE);
			HandleError("Cannot change units", __func__);
		}
	}
    emit UpdateUnits(m_Units);

} // CTHM1176InstrumentController::SetUnits

void	CTHM1176InstrumentController::SetRangeParms			(sRange<uParm>					RangeParms)
{
	if (m_RangeParms != RangeParms)
	{
		if (m_pTHM1176 == nullptr ||
				!m_pTHM1176->ParmRangeSet(RangeParms))
		{
			HandleError("Cannot change range parameters", __func__);
			if (m_pTHM1176 == nullptr ||
					!m_pTHM1176->ParmRangeGet(m_RangeParms))
				HandleError("Cannot retrieve range parameters", __func__);
		}
		else
			m_RangeParms = RangeParms;
	}
	emit UpdateRangeParms(m_RangeParms);

} // CTHM1176InstrumentController::SetRangeParms

void	CTHM1176InstrumentController::SetCommFormat			(eCommunicationFormat			CommFormat)
{
	if (m_CommFormat != CommFormat)
	{
		if (m_pTHM1176 == nullptr ||
				!m_pTHM1176->SetFormat(CommFormat))
		{
			HandleError("Cannot change communication format", __func__);
			if (m_pTHM1176 == nullptr ||
					!m_pTHM1176->GetFormat(m_CommFormat))
				HandleError("Cannot retrieve communication format", __func__);
		}
		else
			m_CommFormat = CommFormat;
	}
	emit UpdateCommFormat(m_CommFormat);

} // CTHM1176InstrumentController::SetCommFormat

void    CTHM1176InstrumentController::SetCalibrationOverride	(bool Override)
{
    m_CalibrationOverride = Override;

} // CTHM1176InstrumentManager::SetCalibrationOverride


//////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------//
//	THM1176 Instrument Manager: Error reporting							//
//----------------------------------------------------------------------//
void CTHM1176InstrumentManager::HandleError					(QString						Description,
															 QString						Context)
{
	// Create the error structure.
	sError		l_Error;
	l_Error.Code		= CTHM1176InstrumentManager::THM1176_INST_MGR_ERROR;
	l_Error.Description	= Description.toStdString();
	l_Error.Context		= Context.toStdString();

	// Emit the update.
	CErrorList	l_ErrorList;
	l_ErrorList.push_back(l_Error);
	emit UpdateErrorList(l_ErrorList);

} // CTHM1176InstrumentController::HandleError

//----------------------------------------------------------------------//
//	THM1176 Instrument Manager: Start / Stop							//
//----------------------------------------------------------------------//
void	CTHM1176InstrumentManager::Start					(void)
{
    // Create Instrument Scanner and Controller
    m_pInstrumentScanner = new CTHM1176InstrumentScanner();
    m_pInstrumentController = new CTHM1176InstrumentController();

	// Connect the Instrument Scanner slots.
	connect(this, &CTHM1176InstrumentManager::StartInstrumentScanner,
            m_pInstrumentScanner, &CTHM1176InstrumentScanner::Start);
	connect(this, &CTHM1176InstrumentManager::StopInstrumentScanner,
            m_pInstrumentScanner, &CTHM1176InstrumentScanner::Stop);

	// Connect the Instrument Scanner signals.
    connect(m_pInstrumentScanner, &CTHM1176InstrumentScanner::UpdateInstrumentList,
			this, &CTHM1176InstrumentManager::UpdateInstrumentList);
    connect(m_pInstrumentScanner, &CTHM1176InstrumentScanner::UpdateErrorList,
			this, &CTHM1176InstrumentManager::UpdateErrorList);

	// Connect the Instrument Controller slots.
	// - Startup and shutdown.
	connect(this, &CTHM1176InstrumentManager::StartInstrumentController,
            m_pInstrumentController, &CTHM1176InstrumentController::Start);
	connect(this, &CTHM1176InstrumentManager::StopInstrumentController,
            m_pInstrumentController, &CTHM1176InstrumentController::Stop);
	// - Connection
    connect(m_pInstrumentScanner, &CTHM1176InstrumentScanner::UpdateInstrumentList,
            m_pInstrumentController, &CTHM1176InstrumentController::UpdateInstrumentList);
	connect(this, &CTHM1176InstrumentManager::RelayCurrentInstrument,
            m_pInstrumentController, &CTHM1176InstrumentController::SetCurrentInstrument);
	// - Instrument control
	connect(this, &CTHM1176InstrumentManager::RelayOperatingMode,
            m_pInstrumentController, &CTHM1176InstrumentController::SetOperatingMode);
	connect(this, &CTHM1176InstrumentManager::SendTrigger,
            m_pInstrumentController, &CTHM1176InstrumentController::SendTrigger,
            Qt::BlockingQueuedConnection);
	// - Parameters
	connect(this, &CTHM1176InstrumentManager::RelayAveragingParms,
            m_pInstrumentController, &CTHM1176InstrumentController::SetAveragingParms);
	connect(this, &CTHM1176InstrumentManager::RelayTriggerParms,
            m_pInstrumentController, &CTHM1176InstrumentController::SetTriggerParms);
	connect(this, &CTHM1176InstrumentManager::RelayOutputSelect,
            m_pInstrumentController, &CTHM1176InstrumentController::SetOutputSelect);
	connect(this, &CTHM1176InstrumentManager::RelaySleepParm,
            m_pInstrumentController, &CTHM1176InstrumentController::SetSleepParm);
	connect(this, &CTHM1176InstrumentManager::RelayUnits,
            m_pInstrumentController, &CTHM1176InstrumentController::SetUnits);
	connect(this, &CTHM1176InstrumentManager::RelayRangeParms,
            m_pInstrumentController, &CTHM1176InstrumentController::SetRangeParms);
    connect(this, &CTHM1176InstrumentManager::RelayCommFormat,
            m_pInstrumentController, &CTHM1176InstrumentController::SetCommFormat);
    connect(this, &CTHM1176InstrumentManager::SetCalibrationOverride,
            m_pInstrumentController, &CTHM1176InstrumentController::SetCalibrationOverride);

	// Connect the Instrument Controller signals.
	// - Basic instrument control:
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateCurrentInstrument,
			this, &CTHM1176InstrumentManager::UpdateCurrentInstrument);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateInstrumentPointer,
			this, &CTHM1176InstrumentManager::UpdateInstrumentPointer);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateOperatingMode,
			this, &CTHM1176InstrumentManager::UpdateOperatingMode);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateMeasurement,
			this, &CTHM1176InstrumentManager::UpdateMeasurement);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateErrorList,
			this, &CTHM1176InstrumentManager::UpdateErrorList);
	// - Instrument information and parameter bounds:
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateIdentification,
			this, &CTHM1176InstrumentManager::UpdateIdentification);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateRangeList,
			this, &CTHM1176InstrumentManager::UpdateRangeList);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateUnitsList,
			this, &CTHM1176InstrumentManager::UpdateUnitsList);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateDivisorList,
			this, &CTHM1176InstrumentManager::UpdateDivisorList);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateAveragingParmBounds,
			this, &CTHM1176InstrumentManager::UpdateAveragingParmBounds);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateTriggerParmBounds,
			this, &CTHM1176InstrumentManager::UpdateTriggerParmBounds);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateRangeParmBounds,
			this, &CTHM1176InstrumentManager::UpdateRangeParmBounds);
	// - Parameters
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateAveragingParms,
			this, &CTHM1176InstrumentManager::UpdateAveragingParms);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateTriggerParms,
			this, &CTHM1176InstrumentManager::UpdateTriggerParms);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateOutputSelect,
			this, &CTHM1176InstrumentManager::UpdateOutputSelect);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateSleepParm,
			this, &CTHM1176InstrumentManager::UpdateSleepParm);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateUnits,
			this, &CTHM1176InstrumentManager::UpdateUnits);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateRangeParms,
			this, &CTHM1176InstrumentManager::UpdateRangeParms);
    connect(m_pInstrumentController, &CTHM1176InstrumentController::UpdateCommFormat,
			this, &CTHM1176InstrumentManager::UpdateCommFormat);

	// Create and initialize the resource manager.
    m_pResourceManager = new THM1176_RSRC_MGR_CLS;
	if (nullptr == m_pResourceManager || !m_pResourceManager->Initialize())
		HandleError("Cannot initialize Resource Manager", __func__);

	// Move the Instrument Scanner to its own thread.
    m_pInstrumentScanner->moveToThread(&m_InstrumentScanThread);
	m_InstrumentScanThread.start();

	// Initialize the Instrument Scanner.
	emit StartInstrumentScanner(m_pResourceManager);

	// Move the Instrument Controller to its own thread.
    m_pInstrumentController->moveToThread(&m_InstrumentControlThread);
	m_InstrumentControlThread.start();
	
	// Set up the Instrument Controller to clean up after itself.
    connect(&m_InstrumentControlThread, &QThread::finished,
            m_pInstrumentController, &CTHM1176InstrumentController::deleteLater);
    connect(&m_InstrumentControlThread, &QThread::finished,
            m_pInstrumentScanner, &CTHM1176InstrumentScanner::Stop);
    connect(&m_InstrumentScanThread, &QThread::finished,
            m_pInstrumentScanner, &CTHM1176InstrumentScanner::deleteLater);

	// Initialize the Instrument Controller.
	emit StartInstrumentController(m_pResourceManager);

} // CTHM1176InstrumentManager::Start

void	CTHM1176InstrumentManager::Stop						(void)
{
	// Shut down the Instrument Controller.
    // Its thread's finished() signal will in turn shut down the Instrument Scanner.
	emit StopInstrumentController();
	
    // Wait for the Instrument Controller thread to shut down.
    if (!m_InstrumentControlThread.wait())
        HandleError("Cannot quit Instrument Controller thread", __func__);

    // Wait for the Instrument Scanner thread to shut down.
    m_InstrumentScanThread.quit();
    if (!m_InstrumentScanThread.wait())
        HandleError("Cannot quit Instrument Scanner thread", __func__);

    // Kill the Resource Manager.
    delete m_pResourceManager;

    // Reset the object pointers to NULL, just to be sure.
    m_pResourceManager      = nullptr;
    m_pTHM1176              = nullptr;
    m_pInstrumentScanner    = nullptr;
    m_pInstrumentController = nullptr;

} // CTHM1176InstrumentManager::Stop

//----------------------------------------------------------------------//
//	THM1176 Instrument Manager communication with Instrument Scanner	//
//----------------------------------------------------------------------//
void	CTHM1176InstrumentManager::UpdateInstrumentList					(CResourceList					InstrumentList)
{
    if (m_InstrumentList != InstrumentList)
    {
        m_InstrumentList = InstrumentList;
        emit NotifyInstrumentList(m_InstrumentList);
    }

} //CTHM1176InstrumentManager::UpdateInstrumentList

//----------------------------------------------------------------------//
//	THM1176 Instrument Manager communication with Instrument Controller	//
//----------------------------------------------------------------------//
//	Basic instrument control:
void	CTHM1176InstrumentManager::UpdateCurrentInstrument				(tResourceName					CurrentInstrument)
{
	// Only emit notification if the current instrument changes.
	if (m_CurrentInstrument != CurrentInstrument)
	{
		m_CurrentInstrument = CurrentInstrument;
		emit NotifyCurrentInstrument(m_CurrentInstrument);
	}

} // CTHM1176InstrumentManager::UpdateCurrentInstrument

void	CTHM1176InstrumentManager::UpdateInstrumentPointer				(CTHM1176Instrument<THM1176_INSTR_CLS, THM1176_RSRC_MGR_CLS> * pTHM1176)
{
	// The instrument pointer is for internal consumption only: just store it.
	m_pTHM1176 = pTHM1176;

} // CTHM1176InstrumentManager::UpdateInstrumentPointer

void	CTHM1176InstrumentManager::UpdateOperatingMode					(eTHM1176OperatingMode			OperatingMode)
{
    // Only emit notification if the parameter changes.
	if (m_OperatingMode != OperatingMode)
	{
		m_OperatingMode = OperatingMode;
		emit NotifyOperatingMode(m_OperatingMode);
	}

} // CTHM1176InstrumentManager::UpdateOperatingMode

void	CTHM1176InstrumentManager::UpdateMeasurement					(CMeasurement					Measurement)
{
	// Always emit notification of measurements.
	m_Measurement = Measurement;
	emit NotifyMeasurement(m_Measurement);

} // CTHM1176InstrumentManager::UpdateMeasurement

void	CTHM1176InstrumentManager::UpdateErrorList						(CErrorList						ErrorList)
{
	// Always emit notification of errors.
	m_ErrorList = ErrorList;
	emit NotifyErrorList(m_ErrorList);

} // CTHM1176InstrumentManager::UpdateErrorList

//	Instrument information and parameter bounds:
void	CTHM1176InstrumentManager::UpdateIdentification					(sIdentifier					Identification)
{
	// Always emit – only done when connecting an instrument.
	m_Identification = Identification;
	emit NotifyIdentification(m_Identification);

} // CTHM1176InstrumentManager::UpdateIdentification

void	CTHM1176InstrumentManager::UpdateRangeList						(CFluxList						RangeList)
{
	// Always emit – only done when connecting an instrument.
	m_RangeList = RangeList;
	emit NotifyRangeList(m_RangeList);

} // CTHM1176InstrumentManager::UpdateRangeList

void	CTHM1176InstrumentManager::UpdateUnitsList						(CTHM1176UnitsList				UnitsList)
{
	// Always emit – only done when connecting an instrument.
	m_UnitsList = UnitsList;
	emit NotifyUnitsList(m_UnitsList);

} // CTHM1176InstrumentManager::UpdateUnitsList

void	CTHM1176InstrumentManager::UpdateDivisorList					(CDivisorList					DivisorList)
{
	// Always emit – only done when connecting an instrument.
	m_DivisorList = DivisorList;
	emit NotifyDivisorList(m_DivisorList);

} // CTHM1176InstrumentManager::UpdateDivisorList

void	CTHM1176InstrumentManager::UpdateAveragingParmBounds			(sAveraging<sBoundedParm>		AveragingParmBounds)
{
	// Always emit – only done when connecting an instrument.
	m_AveragingParmBounds = AveragingParmBounds;
	emit NotifyAveragingParmBounds(m_AveragingParmBounds);

	// Also recover and emit the current parameter value.
	m_AveragingParms.NoPoints = m_AveragingParmBounds.NoPoints.Val;
	emit NotifyAveragingParms(m_AveragingParms);

} // CTHM1176InstrumentManager::UpdateAveragingParmBounds

void	CTHM1176InstrumentManager::UpdateTriggerParmBounds				(sInputTrigger<sBoundedParm>	TriggerParmBounds)
{
	// Always emit – only done when connecting an instrument.
	m_TriggerParmBounds = TriggerParmBounds;
	emit NotifyTriggerParmBounds(m_TriggerParmBounds);

	// Also recover and emit the current parameter value.
	m_TriggerParms.Source	= TriggerParmBounds.Source;
	m_TriggerParms.Period_s	= TriggerParmBounds.Period_s.Val;
	m_TriggerParms.Count	= TriggerParmBounds.Count.Val;
	emit NotifyTriggerParms(m_TriggerParms);

} // CTHM1176InstrumentManager::UpdateTriggerParmBounds

void	CTHM1176InstrumentManager::UpdateRangeParmBounds				(sRange<sBoundedParm>			RangeParmBounds)
{
	// Always emit – only done when connecting an instrument.
	m_RangeParmBounds = RangeParmBounds;
	emit NotifyRangeParmBounds(m_RangeParmBounds);

	// Also recover and emit the current parameter value.
	m_RangeParms.Auto	= m_RangeParmBounds.Auto;
	m_RangeParms.Range	= m_RangeParmBounds.Range.Val;
	emit NotifyRangeParms(m_RangeParms);

} // CTHM1176InstrumentManager::UpdateRangeParmBounds

// Parameters
void	CTHM1176InstrumentManager::UpdateAveragingParms					(sAveraging<uParm>				AveragingParms)
{
	// Only emit notification if the parameter changes.
	// Note: notification of averaging parameters at connection is handled by UpdateAveragingParmBounds.
	if (m_AveragingParms != AveragingParms)
	{
		m_AveragingParms = AveragingParms;
		emit NotifyAveragingParms(m_AveragingParms);
	}

} // CTHM1176InstrumentManager::UpdateAveragingParms

void	CTHM1176InstrumentManager::UpdateTriggerParms					(sInputTrigger<uParm>			TriggerParms)
{
	// Only emit notification if the parameter changes.
	// Note: notification of trigger parameters at connection is handled by UpdateTriggerParmBounds.
	if (m_TriggerParms != TriggerParms)
	{
		m_TriggerParms = TriggerParms;
		emit NotifyTriggerParms(m_TriggerParms);
	}

} // CTHM1176InstrumentManager::UpdateTriggerParms

void	CTHM1176InstrumentManager::UpdateOutputSelect					(sArbitraryMeasurements			OutputSelect)
{
	// Only emit notification if the parameter changes, or if instrument is not connected (i.e. connecting).
	if (m_OutputSelect != OutputSelect || kTHM1176NotConnected == m_OperatingMode)
	{
		m_OutputSelect = OutputSelect;
		emit NotifyOutputSelect(m_OutputSelect);
	}

} // CTHM1176InstrumentManager::UpdateOutputSelect

void	CTHM1176InstrumentManager::UpdateSleepParm						(bool							SleepParm)
{
	// Only emit notification if the parameter changes, or if instrument is not connected (i.e. connecting).
	if (m_SleepParm != SleepParm || kTHM1176NotConnected == m_OperatingMode)
	{
		m_SleepParm = SleepParm;
		emit NotifySleepParm(m_SleepParm);
	}

} // CTHM1176InstrumentManager::UpdateSleepParm

void	CTHM1176InstrumentManager::UpdateUnits							(eTHM1176Units                  Units)
{
	// Only emit notification if the parameter changes, or if instrument is not connected (i.e. connecting).
	if (m_Units != Units || kTHM1176NotConnected == m_OperatingMode)
	{
		m_Units = Units;
		emit NotifyUnits(m_Units);
	}

} // CTHM1176InstrumentManager::UpdateUnits

void	CTHM1176InstrumentManager::UpdateRangeParms						(sRange<uParm>					RangeParms)
{
	// Only emit notification if the parameter changes.
	// Note: notification of range parameters at connection is handled by UpdateRangeParmBounds.
	if (m_RangeParms != RangeParms)
	{
		m_RangeParms = RangeParms;
		emit NotifyRangeParms(m_RangeParms);
	}

} // CTHM1176InstrumentManager::UpdateRangeParms

void	CTHM1176InstrumentManager::UpdateCommFormat						(eCommunicationFormat			CommFormat)
{
	// Only emit notification if the parameter changes, or if instrument is not connected (i.e. connecting).
	if (m_CommFormat != CommFormat || kTHM1176NotConnected == m_OperatingMode)
	{
		m_CommFormat = CommFormat;
		emit NotifyCommFormat(m_CommFormat);
	}

} // CTHM1176InstrumentManager::UpdateCommFormat


//----------------------------------------------------------------------//
//	THM1176 Instrument Manager property Get methods						//
//----------------------------------------------------------------------//
//	Basic instrument control:
CResourceList						CTHM1176InstrumentManager::GetInstrumentList		(void)
{
	return m_InstrumentList;

} // CTHM1176InstrumentManager::GetInstrumentList

tResourceName						CTHM1176InstrumentManager::GetCurrentInstrument		(void)
{
	return m_CurrentInstrument;

} // CTHM1176InstrumentManager::GetCurrentInstrument

eTHM1176OperatingMode				CTHM1176InstrumentManager::GetOperatingMode			(void)
{
	return m_OperatingMode;

} // CTHM1176InstrumentManager::GetOperatingMode

bool						CTHM1176InstrumentManager::GetIsTHMNullptr			(void)
{
    if (m_pTHM1176 == nullptr)
        return true;
    return false;

} // CTHM1176InstrumentManager::GetIsTHMNullptr

CMeasurement						CTHM1176InstrumentManager::GetMeasurement			(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_Measurement;

} // CTHM1176InstrumentManager::GetOperatingMode

CErrorList							CTHM1176InstrumentManager::GetErrorList				(void)
{
	return m_ErrorList;

} // CTHM1176InstrumentManager::GetErrorList


//	Instrument information and parameter bounds:
sIdentifier							CTHM1176InstrumentManager::GetIdentification		(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_Identification;

} // CTHM1176InstrumentManager::GetIdentification

CFluxList							CTHM1176InstrumentManager::GetRangeList				(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_RangeList;

} // CTHM1176InstrumentManager::GetRangeList

CTHM1176UnitsList					CTHM1176InstrumentManager::GetUnitsList				(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_UnitsList;

} // CTHM1176InstrumentManager::GetUnitsList

CDivisorList						CTHM1176InstrumentManager::GetDivisorList			(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_DivisorList;

} // CTHM1176InstrumentManager::GetDivisorList

sAveraging<sBoundedParm>			CTHM1176InstrumentManager::GetAveragingParmBounds	(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_AveragingParmBounds;

} // CTHM1176InstrumentManager::GetAveragingParmBounds

sInputTrigger<sBoundedParm>			CTHM1176InstrumentManager::GetTriggerParmBounds		(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_TriggerParmBounds;

} // CTHM1176InstrumentManager::GetTriggerParmBounds

sRange<sBoundedParm>				CTHM1176InstrumentManager::GetRangeParmBounds		(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_RangeParmBounds;

} // CTHM1176InstrumentManager::GetRangeParmBounds


// Parameters:
sAveraging<uParm>					CTHM1176InstrumentManager::GetAveragingParms		(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_AveragingParms;

} // CTHM1176InstrumentManager::GetAveragingParms

sInputTrigger<uParm>				CTHM1176InstrumentManager::GetTriggerParms			(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_TriggerParms;

} // CTHM1176InstrumentManager::GetTriggerParms

sArbitraryMeasurements				CTHM1176InstrumentManager::GetOutputSelect			(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_OutputSelect;

} // CTHM1176InstrumentManager::GetOutputSelect

bool								CTHM1176InstrumentManager::GetSleepParm				(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_SleepParm;

} // CTHM1176InstrumentManager::GetSleepParm

eTHM1176Units						CTHM1176InstrumentManager::GetUnits					(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_Units;

} // CTHM1176InstrumentManager::GetUnits

sRange<uParm>						CTHM1176InstrumentManager::GetRangeParms			(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_RangeParms;

} // CTHM1176InstrumentManager::GetRangeParms

eCommunicationFormat				CTHM1176InstrumentManager::GetCommFormat			(void)
{
	if (m_pTHM1176 == nullptr)
		HandleError("Instrument not connected", __func__);
	return m_CommFormat;

} // CTHM1176InstrumentManager::GetCommFormat

bool CTHM1176InstrumentManager::GetImmediateMeasurementPeriod(const sAveraging<uParm> & rAvg, F64 & rPeriod)
{
    if (m_pTHM1176 == nullptr)
    {
        HandleError("Instrument not connected", __func__);
        return false;
    }
    else
    {
        return m_pTHM1176->GetImmediateMeasurementPeriod(rAvg, GetIdentification().ModelRevision, rPeriod);
    }
}

bool CTHM1176InstrumentManager::ReadInformationDates(QDateTime& rManufacturingDate, QDateTime& rCalibrationDate)
{
    if (m_pTHM1176 == nullptr)
    {
        HandleError("Instrument not connected", __func__);
        return false;
    }
    else
    {
        std::string l_SManufacturingDate;
        std::time_t l_ManufacturingDate;
        std::string l_SCalibrationDate;
        std::time_t l_CalibrationDate;
        bool l_Success = m_pTHM1176->ReadInformationDates(l_SManufacturingDate, l_ManufacturingDate, l_SCalibrationDate, l_CalibrationDate);

        if(l_Success)
        {
            rManufacturingDate = QDateTime::fromSecsSinceEpoch(l_ManufacturingDate);
            rCalibrationDate = QDateTime::fromSecsSinceEpoch(l_CalibrationDate);
        }

        return l_Success;
    }
} // CTHM1176InstrumentManager::ReadInformationDates


//----------------------------------------------------------------------//
//	THM1176 Instrument Manager property Set methods/slots				//
//----------------------------------------------------------------------//
//	- Basic instrument control:
void	CTHM1176InstrumentManager::SetCurrentInstrument					(tResourceName					CurrentInstrument)
{
	// Return immediately if the value has not changed.
	if (CurrentInstrument == m_CurrentInstrument)
		return;
	// Ensure that the resource name is in the current resource list.
	auto l_pResource = m_InstrumentList.begin();
	for (; l_pResource < m_InstrumentList.end(); l_pResource++)
		if (*l_pResource == CurrentInstrument) break;
	if (l_pResource >= m_InstrumentList.end())
	{
		HandleError("Resource name not in resource list", __func__);
		return;
	}
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if (m_pTHM1176 != nullptr &&
			(m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayCurrentInstrument(CurrentInstrument);
} // CTHM1176InstrumentManager::SetCurrentInstrument

void	CTHM1176InstrumentManager::SetOperatingMode						(eTHM1176OperatingMode			OperatingMode)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (OperatingMode == m_OperatingMode) return;
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayOperatingMode(OperatingMode);
} // CTHM1176InstrumentManager::SetOperatingMode

//	- Parameters:
void	CTHM1176InstrumentManager::SetAveragingParms					(sAveraging<uParm>				AveragingParms)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (AveragingParms == m_AveragingParms) return;
    // Ensure the averaging count is in range.
	if (AveragingParms.NoPoints < m_AveragingParmBounds.NoPoints.Min ||
			AveragingParms.NoPoints > m_AveragingParmBounds.NoPoints.Max)
	{
		HandleError("Averaging parameter out of range", __func__);
		return;
	}
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayAveragingParms(AveragingParms);
} // CTHM1176InstrumentManager::SetAveragingParms

void	CTHM1176InstrumentManager::SetTriggerParms						(sInputTrigger<uParm>			TriggerParms)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (TriggerParms == m_TriggerParms) return;
	// Ensure that the trigger parameters are in range.
	if (TriggerParms.Count < m_TriggerParmBounds.Count.Min ||
			TriggerParms.Count > m_TriggerParmBounds.Count.Max ||
			(TriggerParms.Source == kInputTrigSrcTimer &&
			 (TriggerParms.Period_s < m_TriggerParmBounds.Period_s.Min ||
			  TriggerParms.Period_s > m_TriggerParmBounds.Period_s.Max)))
	{
		HandleError("Trigger parameter out of bounds", __func__);
		return;
	}
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayTriggerParms(TriggerParms);
} // CTHM1176InstrumentManager::SetTriggerParms

void	CTHM1176InstrumentManager::SetOutputSelect						(sArbitraryMeasurements			OutputSelect)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (OutputSelect == m_OutputSelect) return;
	// Ensure that the output selection count is in range.
	if (OutputSelect.NoMeasurements <= 0 ||
			OutputSelect.NoMeasurements > m_TriggerParms.Count)
	{
		HandleError("Invalid measurement count", __func__);
		return;
	}
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayOutputSelect(OutputSelect);
} // CTHM1176InstrumentManager::SetOutputSelect

void	CTHM1176InstrumentManager::SetSleepParm							(bool							SleepParm)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (SleepParm == m_SleepParm) return;
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelaySleepParm(SleepParm);
} // CTHM1176InstrumentManager::SetSleepParm

void	CTHM1176InstrumentManager::SetUnits								(eTHM1176Units                  Units)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (Units == m_Units) return;
	// Ensure that the selected units are supported by this instrument.
	auto l_pUnits = m_UnitsList.begin();
	for (; l_pUnits < m_UnitsList.end(); l_pUnits++)
		if (*l_pUnits == Units) break;
	if (l_pUnits >= m_UnitsList.end())
	{
		HandleError("Invalid Units", __func__);
		return;
	}
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayUnits(Units);
} // CTHM1176InstrumentManager::SetUnits

void	CTHM1176InstrumentManager::SetRangeParms						(sRange<uParm>					RangeParms)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (RangeParms == m_RangeParms) return;
	// Ensure that any manual range selections are in range.
	tFlux l_Tolerance = 0.001f * RangeParms.Range;
	if (!RangeParms.Auto)
	{
		auto l_pRange = m_RangeList.begin();
		for (; l_pRange < m_RangeList.end(); l_pRange++)
			if (abs(*l_pRange - RangeParms.Range) < l_Tolerance) break;
		if (l_pRange >= m_RangeList.end())
		{
			HandleError("Invalid range selection", __func__);
			return;
		}
	}
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayRangeParms(RangeParms);
} // CTHM1176InstrumentManager::SetRangeParms

void	CTHM1176InstrumentManager::SetCommFormat						(eCommunicationFormat			CommFormat)
{
	// Ensure that the instrument is connected.
	if (m_pTHM1176 == nullptr)
	{
		HandleError("Instrument not connected", __func__);
		return;
	}
	// Return immediately if the value has not changed.
	if (CommFormat == m_CommFormat) return;
	// If appropriate, abort any outstanding Reads blocking the Instrument Controller.
	if ((m_OperatingMode == kTHM1176Measure || m_OperatingMode == kTHM1176MeasureContinuously) &&
			m_Identification.FirmwareVersion.Major >= THM1176_MIN_VERSION_WITH_ABORTREAD &&
			!m_pTHM1176->AbortRead())
		HandleError("Cannot abort read", __func__);
	// Send it on to the Instrument Controller.
	emit RelayCommFormat(CommFormat);
} // CTHM1176InstrumentManager::SetCommFormat
