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

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief THM1176 API

// Standard includes
#include <regex>
#include <thread>
#include <chrono>
#include <set>
#include <iostream>

// Personal includes
#include "THM1176.h"
#include "THM1176TypeConversions.h"
#include "OSDefines.h"
#include "Helpers.h"
#include "Exception.h"
#include "SCPIParsing.h"

//----------------------------------------------------------------------//
//								Definitions								//
//----------------------------------------------------------------------//
#define DEBUG_MTL_INSTRUMENT_THM1176					1
#define DEBUG_MTL_INSTRUMENT_THM1176_ERRORS_ONLY		0
#if (defined(_DEBUG) && defined(DEBUG_MTL_INSTRUMENT_THM1176) && DEBUG_MTL_INSTRUMENT_THM1176)
	#if (defined(DEBUG_MTL_INSTRUMENT_THM1176_ERRORS_ONLY) && DEBUG_MTL_INSTRUMENT_THM1176_ERRORS_ONLY)
		#define MTL_INSTRUMENT_THM1176_DEBUG_COUT(__X__)
	#else
        #define MTL_INSTRUMENT_THM1176_DEBUG_COUT(__X__)	std::cout << __X__
	#endif
#define MTL_INSTRUMENT_THM1176_DEBUG_CERR(__X__)			std::cerr << __X__
#else
	#define MTL_INSTRUMENT_THM1176_DEBUG_COUT(__X__)
	#define MTL_INSTRUMENT_THM1176_DEBUG_CERR(__X__)
#endif

static const I32 THM1176_FATAL_ERROR_CODE_LIMIT			(200);
static const I32 THM1176_ERROR_CODE_MEAS_OVERRANGE		(205);
static const U32 THM1176_FILE_ACCESS_TIMEOUT			(20000);					// ms
static const U32 THM1176_CALIBRATION_TIMEOUT			(30000);					// ms
static const std::string THM1176_CALIBRATION_FILE_NAME	("cal.dat");
static const std::string THM1176_INFO_FILE_NAME         ("info.dat");
static const U32 THM1176_CAL_FILE_OFFSET_VERSION		(32);						// bytes
static const U32 THM1176_CAL_FILE_OFFSET_MATRIX_V2		(116);						// bytes
static const F64 THM1176_IMMEDIATE_TIME_PER_ACQ_A		(1.0281823091218700E-04);	// s
static const F64 THM1176_IMMEDIATE_TIME_PER_ACQ_B		(0.0000116103073008506);	// s
static const F64 THM1176_IMMEDIATE_TIME_PER_MEAS_A		(4.4532792007542600E-05);	// s
static const F64 THM1176_IMMEDIATE_TIME_PER_MEAS_B		(0.0000285930870825438);	// s

static const char * STATUS_SET_CMDS[4][3] =
{
	{ "",					"",					"*SRE"				},	// kStatusByte
	{ "",					"",					"*ESE"				},	// kStandardEventStatusRegister
	{ "",					"",					":STAT:QUES:ENAB"	},	// kStatusQuestionableStatusRegister
	{ "",					"",					":STAT:OPER:ENAB"	}	// kStatusOperationStatusRegister
};

static const char * STATUS_GET_CMDS[4][3] =
{
	{ "*STB?",				"",					"*SRE?"				},	// kStatusByte
	{ "*ESR?",				"",					"*ESE?"				},	// kStandardEventStatusRegister
	{ ":STAT:QUES:EVEN?",	":STAT:QUES:COND?",	":STAT:QUES:ENAB?"	},	// kStatusQuestionableStatusRegister
	{ ":STAT:OPER:EVEN?",	":STAT:OPER:COND?",	":STAT:OPER:ENAB?"	}	// kStatusOperationStatusRegister
};

static const std::set<std::string> MODELS_NOT_TO_CALIBRATE =
{
    std::string("TFM1186")
};

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

//----------------------------------------------------------------------//
//								Utilities:								//
//	Replacements for std::to_string(float) and ...(double).				//
//	These maintain full precision and always use period as seperator.	//
//----------------------------------------------------------------------//
#include <sstream>
#include <iomanip>
#include <locale>

static std::string l_ToString(F32 number, int precision = 7, const char * locale = "C")
{
	std::locale			l_locale = std::locale(locale);
	std::ostringstream	l_oss;

	l_oss.imbue(l_locale);
	l_oss << std::scientific << std::setprecision(precision);
	l_oss << number;

	return l_oss.str();
}

static std::string l_ToString(F64 number, int precision = 15, const char * locale = "C")
{
	std::locale			l_locale = std::locale(locale);
	std::ostringstream	l_oss;

	l_oss.imbue(l_locale);
	l_oss << std::scientific << std::setprecision(precision);
	l_oss << number;

	return l_oss.str();
}

//----------------------------------------------------------------------//
//								Utilities								//
//----------------------------------------------------------------------//
static void l_ParseErrorString(std::string & rErrStr, const std::string & rContext, sError & rError)
{
	// Get code
	size_t l_Comma	= rErrStr.find_first_of(',');
	rError.Code = std::stoi(rErrStr.substr(0, l_Comma));
	// Get description
	size_t l_OpenQuote	= rErrStr.find_first_of('"', l_Comma + 1);
	size_t l_CloseQuote	= rErrStr.find_last_of('"');
	rError.Description	= rErrStr.substr(l_OpenQuote + 1, l_CloseQuote - (l_OpenQuote + 1));
	rError.Context		= rContext;
}

static std::string l_ParseRegexError (std::regex_error & rE)
{
	std::vector<std::string> lErrorList;
	if (rE.code() & std::regex_constants::error_badrepeat)
		lErrorList.push_back ("error_badrepeat");
	if (rE.code() & std::regex_constants::error_ctype)
		lErrorList.push_back ("error_ctype");
	if (rE.code() & std::regex_constants::error_escape)
		lErrorList.push_back ("error_escape");
	if (rE.code() & std::regex_constants::error_backref)
		lErrorList.push_back ("error_backref");
	if (rE.code() & std::regex_constants::error_brack)
		lErrorList.push_back ("error_brack");
	if (rE.code() & std::regex_constants::error_paren)
		lErrorList.push_back ("error_paren");
	if (rE.code() & std::regex_constants::error_brace)
		lErrorList.push_back ("error_brace");
	if (rE.code() & std::regex_constants::error_badbrace)
		lErrorList.push_back ("error_badbrace");
	if (rE.code() & std::regex_constants::error_range)
		lErrorList.push_back ("error_range");
	if (rE.code() & std::regex_constants::error_space)
		lErrorList.push_back ("error_space");
	if (rE.code() & std::regex_constants::error_badrepeat)
		lErrorList.push_back ("error_badrepeat");
	if (rE.code() & std::regex_constants::error_complexity)
		lErrorList.push_back ("error_complexity");
	if (rE.code() & std::regex_constants::error_stack)
		lErrorList.push_back ("error_stack");

	std::string lErrors = lErrorList.front();
	for (auto lpError = lErrorList.begin() + 1; lpError < lErrorList.end(); lpError++)
		lErrors += " | " + *lpError;
	return lErrors;
}

//----------------------------------------------------------------------//
//								Methods									//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadIdentification(std::string & rIdentification)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead("*IDN?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		rIdentification = std::string(m_ReadBuffer.begin(), m_ReadBuffer.end());
	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rIdentification.clear();
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParseIdentification(struct sIdentifier & rIdentification)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	try
	{
		std::regex l_Regex("([^,]+),([^,]+),([^,]+),([^,]+)");
		std::smatch l_Match;
		if (!std::regex_match(m_Identification, l_Match, l_Regex))
			throw MTL::CException<CTHM1176Instrument>("Failed to match ID string", MTL__LOCATION__);

		rIdentification.Manufacturer	= l_Match[1].str();
		rIdentification.Model			= l_Match[2].str();
		rIdentification.SerialNumber	= static_cast<U32>(stoul(l_Match[3].str()));

		std::string l_Versions			= l_Match[4].str();
        l_Regex = "el(([A-Z])([0-9]+))-pr(([A-Z]?)([0-9]+))-fw(([0-9]+)\\.([0-9]+))\\n";
		if (!std::regex_match(l_Versions, l_Match, l_Regex))
			throw MTL::CException<CTHM1176Instrument>("Failed to match version string", MTL__LOCATION__);

		rIdentification.ElectronicsVersion.Name		= l_Match[1].str();
		rIdentification.ElectronicsVersion.Major	= static_cast<U8>(l_Match[2].str()[0] - '@');
		rIdentification.ElectronicsVersion.Minor	= static_cast<U8>(stoul(l_Match[3].str()));
		
		rIdentification.ProbeVersion.Name			= l_Match[4].str();
		rIdentification.ProbeVersion.Major			= static_cast<U8>(l_Match[5].str()[0] - '@');
		rIdentification.ProbeVersion.Minor			= static_cast<U8>(stoul(l_Match[6].str()));
		
		rIdentification.FirmwareVersion.Name		= l_Match[7].str();
		rIdentification.FirmwareVersion.Major		= static_cast<U8>(stoul(l_Match[8].str()));
		rIdentification.FirmwareVersion.Minor		= static_cast<U8>(stoul(l_Match[9].str()));
        rIdentification.ModelRevision               = rIdentification.SerialNumber < 3000 ? eModelRevision::kA : eModelRevision::kB;

        //Instrument model enum:
        std::map<std::string, eInstrModel> l_InstModel =
            {
             { "THM1176-LF",   eInstrModel::kTHM1176LF  },
             { "THM1176-MF",   eInstrModel::kTHM1176MF },
             { "THM1176-HF",   eInstrModel::kTHM1176HF },
             { "THM1176-HFC",  eInstrModel::kTHM1176HFC },
             { "TFM1186",      eInstrModel::kTFM1186 },
             };
        auto l_pInstrModel = l_InstModel.find(rIdentification.Model);
        if (l_pInstrModel == l_InstModel.cend())
        {
            rIdentification.InstrModel = eInstrModel::kUnknown;
            throw MTL::CException<CTHM1176Instrument>("Failed to parse instrument model." , MTL__LOCATION__);
        }
        else
        {
            rIdentification.InstrModel = l_pInstrModel->second;
        }
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	catch (std::regex_error& rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(std::string(rE.what()) + " (" + l_ParseRegexError(rE) + ") at " + MTL__LOCATION__ + '\n');
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetErrorList(CErrorList & rList, const std::string & rContext)
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rList.clear();
	try
	{
		uStatusByte l_STB;
		// Read STB
		if (!InstrType::ReadSTB(l_STB.RawSTB))
			throw MTL::CException<CTHM1176Instrument>("Failed reading STB", MTL__LOCATION__);

		// While there is an error
		CSCPIBuffer l_Error(4096);
		while (l_STB.StatusByte.EAV)
		{
			// Try to read error string
			enum eRetry { kRetryErrorRetrieving };
			try
			{
				if (!InstrType::Write(":SYST:ERR?"))
					throw kRetryErrorRetrieving;
				if (!InstrType::Read(l_Error))
					throw kRetryErrorRetrieving;
			}
			catch (eRetry & rRetry)
			{
				if (rRetry == kRetryErrorRetrieving)
				{
					// Clear
					if (!InstrType::Clear())
						throw MTL::CException<CTHM1176Instrument>("Could not clear device", MTL__LOCATION__);
					// Retry reading error
					if (!InstrType::Write(":SYST:ERR?"))
						throw MTL::CException<CTHM1176Instrument>("Failed getting error list", MTL__LOCATION__);
					if (!InstrType::Read(l_Error))
						throw MTL::CException<CTHM1176Instrument>("Failed getting error list", MTL__LOCATION__);
				}
			}
			// Parse Error
			std::string l_ErrStr = std::string(l_Error.data(), l_Error.size());
			sError l_Err;
			l_ParseErrorString(l_ErrStr, rContext, l_Err);
			rList.push_back(l_Err);
			// Read STB
			if (!InstrType::ReadSTB(l_STB.RawSTB))
				throw MTL::CException<CTHM1176Instrument>("Failed reading STB", MTL__LOCATION__);
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rList.clear();
		return false;
	}
#if (defined(_DEBUG))
	for (auto it = rList.begin(); it != rList.end(); it++)
	{
		CERR("THM1176Error " << it->Code << " : " << it->Description << " in " << it->Context << std::endl);
	}
#endif
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::WriteAndRead(const std::string & rWriteStr, CSCPIBuffer & rReadBuffer)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << rWriteStr << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

    bool l_InitialLockState = InstrType::LockedExclusive();
    try
	{
		rReadBuffer.clear();
		m_ErrorList.clear();

        // If we don't already have the exclusive lock on this session, acquire it for this write&read cycle.
        if (!l_InitialLockState && !InstrType::LockExclusive(m_Timeout))
			throw MTL::CException<CTHM1176Instrument>("Failed acquiring lock", MTL__LOCATION__);

		// Append *OPC ? if command does not include "*IDN?" (which is the only command to return an indefinite - length response, and can therefore not be followed by another query).
		//												Match a string which contains "*IDN?"
		bool l_OPCAppended = !std::regex_match(rWriteStr, std::regex("\\*IDN\\?"));
		std::string l_Query;
		if (l_OPCAppended)
			l_Query = rWriteStr + ";*OPC?";
		else
			l_Query = rWriteStr;

		// Write
		if (!InstrType::Write(l_Query))
			throw MTL::CException<CTHM1176Instrument>("Could not write", MTL__LOCATION__);

		// Check errors
		uStatusByte l_STB;
		if (!InstrType::ReadSTB(l_STB.RawSTB))
			throw MTL::CException<CTHM1176Instrument>("Failed reading STB", MTL__LOCATION__);
		if (l_STB.StatusByte.EAV)
			throw MTL::CException<CTHM1176Instrument>("Error available", MTL__LOCATION__);

		// Repeat while there is a message available
		// There is always at least the *IDN? or the *OPC? answer
		bool l_Timeout = false;
		do
		{
			// Check the abort flag.
			if (m_AbortRead)
				throw MTL::CException<CTHM1176Instrument>("Read aborted by user", MTL__LOCATION__);
			// Read and append. Tolerate time-outs.
			if (InstrType::Read(rReadBuffer, true))
			{
				l_Timeout = false;
			}
			else
			{
				l_Timeout = InstrType::Timeout();
				if (!l_Timeout)
					throw MTL::CException<CTHM1176Instrument>("Could not read", MTL__LOCATION__);
			}
			// Read STB
			if (!InstrType::ReadSTB(l_STB.RawSTB))
				throw MTL::CException<CTHM1176Instrument>("Failed reading STB", MTL__LOCATION__);
			// Check EAV (Error Available) bit.
			if (l_STB.StatusByte.EAV)
				throw MTL::CException<CTHM1176Instrument>("Error available", MTL__LOCATION__);
			
		} while (l_STB.StatusByte.MAV || l_Timeout);

		// Remove *OPC? result if given
		if (l_OPCAppended)
		{
			std::size_t l_MaxSizeToMatch = std::strlen(";1\n");	// It is unnecessary to perform a regex on more characters than this
			std::match_results<std::vector<char>::iterator> m;
			// Match a string that ends with an optional ';', a '1', and an optional '\n'
			if (std::regex_match((rReadBuffer.size() <= l_MaxSizeToMatch) ? rReadBuffer.begin() : rReadBuffer.end() - l_MaxSizeToMatch, rReadBuffer.end(), m, std::regex(";?1\\n?$")))
				rReadBuffer.resize(rReadBuffer.size() - m.length());
		}

        // If we did not have an exclusive lock on the session initially, release the lock.
        if (!l_InitialLockState && !InstrType::Unlock())
			throw MTL::CException<CTHM1176Instrument>("Could not unlock", MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);

		// If we landed here because of an AbortRead, try to abort and clear the I/O buffers.
		if (m_AbortRead)
		{
			InstrType::Write(":ABOR");
			InstrType::Clear();
		}

		// Try to read whatever data is available.
		uStatusByte l_STB;
		while (InstrType::ReadSTB(l_STB.RawSTB) && l_STB.StatusByte.MAV)
			InstrType::Read(rReadBuffer, true);

		// Try to get errors
		GetErrorList(m_ErrorList, rWriteStr);

		// Try to release the lock
        if (!l_InitialLockState) InstrType::Unlock();

		// Reset the abort flag.
		m_AbortRead = false;

		return false;
	}
	catch (std::regex_error& rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(std::string(rE.what()) + " (" + l_ParseRegexError(rE) + ") at " + MTL__LOCATION__ + '\n');
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadBootTime(CAbsoluteTimestamp & rBootTime)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
        // Get the current timestamp (ns).
        if (!WriteAndRead("*RST", m_ReadBuffer))
            throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
        if(!WriteAndRead(":INIT", m_ReadBuffer)) //Initiate fake measurement to get timestampe (don't care about measurement overrange error)
        {
            if(!m_ErrorList.empty())
            {
                auto l_pError = m_ErrorList.begin();
                for (; l_pError < m_ErrorList.end(); l_pError++)
                {
                    if (l_pError->Code != THM1176_ERROR_CODE_MEAS_OVERRANGE)
                        throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
                }
            }
        }

        if (!WriteAndRead(":FETC:TIM?", m_ReadBuffer))
            throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
		std::string l_TimestampString(m_ReadBuffer.begin(), m_ReadBuffer.end());
		U64 l_Timestamp = std::stoull(l_TimestampString, nullptr, 0);

		// Get the current clock time.
		// /!\ This is assumed to be in seconds since the Epoch.
		std::time_t l_CurrentTime = std::time(nullptr);

		// Compute the boot time.
		rBootTime = CAbsoluteTimestamp(l_CurrentTime, 0) - CAbsoluteTimestamp(0, l_Timestamp);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rBootTime.clear();
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadAllRanges(CFluxList & rRanges)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":SENS:ALL?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize(',');
		if (l_Tokens.size() < 1)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Loop to get the ranges.
		rRanges.clear();
		for (auto it = l_Tokens.begin(); it != l_Tokens.end(); it++)
		{
			std::string	l_StrRange(it->begin, it->end - 2);	// Throw away the " T" at the end.
			try
			{
				rRanges.push_back(std::stof(l_StrRange));
			}
			catch (const std::invalid_argument& ia)
			{
				throw MTL::CException<CTHM1176Instrument>(std::string("Range has incorrect format: ") + ia.what(), MTL__LOCATION__);
			}
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rRanges.clear();
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadAllUnits(std::map<eUnits,U32> & rAllUnits)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":UNIT:ALL?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize(',');
		if (l_Tokens.size() < 2 || (l_Tokens.size() % 2) == 1)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Loop to get the units and their associated divisors.
		rAllUnits.clear();
		for (auto it = l_Tokens.begin(); it != l_Tokens.end(); it++)
		{
			std::string l_StrUnit(it->begin, it->end);
			it++;
			std::string l_StrDivisor(it->begin, it->end);
			eUnits l_Units;
			if (!StringToUnits(l_StrUnit, l_Units))
				throw MTL::CException<CTHM1176Instrument>("Invalid units", MTL__LOCATION__);
			U32 l_Divisor = static_cast<U32>(std::stoul(l_StrDivisor));
			rAllUnits.insert(std::pair<eUnits, U32>(l_Units, l_Divisor));
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rAllUnits.clear();
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadUseCalibration(bool & rUseCal)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":CAL:STAT?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		std::string l_Answer(m_ReadBuffer.begin(), m_ReadBuffer.end());
		if (l_Answer == "ON")
			rUseCal = true;
		else if (l_Answer == "OFF")
			rUseCal = false;
		else
			throw MTL::CException<CTHM1176Instrument>("Invalid response format", MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadManufacturingDate(std::string & rDate)
{
	try
	{
		std::string l_CalFileContent;

		// Read the information file.
		if (!ReadFile(THM1176_INFO_FILE_NAME, l_CalFileContent))
			throw MTL::CException<CTHM1176Instrument>("Cannot read calibration file", MTL__LOCATION__);

		// Extract the Manufacturing Date.
		rDate = std::string(l_CalFileContent, 100, 10);

	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rDate = "";
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadRotationMatrix(Matrix3f & rMatrix, std::string & rCalibrationDate)
{
	try
	{
		std::string l_CalFileContent;

		// Read the calibration file.
		if (!ReadFile(THM1176_CALIBRATION_FILE_NAME, l_CalFileContent))
			throw MTL::CException<CTHM1176Instrument>("Cannot read calibration file", MTL__LOCATION__);

		// Extract the Calibration Date.
		rCalibrationDate = std::string(l_CalFileContent, 64, 10);

		// Get the appropriate offset, depending on the calibration file version.
		char * l_pMatrix = const_cast<char *>(l_CalFileContent.c_str());
		switch (l_CalFileContent[THM1176_CAL_FILE_OFFSET_VERSION])
		{
			case '2':
				l_pMatrix += THM1176_CAL_FILE_OFFSET_MATRIX_V2;
				break;
			default:
				throw MTL::CException<CTHM1176Instrument>("Unknown calibration file version", MTL__LOCATION__);
		}

		// Copy the calibration data into a 2D integer array.
		I32 l_IntMatrix[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
			{
				l_IntMatrix[i][j] = BinaryToI32(l_pMatrix);
				l_pMatrix += sizeof (I32);
			}

		// Convert to a floating-point matrix.
		const F32 l_C =  static_cast<F32>(0x7FFFFFFF);
        rMatrix <<	l_IntMatrix[0][0]/l_C, l_IntMatrix[0][1]/l_C, l_IntMatrix[0][2]/l_C,
					l_IntMatrix[1][0]/l_C, l_IntMatrix[1][1]/l_C, l_IntMatrix[1][2]/l_C,
					l_IntMatrix[2][0]/l_C, l_IntMatrix[2][1]/l_C, l_IntMatrix[2][2]/l_C;
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rCalibrationDate = "";
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ApplyRotationMatrix(tFlux & rBx, tFlux & rBy, tFlux & rBz)
{
	try
	{
		// Check whether we're using calibration.
		if (!m_UseCalibration)
			throw MTL::CException<CTHM1176Instrument>("Not using calibration", MTL__LOCATION__);

		// Apply the rotation.
		Vector3f l_B(rBx, rBy, rBz);
		l_B = m_RotationMatrix * l_B;

		rBx = l_B(0);
		rBy = l_B(1);
		rBz = l_B(2);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ApplyRotationMatrix(CFluxList & rBx, CFluxList & rBy, CFluxList & rBz)
{
	try
	{
		// Check whether we're using calibration.
		if (!m_UseCalibration)
			throw MTL::CException<CTHM1176Instrument>("Not using calibration", MTL__LOCATION__);

		// Check that the flux-list sizes are equal.
		if (rBx.size() != rBy.size() || rBx.size() != rBz.size())
			throw MTL::CException<CTHM1176Instrument>("Flux-lists not same size", MTL__LOCATION__);

		// Loop through the flux-lists and rotate each vector.
		// Note: could perhaps do this more efficiently by vector multiplication, [3x3][3xN];
		// on the other hand, this requires more packing/unpacking – so maybe not. KISS for now.
		for (auto l_pBx = rBx.begin(), l_pBy = rBy.begin(), l_pBz = rBz.begin();
			 l_pBx < rBx.end();
			 l_pBx++, l_pBy++, l_pBz++)
		{
			Vector3f l_B(*l_pBx, *l_pBy, *l_pBz);
			l_B = m_RotationMatrix * l_B;
			*l_pBx = l_B(0);
			*l_pBy = l_B(1);
			*l_pBz = l_B(2);
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParseMeasurements(CSCPIBufferParser & rBP, eUnits Units, CFluxList & rMeas, U32 NoMeasurements)
{
	rMeas.clear();
	try
	{
		// Get next token.
		std::vector<char>::const_iterator l_tokenbeg, l_tokenend;
		if (!rBP.GetNext(l_tokenbeg, l_tokenend, ';'))
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Handle arbitrary-block data (INTeger and PACKed FORMats)
		size_t l_off, l_len;
		if (IsArbitraryBlock(l_tokenbeg, rBP.end(), l_off, l_len))
		{
			// Force the the correct offset in the Buffer Parser; it may have been misled by a ";" in the binary data.
			rBP.SetNextOffset(l_tokenbeg + l_off + l_len + 1);

			// Retrieve the divisor to convert from native units to the given units.
			tFlux l_DivisorReal = 1.;
			if (m_UseCalibration)
				l_DivisorReal = static_cast<tFlux>(m_Units.at(Units));

			// Handle INTeger format
			if (l_tokenbeg[1] == '6')
			{
				for (;
					 l_len >= 4 && rMeas.size() < NoMeasurements;
					 l_len -= 4, l_off += 4)
				{
					I32	l_MeasI32 = BinaryToI32(&*l_tokenbeg + l_off);
					rMeas.push_back(static_cast<tFlux>(l_MeasI32) / l_DivisorReal);
				}
			}

			// Handle PACKed formats
			else if (l_tokenbeg[1] == '5')
			{
				// Retrieve number of bytes per sample.
				size_t l_Pack =	(l_tokenbeg[l_off] == '1') ?	1 :
								(l_tokenbeg[l_off] == '2') ?	2 : 0;
				if (l_Pack == 0)
					throw MTL::CException<CTHM1176Instrument>("Invalid PACKed reposnse", MTL__LOCATION__);
				l_off += 1; l_len--;

				// Convert initial value.
				I32	l_MeasI32 = BinaryToI32(&*l_tokenbeg + l_off);
				rMeas.push_back(static_cast<tFlux>(l_MeasI32) / l_DivisorReal);

				// Reconstitute compressed values.
				for (l_len -= 4, l_off += 4;
					 l_len >= l_Pack && rMeas.size() < NoMeasurements;
					 l_len -= l_Pack, l_off += l_Pack)
				{
					if (l_Pack == 1)
						l_MeasI32 += static_cast<I8>(*(&*l_tokenbeg + l_off));
					else // (l_Pack == 2)
						l_MeasI32 += BinaryToI16(&*l_tokenbeg + l_off);
					rMeas.push_back(static_cast<tFlux>(l_MeasI32) / l_DivisorReal);
				}
			}
		}
		// Handle text data (ASCii FORMat)
		else
		{
			// Create a new parser object that covers just this token.
			CSCPIBufferParser l_FluxParser(l_tokenbeg, l_tokenend);

			// Loop over the measurements in the token.
			std::vector<char>::const_iterator l_measbeg, l_measend;
			bool l_GetNextRet;
			for (l_GetNextRet = l_FluxParser.GetNext(l_measbeg, l_measend, ',');
				 l_GetNextRet && rMeas.size() < NoMeasurements;
				 l_GetNextRet = l_FluxParser.GetNext(l_measbeg, l_measend, ','))
			{
				std::string l_Measurement(l_measbeg, l_measend);
				rMeas.push_back(std::stof(l_Measurement));
			}
		}

		// Ensure that we got the requested number of measurements.
		if (rMeas.size() != NoMeasurements)
			throw MTL::CException<CTHM1176Instrument>("Failed to retrieve all measurements", MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rMeas.clear();
		return false;
	}
	return true;
}

//----------------------------------------------------------------------//
//						Constructors / destructors						//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
CTHM1176Instrument<InstrType, RsrcMgrType>::CTHM1176Instrument(RsrcMgrType & rResourceManager, tResourceName ResourceName)
:	InstrType(rResourceManager, ResourceName),
    m_Timeout(0), m_ReadBuffer(65535), m_Sleep(false), m_UseCalibration(true), m_AbortRead(false)
{
}

template <class InstrType, class RsrcMgrType>
CTHM1176Instrument<InstrType, RsrcMgrType>::~CTHM1176Instrument()
{
	if (InstrType::IsOpen())
	{
		Abort();
		Disconnect();
	}
}

//----------------------------------------------------------------------//
//							General Information							//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
const CErrorList & CTHM1176Instrument<InstrType, RsrcMgrType>::CurrentErrorList()
{
	return m_ErrorList;
}

template <class InstrType, class RsrcMgrType>
void CTHM1176Instrument<InstrType, RsrcMgrType>::ClearErrorList()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();
}

//----------------------------------------------------------------------//
//								Connect / Disconnect					//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::Connect(U32 InitialTimeout, bool Exclusive, std::string *pErrMsg)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " timeout=" << InitialTimeout << " exclusive=" << Exclusive << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();

	// Hack to silence a compiler warning, that FirmwareVersion.Minor < THM1176_SUPPORTED_VERSION_MIN_MINOR
	// is always false (because FirmwareVersion.Minor is unsigned and THM1176_SUPPORTED_VERSION_MIN_MINOR is zero).
	U8 l_MinMajor = THM1176_SUPPORTED_VERSION_MIN_MAJOR;
	U8 l_MinMinor = THM1176_SUPPORTED_VERSION_MIN_MINOR;

	try
	{
		m_Timeout = InitialTimeout;
		if (!InstrType::Open())
			throw MTL::CException<CTHM1176Instrument>("Could not open", MTL__FUNCTION_NAME__);
		if (Exclusive && !InstrType::LockExclusive(m_Timeout))
			throw MTL::CException<CTHM1176Instrument>("Could not obtain exclusive lock", MTL__FUNCTION_NAME__);
		if (!InstrType::Clear())
			throw MTL::CException<CTHM1176Instrument>("Could not clear", MTL__FUNCTION_NAME__);
		if (!WriteAndRead("*CLS", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>("Failed W&R of *CLS", MTL__FUNCTION_NAME__);
		if (!ReadIdentification(m_Identification))
			throw MTL::CException<CTHM1176Instrument>("Could not get identification", MTL__FUNCTION_NAME__);
		if (!ParseIdentification(m_IdentificationStruct))
			throw MTL::CException<CTHM1176Instrument>("Could parse identification", MTL__FUNCTION_NAME__);
		if ((m_IdentificationStruct.Model == "THM1176-A") && (m_IdentificationStruct.FirmwareVersion.Major < l_MinMajor || m_IdentificationStruct.FirmwareVersion.Minor < l_MinMinor))
			throw MTL::CException<CTHM1176Instrument>("Unsupported firmware version", MTL__FUNCTION_NAME__);
		if (!ReadBootTime(m_BootTime))
			throw MTL::CException<CTHM1176Instrument>("Could not get boot time", MTL__FUNCTION_NAME__);
		if (!ReadAllUnits(m_Units))
			throw MTL::CException<CTHM1176Instrument>("Could not get units", MTL__FUNCTION_NAME__);
		if (!ReadAllRanges(m_Ranges))
			throw MTL::CException<CTHM1176Instrument>("Could not get ranges", MTL__FUNCTION_NAME__);
		if (!ReadManufacturingDate(m_ManufacturingDate))
			throw MTL::CException<CTHM1176Instrument>("Could not get manufacturing date", MTL__FUNCTION_NAME__);
		if (!ReadRotationMatrix(m_RotationMatrix, m_CalibrationDate))
			throw MTL::CException<CTHM1176Instrument>("Could not get rotation matrix", MTL__FUNCTION_NAME__);
		if (!ReadUseCalibration(m_UseCalibration))
			throw MTL::CException<CTHM1176Instrument>("Could not get Use Calibration parameter", MTL__FUNCTION_NAME__);
		if (!ParmUnitsGet(m_UnitsParms))
			throw MTL::CException<CTHM1176Instrument>("Could not get units", MTL__FUNCTION_NAME__);
		if (!ParmAveragingGet(m_AveragingParms))
			throw MTL::CException<CTHM1176Instrument>("Could not get averaging parameters", MTL__FUNCTION_NAME__);
		if (!ParmTriggerInputGet(m_TriggerParms))
			throw MTL::CException<CTHM1176Instrument>("Could not get trigger parameters", MTL__FUNCTION_NAME__);
		if (!ParmRangeGet(m_RangeParms))
			throw MTL::CException<CTHM1176Instrument>("Could not get range parameters", MTL__FUNCTION_NAME__);
		if (!GetFormat(m_Format))
			throw MTL::CException<CTHM1176Instrument>("Could not get communication format", MTL__FUNCTION_NAME__);

	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		if (pErrMsg != nullptr)
			*pErrMsg = rE.what();
		if (InstrType::IsOpen())
			InstrType::Close();
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
void CTHM1176Instrument<InstrType, RsrcMgrType>::Disconnect()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	if (InstrType::IsOpen())
		InstrType::Close();
}

//----------------------------------------------------------------------//
//								Parameters								//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmAveragingSet(const sAveraging<uParm> & rAvg)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " " << rAvg << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	std::string l_Command = ":AVER:COUN " + std::to_string(rAvg.NoPoints);
	bool l_Success = WriteAndRead(l_Command, m_ReadBuffer);
	if (l_Success) m_AveragingParms = rAvg;
	return l_Success;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmAveragingGet(sAveraging<uParm> & rAvg)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":AVER:COUN?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		rAvg.NoPoints = static_cast<U16>(std::stoi(std::string(m_ReadBuffer.begin(), m_ReadBuffer.end())));
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmAveragingGet(sAveraging<sBoundedParm> & rAvg)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":AVER:COUN?;:AVER:COUN? MIN;:AVER:COUN? MAX;:AVER:COUN? DEF", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize();
		if (l_Tokens.size() != 4)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		rAvg.NoPoints.Val = static_cast<U16>(std::stoi(std::string(l_Tokens[0].begin, l_Tokens[0].end)));
		rAvg.NoPoints.Min = static_cast<U16>(std::stoi(std::string(l_Tokens[1].begin, l_Tokens[1].end)));
		rAvg.NoPoints.Max = static_cast<U16>(std::stoi(std::string(l_Tokens[2].begin, l_Tokens[2].end)));
		rAvg.NoPoints.Def = static_cast<U16>(std::stoi(std::string(l_Tokens[3].begin, l_Tokens[3].end)));
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmSleepSet(bool Sleep)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " sleep=" << Sleep << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Sleep = Sleep;
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmSleepGet(bool & rSleep)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rSleep = m_Sleep;
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmTriggerInputSet(const sInputTrigger<uParm> & rInputTrig)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " " << rInputTrig << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	std::string l_Command = ":TRIG:SOUR ";
	switch (rInputTrig.Source)
	{
		case kInputTrigSrcImmediate:
			l_Command += "IMM";
			l_Command += ";COUN " + std::to_string(rInputTrig.Count);
			break;
		case kInputTrigSrcTimer:
			l_Command += "TIM";
			l_Command += ";COUN " + std::to_string(rInputTrig.Count);
			l_Command += ";TIM " + l_ToString(rInputTrig.Period_s);
			break;
		case kInputTrigSrcBus:
			l_Command += "BUS";
			l_Command += ";COUN " + std::to_string(rInputTrig.Count);
			break;
	}

    if (WriteAndRead(l_Command, m_ReadBuffer))
    {
		m_TriggerParms = rInputTrig;
        return true;
    }
	else
	{
		CErrorList l_ErrorList = m_ErrorList;
		ParmTriggerInputGet(m_TriggerParms);
		m_ErrorList = l_ErrorList;
        return false;
	}
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmTriggerInputGet(sInputTrigger<uParm> & rInputTrig)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":TRIG:SOUR?"
						  ";TIM?"
						  ";COUN?",
						  m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize();
		if (l_Tokens.size() != 3)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Source
		std::string l_Source(l_Tokens[0].begin, l_Tokens[0].end);
		if (l_Source == "IMMEDIATE")
			rInputTrig.Source = kInputTrigSrcImmediate;
		else if (l_Source == "TIMER")
			rInputTrig.Source = kInputTrigSrcTimer;
		else if (l_Source == "BUS")
			rInputTrig.Source = kInputTrigSrcBus;
		else
			throw MTL::CException<CTHM1176Instrument>("Invalid answer format", MTL__LOCATION__);

		// Period
		rInputTrig.Period_s = std::stod(std::string(l_Tokens[1].begin, l_Tokens[1].end));

		// Trigger Count
		rInputTrig.Count = static_cast<U16>(std::stoi(std::string(l_Tokens[2].begin, l_Tokens[2].end)));
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmTriggerInputGet(sInputTrigger<sBoundedParm> & rInputTrig)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(	":TRIG:SOUR?"
						  ";TIM?;TIM? MIN;TIM? MAX;TIM? DEF"
						  ";COUN?;COUN? MIN;COUN? MAX;COUN? DEF",
						  m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize();
		if (l_Tokens.size() != 9)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Source
		std::string l_Source(l_Tokens[0].begin, l_Tokens[0].end);
		if (l_Source == "IMMEDIATE")
			rInputTrig.Source = kInputTrigSrcImmediate;
		else if (l_Source == "TIMER")
			rInputTrig.Source = kInputTrigSrcTimer;
		else if (l_Source == "BUS")
			rInputTrig.Source = kInputTrigSrcBus;
		else
			throw MTL::CException<CTHM1176Instrument>("Invalid answer format", MTL__LOCATION__);

		// Period
		rInputTrig.Period_s.Val = std::stod(std::string(l_Tokens[1].begin, l_Tokens[1].end));
		rInputTrig.Period_s.Min = std::stod(std::string(l_Tokens[2].begin, l_Tokens[2].end));
		rInputTrig.Period_s.Max = std::stod(std::string(l_Tokens[3].begin, l_Tokens[3].end));
		rInputTrig.Period_s.Def = std::stod(std::string(l_Tokens[4].begin, l_Tokens[4].end));

		// Trigger Count
		rInputTrig.Count.Val = static_cast<U16>(std::stoi(std::string(l_Tokens[5].begin, l_Tokens[5].end)));
		rInputTrig.Count.Min = static_cast<U16>(std::stoi(std::string(l_Tokens[6].begin, l_Tokens[6].end)));
		rInputTrig.Count.Max = static_cast<U16>(std::stoi(std::string(l_Tokens[7].begin, l_Tokens[7].end)));
		rInputTrig.Count.Def = static_cast<U16>(std::stoi(std::string(l_Tokens[8].begin, l_Tokens[8].end)));
		}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmUnitsSet(eUnits Units)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " units=" << Units << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	std::string l_Command = ":UNIT " + UnitsToString(Units);

    if (WriteAndRead(l_Command, m_ReadBuffer))
    {
        m_UnitsParms = Units;
        return true;
    }
    else
    {
        CErrorList l_ErrorList = m_ErrorList;
        ParmUnitsGet(m_UnitsParms);
        m_ErrorList = l_ErrorList;
        return false;
    }
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmUnitsGet(eUnits & rUnits)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":UNIT?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		std::string l_Answer(m_ReadBuffer.begin(), m_ReadBuffer.end());
		if (!StringToUnits(l_Answer, rUnits))
			throw MTL::CException<CTHM1176Instrument>("Invalid response format", MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmUseCalibrationSet(bool UseCal)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " usecal=" << UseCal << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	std::string l_Command = std::string(":CAL:STAT ") + (UseCal ? "ON" : "OFF");

	if (WriteAndRead(l_Command, m_ReadBuffer))
	{
		m_UseCalibration = UseCal;
		return true;
	}
	else
		return false;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmUseCalibrationGet(bool	& rUseCal)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rUseCal = m_UseCalibration;
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmRangeSet(const sRange<uParm> & rRange)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " " << rRange << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	std::string l_Command;
	if (rRange.Auto)
		l_Command = ":SENS:AUTO ON";
	else
		l_Command = ":SENS:AUTO OFF;:SENS " + l_ToString(rRange.Range, 1) + " T";

	if (WriteAndRead(l_Command, m_ReadBuffer))
	{
		m_RangeParms = rRange;
		return true;
	}
	else
	{
		CErrorList l_ErrorList = m_ErrorList;
		ParmRangeGet(m_RangeParms);
		m_ErrorList = l_ErrorList;
		return false;
	}
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmRangeGet(sRange<uParm> & rRange)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":SENS:AUTO?;:SENS?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize();
		if (l_Tokens.size() != 2)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Auto-ranging
		std::string l_Auto(l_Tokens[0].begin, l_Tokens[0].end);
		if (l_Auto == "ON")
			rRange.Auto = true;
		else if (l_Auto == "OFF")
			rRange.Auto = false;
		else
			throw MTL::CException<CTHM1176Instrument>("Invalid response", MTL__LOCATION__);

		// Range
		std::string l_Range(l_Tokens[1].begin, l_Tokens[1].end - 2);	// Ignore trailing " T"
		rRange.Range = std::stof(l_Range);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ParmRangeGet(sRange<sBoundedParm> & rRange)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		if (!WriteAndRead(":SENS:AUTO?;:SENS?;:SENS? MIN;:SENS? MAX;:SENS? DEF", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		CSCPIBufferParser::tTokens l_Tokens = l_BP.Tokenize();
		if (l_Tokens.size() != 5)
			throw MTL::CException<CTHM1176Instrument>("Invalid number of tokens in answer", MTL__LOCATION__);

		// Auto-ranging
		std::string l_Auto(l_Tokens[0].begin, l_Tokens[0].end);
		if (l_Auto == "ON")
			rRange.Auto = true;
		else if (l_Auto == "OFF")
			rRange.Auto = false;
		else
			throw MTL::CException<CTHM1176Instrument>("Invalid response", MTL__LOCATION__);

		// Range
		std::string l_Val(l_Tokens[1].begin, l_Tokens[1].end - 2);	// Ignore trailing " T"
		rRange.Range.Val = std::stof(l_Val);

		// Minimum
		std::string l_Min(l_Tokens[2].begin, l_Tokens[2].end - 2);	// Ignore trailing " T"
		rRange.Range.Min = std::stof(l_Min);

		// Maximum
		std::string l_Max(l_Tokens[3].begin, l_Tokens[3].end - 2);	// Ignore trailing " T"
		rRange.Range.Max = std::stof(l_Max);

		// Default
		std::string l_Def(l_Tokens[4].begin, l_Tokens[4].end - 2);	// Ignore trailing " T"
		rRange.Range.Def = std::stof(l_Def);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

//----------------------------------------------------------------------//
//							Files										//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadFileDirectory(U32 & rUsedBytes, U32 & rAvailableBytes, tFileList & rFileList)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rFileList.clear();
	m_ErrorList.clear();

	try
	{
		// Extend timeout.
		if (!InstrType::SetTimeout(THM1176_FILE_ACCESS_TIMEOUT))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not set timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Write the command and read the result.
		if (!WriteAndRead(":MMEM?", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Parse results
		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		std::vector<char>::const_iterator l_tokenbeg, l_tokenend;

		// Used Bytes
		if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ','))
			throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
		rUsedBytes = static_cast<U32>(std::stoul(std::string(l_tokenbeg, l_tokenend)));

		// Available Bytes
		if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ','))
			throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
		rAvailableBytes = static_cast<U32>(std::stoul(std::string(l_tokenbeg, l_tokenend)));

		// File List
		while (l_BP.GetNext(l_tokenbeg, l_tokenend, ','))
		{
			sFile l_File;
			// Path
			l_File.Path = std::string(l_tokenbeg, l_tokenend);
			// Size
			if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ','))
				throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
			l_File.Size = std::stoul(std::string(l_tokenbeg, l_tokenend));
			// Type
			if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ','))
				throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
			l_File.Type = std::string(l_tokenbeg, l_tokenend);
			// Add file to the list
			rFileList.push_back(l_File);
		}

		// Reset timeout to default.
		if (!InstrType::SetTimeout(m_Timeout))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not reset timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		InstrType::SetTimeout(m_Timeout);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadFile(std::string Path, std::string & rContent)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " path=" << Path << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();
	try
	{
		// Build the command.
		std::string l_Command;
		l_Command = ":MMEM:DATA? " "\"" + Path + "\"";

		// Extend timeout.
		if (!InstrType::SetTimeout(THM1176_FILE_ACCESS_TIMEOUT))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not set timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Write the command and read the result.
		if (!WriteAndRead(l_Command, m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Parse the result.
		size_t l_off, l_len;
		if (!IsArbitraryBlock(m_ReadBuffer.begin(), m_ReadBuffer.end(), l_off, l_len))
			throw MTL::CException<CTHM1176Instrument>(std::string("Missing or invalid arbitrary block in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
		rContent = std::string(m_ReadBuffer.begin() + l_off, m_ReadBuffer.begin() + l_off + l_len);

		// Reset timeout to default.
		if (!InstrType::SetTimeout(m_Timeout))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not reset timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		InstrType::SetTimeout(m_Timeout);
		return false;
	}
	return true;
}

//----------------------------------------------------------------------//
//						Initiate / Abort / Trigger						//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::Initiate(bool Continuous)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " cont=" << Continuous << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	// Set up the information needed to reconstruct the timing.
	m_AveragingParmsAtInit	= m_AveragingParms;
	m_TriggerParmsAtInit	= m_TriggerParms;
	m_TriggerTimes.clear();

	// Initiate the measurement.
	std::string l_Command = Continuous ? ":INIT:CONT 1;:INIT" : ":INIT:CONT 0;:INIT";
	return WriteAndRead(l_Command, m_ReadBuffer);
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::Abort(void)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return WriteAndRead(":ABOR", m_ReadBuffer);
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::SendBusTrigger()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();

	// Save the time point.
	std::chrono::high_resolution_clock::time_point l_T = std::chrono::high_resolution_clock::now();
	m_TriggerTimes.push_back(l_T);

	// Assert the trigger.
	return InstrType::AssertTrigger();
}

//----------------------------------------------------------------------//
//							Measurements / Data							//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::MeasurementsGet(U32 NoMeasurements,
										 CFluxList & rBx, CFluxList & rBy, CFluxList & rBz,
										 eUnits & rUnits, U16 & rTemp, CTimestampList & rTimestampList,
										 sMeasurementConditions * pMeasurementConditions)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " nmeas=" << NoMeasurements << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	sArbitraryMeasurements l_ArbSelect;
	l_ArbSelect.Bx				= true;
	l_ArbSelect.By				= true;
	l_ArbSelect.Bz				= true;
	l_ArbSelect.Temperature		= true;
	l_ArbSelect.Timestamp		= true;
	l_ArbSelect.NoMeasurements	= NoMeasurements;

	return MeasurementsGet(l_ArbSelect, rBx, rBy, rBz, rUnits, rTemp, rTimestampList, pMeasurementConditions);
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::MeasurementsGet(sArbitraryMeasurements ArbSelect,
										 CFluxList & rBx, CFluxList & rBy, CFluxList & rBz,
										 eUnits & rUnits, U16 & rTemp, CTimestampList & rTimestampList,
										 sMeasurementConditions * pMeasurementConditions)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " " << ArbSelect << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		// Build command
        std::string l_Command = ":UNIT?;:SENS?";
		if (ArbSelect.Bx)				l_Command += ";:FETC:ARR:X? " + std::to_string(ArbSelect.NoMeasurements);
		if (ArbSelect.By)				l_Command += ";:FETC:ARR:Y? " + std::to_string(ArbSelect.NoMeasurements);
		if (ArbSelect.Bz)				l_Command += ";:FETC:ARR:Z? " + std::to_string(ArbSelect.NoMeasurements);
		if (ArbSelect.Temperature)		l_Command += ";:FETC:TEMP?";
		if (ArbSelect.Timestamp)		l_Command += ";:FETC:TIM?";
		if (m_Sleep)					l_Command += ";:SYST:SLEE";

		// Execute it
		if (!WriteAndRead(l_Command, m_ReadBuffer))
		{
			// See whether we have a serious error that would prevent parsing the data.
            if(!m_ErrorList.empty())
            {
                auto l_pError = m_ErrorList.begin();
                for (; l_pError < m_ErrorList.end(); l_pError++)
                    if (l_pError->Code <= THM1176_FATAL_ERROR_CODE_LIMIT) break;
                if (l_pError < m_ErrorList.end())
                    throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
            }
		}
		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());

		// Get Units
		std::vector<char>::const_iterator l_tokenbeg, l_tokenend;
		if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ';'))
			throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
		if (!StringToUnits(std::string(l_tokenbeg, l_tokenend), rUnits))
			throw MTL::CException<CTHM1176Instrument>("Could not parse units", MTL__LOCATION__);

        // Get the currently selected range.
        if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ';'))
            throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
        std::string l_RangeString(l_tokenbeg, l_tokenend);
        tFlux l_Range = stof(l_RangeString);

        // Get Bx
		rBx.clear();
		if (ArbSelect.Bx)
		{
			if (!ParseMeasurements(l_BP, rUnits, rBx, ArbSelect.NoMeasurements))
				throw MTL::CException<CTHM1176Instrument>("Error parsing Bx", MTL__LOCATION__);
		}

		// Get By
		rBy.clear();
		if (ArbSelect.By)
		{
			if (!ParseMeasurements(l_BP, rUnits, rBy, ArbSelect.NoMeasurements))
				throw MTL::CException<CTHM1176Instrument>("Error parsing By", MTL__LOCATION__);
		}

		// Get Bz
		rBz.clear();
		if (ArbSelect.Bz)
		{
			if (!ParseMeasurements(l_BP, rUnits, rBz, ArbSelect.NoMeasurements))
				throw MTL::CException<CTHM1176Instrument>("Error parsing Bz", MTL__LOCATION__);
		}

		// Apply the angle correction – or not.
		if (!ApplyRotationMatrix(rBx, rBy, rBz))
		{
			sError l_Error = sError(THM1176_NO_ANGLE_CORRECTION_CODE, "No angle correction", __func__);
			m_ErrorList.push_back(l_Error);
		}

        // Get Temperature
		rTemp = 0;
		if (ArbSelect.Temperature)
		{
			if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ';'))
				throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
			std::string l_TemperatureString(l_tokenbeg, l_tokenend);
			rTemp = static_cast<U16>(stoul(l_TemperatureString));
		}

		// Get Timestamp
		rTimestampList.clear();
		if (ArbSelect.Timestamp)
		{
			// Fetch the timestamp of the last acquisition.
			if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ';'))
				throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
			std::string l_TimestampString(l_tokenbeg, l_tokenend);
			U64 l_LastTimestamp = std::stoull(l_TimestampString, nullptr, 0);

			// Reconstruct a list of timestamps for each acquisition.
			// How we do this depends on the trigger mode.
			switch (m_TriggerParmsAtInit.Source)
			{
				// For Immediate trigger, use the empirically determined time per acquisition
				// and time per measurement [ns].
				case kInputTrigSrcImmediate:
					for (int i = ArbSelect.NoMeasurements - 1; i >= 0; i--)
					{
                        F64 l_Period;
                        GetImmediateMeasurementPeriod (m_AveragingParmsAtInit, m_IdentificationStruct.ModelRevision, l_Period);
                        U64 l_TimeOffset = static_cast<U64> (i * l_Period * 1E9 + 0.5);
						rTimestampList.push_back(l_LastTimestamp - l_TimeOffset);
					}
					break;

				// For Timed trigger, use the programmed period [s, with conversion to ns].
				case kInputTrigSrcTimer:
					for (int i = ArbSelect.NoMeasurements - 1; i >= 0; i--)
					{
						U64 l_TimeOffset = static_cast<U64> (i * m_TriggerParmsAtInit.Period_s * 1E9 + 0.5);
						rTimestampList.push_back(l_LastTimestamp - l_TimeOffset);
					}
					break;

				// For Bus trigger, use the time points saved by SendBusTrigger(), [durations converted to ns].
				case kInputTrigSrcBus:
				{
					auto				l_LastTriggerTime = m_TriggerTimes.back();
					for (auto l_pTime = m_TriggerTimes.begin(); l_pTime < m_TriggerTimes.begin() + ArbSelect.NoMeasurements; l_pTime++)
					{
						std::chrono::nanoseconds l_TimeOffset = l_LastTriggerTime - *l_pTime;
						rTimestampList.push_back(l_LastTimestamp - l_TimeOffset.count());
					}
					break;
				}
			}
		} // if (ArbSelect.Timestamp)

		// Get measurement conditions.
		if (pMeasurementConditions)
		{
			// Fill in the actual trigger period.
			switch (m_TriggerParmsAtInit.Source)
			{
				case kInputTrigSrcImmediate:
                    GetImmediateMeasurementPeriod(m_AveragingParmsAtInit, m_IdentificationStruct.ModelRevision, m_TriggerParmsAtInit.Period_s);
					break;
				case kInputTrigSrcTimer:
					break;
				case kInputTrigSrcBus:
					rTimestampList.GetEstimatedPeriod(m_TriggerParmsAtInit.Period_s);
					m_TriggerParmsAtInit.Period_s *= 1E-9;
					break;
			}

			// Copy the parameters relevant to the measurement.
			pMeasurementConditions->TriggerParms	= m_TriggerParmsAtInit;
			pMeasurementConditions->AveragingParms	= m_AveragingParmsAtInit;
            pMeasurementConditions->UseCalibration	= m_UseCalibration;
            pMeasurementConditions->Range           = l_Range;
		}

	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::SetFormat(eCommunicationFormat Format)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " " << Format << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	std::string l_Command;

	switch (Format)
	{
		case kComFormatAscii:
			l_Command += ":FORM ASC";
			break;
		case kComFormatInteger:
			l_Command += ":FORM INT";
			break;
		case kComFormatPacked2Byte:
			l_Command += ":FORM PACK,2";
			break;
		case kComFormatPacked1Byte:
			l_Command += ":FORM PACK,1";
			break;
	}
	if (WriteAndRead(l_Command, m_ReadBuffer))
    {
        m_Format = Format;
        return true;
    }
    else
    {
        CErrorList l_ErrorList = m_ErrorList;
        GetFormat(m_Format);
        m_ErrorList = l_ErrorList;
        return false;
    }
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetFormat(eCommunicationFormat & Format)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		// Fetch the format.
		std::string l_Command = ":FORM?";
		if (!WriteAndRead(l_Command, m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Parse the format.
		std::string l_Format = std::string(m_ReadBuffer.begin(), m_ReadBuffer.end());
		if (std::string("ASCII") == l_Format)
			Format = kComFormatAscii;
		else if (std::string("INTEGER") == l_Format)
			Format = kComFormatInteger;
		else if (std::string("PACKED,2") == l_Format)
			Format = kComFormatPacked2Byte;
		else if (std::string("PACKED,1") == l_Format)
			Format = kComFormatPacked1Byte;
		else
			throw MTL::CException<CTHM1176Instrument>("Received unknown format", MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

//----------------------------------------------------------------------//
// High Level Measurement
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::Measure(tFlux & rBx, tFlux & rBy, tFlux & rBz,
			 bool DefaultParms, eUnits Units, tFlux ExpectedField, unsigned int NoDigits)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " default=" << DefaultParms << " units=" << Units << " expect=" << ExpectedField << " ndigits=" << NoDigits << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		// Build command
		std::string l_Command = ":UNIT " + UnitsToString(Units);
		l_Command += DefaultParms ? ";:MEAS:X?" : ";:READ:X?";
		if (ExpectedField > 0.f || NoDigits != 0) l_Command += " ";
		if (ExpectedField > 0.f)	l_Command += l_ToString(ExpectedField);
		if (NoDigits != 0)			l_Command += "," + std::to_string(NoDigits);
		l_Command += ";:FETC:Y?";
		if (NoDigits != 0)			l_Command += " " + std::to_string(NoDigits);
		l_Command += ";Z?";
		if (NoDigits != 0)			l_Command += " " + std::to_string(NoDigits);
		if (m_Sleep)				l_Command += ";:SYST:SLEE";

		// Execute it
		if (!WriteAndRead(l_Command, m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());

		// Get Bx
		CFluxList l_Meas;
		if (!ParseMeasurements(l_BP, Units, l_Meas, 1))
			throw MTL::CException<CTHM1176Instrument>("Error parsing Bx", MTL__LOCATION__);
		rBx = l_Meas[0];

		// Get By
		if (!ParseMeasurements(l_BP, Units, l_Meas, 1))
			throw MTL::CException<CTHM1176Instrument>("Error parsing By", MTL__LOCATION__);
		rBy = l_Meas[0];

		// Get Bz
		if (!ParseMeasurements(l_BP, Units, l_Meas, 1))
			throw MTL::CException<CTHM1176Instrument>("Error parsing Bz", MTL__LOCATION__);
		rBz = l_Meas[0];

		// Apply the angle correction – or not.
		if (!ApplyRotationMatrix(rBx, rBy, rBz))
		{
			sError l_Error = { THM1176_NO_ANGLE_CORRECTION_CODE, "No angle correction", __func__ };
			m_ErrorList.push_back(l_Error);
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::Measure(CFluxList & rBx, CFluxList & rBy, CFluxList & rBz, unsigned int NoMeasurements,
			 bool DefaultParms, eUnits Units, tFlux ExpectedField, unsigned int NoDigits)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " nmeas=" << NoMeasurements << " default=" << DefaultParms << " units=" << Units << " expect=" << ExpectedField << " ndigits=" << NoDigits << std::endl);
    CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		// Build command
		std::string l_Command = ":UNIT " + UnitsToString(Units);
		l_Command += (DefaultParms ? ";:MEAS:ARR:X? " : ";:READ:ARR:X? ") +
			std::to_string(NoMeasurements);
		if (ExpectedField > 0.f || NoDigits != 0) l_Command += ",";
		if (ExpectedField > 0.f)	l_Command += l_ToString(ExpectedField);
		if (NoDigits != 0)			l_Command += "," + std::to_string(NoDigits);
		l_Command += ";:FETC:ARR:Y? " + std::to_string(NoMeasurements);
		if (NoDigits != 0)			l_Command += "," + std::to_string(NoDigits);
		l_Command += ";Z? " + std::to_string(NoMeasurements);
		if (NoDigits != 0)			l_Command += "," + std::to_string(NoDigits);
		if (m_Sleep)				l_Command += ";:SYST:SLEE";

		// Execute it
		if (!WriteAndRead(l_Command, m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());

		// Get Bx
		CFluxList l_Meas;
		if (!ParseMeasurements(l_BP, Units, rBx, NoMeasurements))
			throw MTL::CException<CTHM1176Instrument>("Error parsing Bx", MTL__LOCATION__);

		// Get By
		if (!ParseMeasurements(l_BP, Units, rBy, NoMeasurements))
			throw MTL::CException<CTHM1176Instrument>("Error parsing By", MTL__LOCATION__);

		// Get Bz
		if (!ParseMeasurements(l_BP, Units, rBz, NoMeasurements))
			throw MTL::CException<CTHM1176Instrument>("Error parsing Bz", MTL__LOCATION__);

		// Apply the angle correction – or not.
		if (!ApplyRotationMatrix(rBx, rBy, rBz))
		{
			sError l_Error = { THM1176_NO_ANGLE_CORRECTION_CODE, "No angle correction", __func__ };
			m_ErrorList.push_back(l_Error);
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

//----------------------------------------------------------------------//
//							Status Handling								//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::StatusPreset()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return WriteAndRead(":STAT:PRES", m_ReadBuffer);
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::StatusGet(sStatusRegister Reg, U16 & rStatus)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " " << Reg << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();
	try
	{
		std::string l_Command(STATUS_GET_CMDS[Reg.Set][Reg.Type]);
		if (0 == l_Command.size())
			throw MTL::CException<CTHM1176Instrument>("Invalid register", MTL__LOCATION__);

		if (!InstrType::Write(l_Command) ||
			!InstrType::Read(m_ReadBuffer) ||
			!GetErrorList(m_ErrorList, l_Command))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		rStatus = static_cast<U16>(std::stoi(std::string(m_ReadBuffer.begin(), m_ReadBuffer.end())));
	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rStatus = 0;
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::StatusGet(RegisterList Regs, StatusValues & rStatus)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();
	try
	{
		std::string l_CompleteCommand;
		for (auto l_Reg : Regs)
		{
			std::string l_Command(STATUS_GET_CMDS[l_Reg.Set][l_Reg.Type]);
			if (0 == l_Command.size())
				throw MTL::CException<CTHM1176Instrument>("Invalid register", MTL__LOCATION__);
			if (l_CompleteCommand.size() > 0) l_CompleteCommand += ";";
			l_CompleteCommand += l_Command;
		}

		if (!InstrType::Write(l_CompleteCommand) ||
			!InstrType::Read(m_ReadBuffer) ||
			!GetErrorList(m_ErrorList, l_CompleteCommand))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed to read status register in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Parse results
		rStatus.clear();
		CSCPIBufferParser l_BP(m_ReadBuffer.begin(), m_ReadBuffer.end());
		std::vector<char>::const_iterator l_tokenbeg, l_tokenend;

		for (size_t i = Regs.size(); i > 0; i--)
		{
			if (!l_BP.GetNext(l_tokenbeg, l_tokenend, ';'))
				throw MTL::CException<CTHM1176Instrument>("Could not find next token", MTL__LOCATION__);
			rStatus.push_back(static_cast<U16>(std::stoi(std::string(l_tokenbeg, l_tokenend))));
		}
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::StatusSetEnableRegister(eStatusRegisterSet Set, U16 DisableMask, U16 EnableMask)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << Set << " disablemask=" << DisableMask << " enablemask=" << EnableMask << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		// Read current enable register
		std::string l_Command(STATUS_GET_CMDS[Set][kStatusEnable]);
		if (!WriteAndRead(l_Command, m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		U16 l_StatusEnab = static_cast<U16>(std::stoi(std::string(m_ReadBuffer.begin(), m_ReadBuffer.end())));

		// Compute new enable register
		l_StatusEnab &= ~DisableMask;
		l_StatusEnab |= EnableMask;

		// Set new enable register
		l_Command = std::string(STATUS_SET_CMDS[Set][kStatusEnable]) + " " + std::to_string(l_StatusEnab);
		if (!WriteAndRead(l_Command, m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("Failed W&R in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		return false;
	}
	return true;
}

//----------------------------------------------------------------------//
//								Utilities								//
//----------------------------------------------------------------------//
template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetIdentification(std::string & rIdentification)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rIdentification = m_Identification;
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetIdentification(struct sIdentifier & rIdentification)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rIdentification = m_IdentificationStruct;
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetAllRanges(CFluxList & rRanges)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rRanges.clear();
	for (auto l_Range : m_Ranges)
		rRanges.push_back(l_Range);
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetAllUnits(CUnitsList & rUnits)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	rUnits.clear();
	for (auto l_Unit : m_Units)
		rUnits.push_back(l_Unit.first);
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetDivisor(eUnits Units, U32 & rDivisor)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " units=" << Units << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
		rDivisor = m_Units.at(Units);
	}
	catch (const std::out_of_range& oor)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(std::string("Units not found: ") + oor.what() + ", " + MTL__FUNCTION_NAME__);
		MTL_Unused(oor);
		rDivisor = 0;
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetRotationMatrix(Matrix3f & Matrix)
{
	Matrix = m_RotationMatrix;
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::GetImmediateMeasurementPeriod(const sAveraging<uParm> & rAvg, eModelRevision modelRev, F64 & rPeriod)
{
	if (rAvg.NoPoints <= 0)
	{
		rPeriod = 0;
		return false;
	}
	else
	{
        switch (modelRev) {
        case kA:
            rPeriod = THM1176_IMMEDIATE_TIME_PER_MEAS_A + rAvg.NoPoints * THM1176_IMMEDIATE_TIME_PER_ACQ_A;
            break;
        case kB:
            rPeriod = THM1176_IMMEDIATE_TIME_PER_MEAS_B + rAvg.NoPoints * THM1176_IMMEDIATE_TIME_PER_ACQ_B;
            break;
        default:
            // Assume type A for the moment
            rPeriod = THM1176_IMMEDIATE_TIME_PER_MEAS_A + rAvg.NoPoints * THM1176_IMMEDIATE_TIME_PER_ACQ_A;
        };

    	return true;
	}
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::CalibrateZeroOffset(bool ForceCalibration)
{
    MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << " force=" << ForceCalibration << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

    // Unless warnings are being overridden, ensure that this model is OK to calibrate.
    if (!ForceCalibration &&
        MODELS_NOT_TO_CALIBRATE.find(m_IdentificationStruct.Model) != MODELS_NOT_TO_CALIBRATE.end())
            return false;

    m_ErrorList.clear();
	try
	{
		// Extend timeout.
		if (!InstrType::SetTimeout(THM1176_CALIBRATION_TIMEOUT))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not set timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Send the command.
        // Note: Reset first to work around firmware bug that can mess up calibratio with non-standard measurement paramters.
        if (!WriteAndRead("*RST;:CAL:INIT", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("W&R failed in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

        // Restore the original parameters.
        CErrorList l_ErrorList = m_ErrorList;
        if (!ParmUnitsSet(m_UnitsParms) ||
            !ParmAveragingSet(m_AveragingParms) ||
            !ParmTriggerInputSet(m_TriggerParms) ||
            !ParmRangeSet(m_RangeParms) ||
            !ParmSleepSet(m_Sleep) ||
            !ParmUseCalibrationSet(m_UseCalibration) ||
            !SetFormat(m_Format))
            throw MTL::CException<CTHM1176Instrument>(std::string("Could not restore parameters in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Reset timeout to default.
		if (!InstrType::SetTimeout(m_Timeout))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not reset timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);

        // Restore the parameters as best as we can.
        CErrorList l_ErrorList = m_ErrorList;
        ParmUnitsSet(m_UnitsParms);
        ParmAveragingSet(m_AveragingParms);
        ParmTriggerInputSet(m_TriggerParms);
        ParmRangeSet(m_RangeParms);
        ParmSleepSet(m_Sleep);
        ParmUseCalibrationSet(m_UseCalibration);
        SetFormat(m_Format);
        m_ErrorList = l_ErrorList;

		InstrType::SetTimeout(m_Timeout);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::RestoreZeroOffset()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();
	try
	{
		// Extend timeout.
		if (!InstrType::SetTimeout(THM1176_CALIBRATION_TIMEOUT))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not set timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Send the command.
		if (!WriteAndRead(":CAL:ZERO", m_ReadBuffer))
			throw MTL::CException<CTHM1176Instrument>(std::string("W&R failed in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);

		// Reset timeout to default.
		if (!InstrType::SetTimeout(m_Timeout))
			throw MTL::CException<CTHM1176Instrument>(std::string("Could not reset timeout in ") + MTL__FUNCTION_NAME__, MTL__LOCATION__);
	}
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		InstrType::SetTimeout(m_Timeout);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::Reset()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	// Clear class variables.
	m_TriggerTimes.clear();
	m_ErrorList.clear();
	m_Sleep				= false;
	m_UseCalibration	= true;
	m_AbortRead			= false;

	try
	{
		// Reset the instrument.
		if (!InstrType::Write("*RST;*CLS"))
			throw MTL::CException<CTHM1176Instrument>("Failed to reset instrument", MTL__LOCATION__);

		// Update the cached parameters.
		if (!ReadUseCalibration(m_UseCalibration))
			throw MTL::CException<CTHM1176Instrument>("Could not get Use Calibration parameter", MTL__LOCATION__);
        if (!ParmUnitsGet(m_UnitsParms))
            throw MTL::CException<CTHM1176Instrument>("Could not get units", MTL__LOCATION__);
        if (!ParmAveragingGet(m_AveragingParms))
            throw MTL::CException<CTHM1176Instrument>("Could not get averaging parameters", MTL__LOCATION__);
        if (!ParmTriggerInputGet(m_TriggerParms))
			throw MTL::CException<CTHM1176Instrument>("Could not get trigger parameters", MTL__LOCATION__);
        if (!ParmRangeGet(m_RangeParms))
            throw MTL::CException<CTHM1176Instrument>("Could not get range parameters", MTL__LOCATION__);
        if (!GetFormat(m_Format))
            throw MTL::CException<CTHM1176Instrument>("Could not get communication format", MTL__LOCATION__);
    }
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);

        // Retrieve the cached parameters as best as we can.
        CErrorList l_ErrorList = m_ErrorList;
        ReadUseCalibration(m_UseCalibration);
        ParmUnitsGet(m_UnitsParms);
		ParmAveragingGet(m_AveragingParms);
		ParmTriggerInputGet(m_TriggerParms);
        ParmRangeGet(m_RangeParms);
        GetFormat(m_Format);
        m_ErrorList = l_ErrorList;

		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::SwitchToDFUMode()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_ErrorList.clear();
	return InstrType::Write(":DIAG:UPGR");
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::ReadInformationDates(std::string & rSManufacturingDate, std::time_t & rManufacturingDate, std::string & rSCalibrationDate, std::time_t & rCalibrationDate)
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try
	{
        // Copy the string version of the Manufacturing date.
        rSManufacturingDate = m_ManufacturingDate;

        // Parse Manufacturing date, YYYY-MM-DD.
		std::match_results<std::string::const_iterator> l_Mm;
		if (!std::regex_match(rSManufacturingDate, l_Mm, std::regex("^([0-9]{4})-([0-9]{2})-([0-9]{2})")))
			throw MTL::CException<CTHM1176Instrument>("Invalid answer format", MTL__LOCATION__);
		struct std::tm l_ManDateStruct = { };
		l_ManDateStruct.tm_year	= std::stoi(l_Mm[1]) - 1900;			// years since 1900
		l_ManDateStruct.tm_mon	= std::stoi(l_Mm[2]) - 1;				// months since January	(0-11)
		l_ManDateStruct.tm_mday	= std::stoi(l_Mm[3]);					// day of the month (1-31)
		if ((rManufacturingDate = mktime(&l_ManDateStruct)) < 0)
			throw MTL::CException<CTHM1176Instrument>("Invalid answer format", MTL__LOCATION__);

        // Copy the string version of the Calibration date.
        rSCalibrationDate = m_CalibrationDate;

        // Parse Calibrationdate, YYYY-MM-DD.
		std::match_results<std::string::const_iterator> l_Cm;
		if (!std::regex_match(rSCalibrationDate, l_Cm, std::regex("^([0-9]{4})-([0-9]{2})-([0-9]{2})")))
			throw MTL::CException<CTHM1176Instrument>("Invalid answer format", MTL__LOCATION__);
		struct tm l_CalDateStruct = { };
		l_CalDateStruct.tm_year	= std::stoi(l_Cm[1]) - 1900;				// years since 1900
		l_CalDateStruct.tm_mon	= std::stoi(l_Cm[2]) - 1;				// months since January	(0-11)
		l_CalDateStruct.tm_mday	= std::stoi(l_Cm[3]);					// day of the month (1-31)
		if ((rCalibrationDate = mktime(&l_CalDateStruct)) < 0)
			throw MTL::CException<CTHM1176Instrument>("Invalid answer format", MTL__LOCATION__);
	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		rSManufacturingDate.clear();
		rManufacturingDate = -1;
		rSCalibrationDate.clear();
		rCalibrationDate = -1;
		return false;
	}
	catch (std::regex_error& rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(std::string(rE.what()) + " (" + l_ParseRegexError(rE) + ") at " + MTL__LOCATION__ + '\n');
		MTL_Unused(rE);
		return false;
	}
	return true;
}

template <class InstrType, class RsrcMgrType>
bool CTHM1176Instrument<InstrType, RsrcMgrType>::AbortRead()
{
	MTL_INSTRUMENT_THM1176_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);

	// Note: this method does not obtain an exclusive lock on the interface,
	// because it is intended to run from a second thread, to abort a WriteAndRead in progress.
	// Its only job is to set m_AbortRead, which is atomic.
	try
	{
		// Set the abort flag.
		m_AbortRead = true;

		// Wait for twice the current timeout value.
		U32 l_CurrentTimeout = InstrType::GetTimeout();
		std::this_thread::sleep_for(std::chrono::milliseconds(2 * l_CurrentTimeout));

		// The Read in WriteAndRead should have quit by now, resetting the abort flag.
		if (m_AbortRead)
			throw MTL::CException<CTHM1176Instrument>("Abort flag not reset in time", MTL__LOCATION__);
	} // try
	catch (MTL::CException<CTHM1176Instrument> & rE)
	{
		MTL_INSTRUMENT_THM1176_DEBUG_CERR(rE.what());
		MTL_Unused(rE);
		m_AbortRead = false;
		return false;
	}

	return true;
}
