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

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief API for VISA instruments: implementation.

// Personal includes
#include "VISAInstrument.h"
#include "OSDefines.h"
#include "Helpers.h"

// Standard includes
#include <string>
#include <cstring>

// Constants
#define VI_STATUS_DESC_MAX_LEN				256
// Definitions
#define READ_STB_WORKAROUND_POLL_PERIOD_MS	10

// Definitions
#define DEBUG_MTL_VISA_INSTRUMENT					0
#define DEBUG_MTL_VISA_INSTRUMENT_ERRORS_ONLY		0
#if (defined(_DEBUG) && defined(DEBUG_MTL_VISA_INSTRUMENT) && DEBUG_MTL_VISA_INSTRUMENT)
#if (defined(DEBUG_MTL_VISA_INSTRUMENT_ERRORS_ONLY) && DEBUG_MTL_VISA_INSTRUMENT_ERRORS_ONLY)
#define MTL_VISA_INSTRUMENT_DEBUG_COUT(__X__)
#else
#define MTL_VISA_INSTRUMENT_DEBUG_COUT(__X__)		COUT(__X__)
#endif
#define MTL_VISA_INSTRUMENT_DEBUG_CERR(__X__)		CERR(__X__)
#else
#define MTL_VISA_INSTRUMENT_DEBUG_COUT(__X__)
#define MTL_VISA_INSTRUMENT_DEBUG_CERR(__X__)
#endif

using namespace MTL::Instrument;

//----------------------------------------------------------------------//
//							Error Handling								//
//----------------------------------------------------------------------//
std::string MTL::Instrument::StatusDescription(ViSession Session, ViStatus Status)
{
	// According to the NI-VISA Programmer Reference Manual:
	//	"If the string cannot be interpreted, the operation returns the warning
	//	code VI_WARN_UNKNOWN_STATUS. However, the output string desc is valid
	//	regardless of the status return value."
	// So, regardless of the return error code, we return the description.
	static ViChar Desc[VI_STATUS_DESC_MAX_LEN];
	viStatusDesc(Session, Status, Desc);
	return std::string(Desc);
}
#if (defined(DEBUG_MTL_VISA_INSTRUMENT) && DEBUG_MTL_VISA_INSTRUMENT)
static void l_DebugCheckStatus(ViSession Session, ViStatus Status)
{
	if (VI_SUCCESS != Status)
	{
		MTL_VISA_INSTRUMENT_DEBUG_CERR(StatusDescription(Session, Status) << std::endl);
	}
}
#else
#define l_DebugCheckStatus(__X__, __Y__)
#endif

//----------------------------------------------------------------------//
//							Resource Manager							//
//----------------------------------------------------------------------//
//------------------------------------------//
// Constructors / destructors
CVISAResourceManager::CVISAResourceManager()
	: m_DefaultRM(0)
{
	m_Status = VI_SUCCESS;
}
CVISAResourceManager::~CVISAResourceManager()
{
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	viClose(m_DefaultRM);
}

//------------------------------------------//
// Initialization / Info
bool CVISAResourceManager::Initialize()
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	try {
		if (0 != m_DefaultRM)
			throw VI_SUCCESS;	// Already initialized

		m_Status = viOpenDefaultRM(&m_DefaultRM);
		if (VI_SUCCESS != m_Status)
		{
			m_DefaultRM = static_cast<ViSession>(NULL);
			throw m_Status;
		}
	}
    catch (I32 & rS)
	{
		MTL_Unused(rS);
		l_DebugCheckStatus(m_DefaultRM, rS);
		return false;
	}

	return true;
}

ViStatus CVISAResourceManager::Status()
{
	return static_cast<ViStatus>(CIEEE488ResourceManager::Status());
}

std::string CVISAResourceManager::StatusDescription (I32 Status)
{
	CLockGuard<CMutex> l_LockGuard(m_Lock);
	
	// According to the NI-VISA Programmer Reference Manual:
	//	"If the string cannot be interpreted, the operation returns the warning
	//	code VI_WARN_UNKNOWN_STATUS. However, the output string desc is valid
	//	regardless of the status return value."
	// So, regardless of the return error code, we return the description.
	static ViChar Desc[VI_STATUS_DESC_MAX_LEN];
	viStatusDesc(m_DefaultRM, Status, Desc);
	return std::string(Desc);
}

const ViSession & CVISAResourceManager::Session()
{
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	return m_DefaultRM;
}
bool CVISAResourceManager::Timeout()
{
	CLockGuard<CMutex> l_LockGuard(m_Lock);
	
	return m_Status == VI_ERROR_TMO;
}

//------------------------------------------//
// Resource utilities
bool CVISAResourceManager::FindResources(CResourceList & rList, std::string Filter)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	bool l_Ret = true;
	rList.clear();
	ViFindList	fList;

	try
	{
		ViChar		rsrcName[VI_FIND_BUFLEN];
		ViUInt32	numInstrs;

		// Find all instruments
		ViString l_Filter = const_cast<ViString>(Filter.c_str());
		m_Status = viFindRsrc(m_DefaultRM, l_Filter, &fList, &numInstrs, rsrcName);
		if (VI_SUCCESS != m_Status && VI_ERROR_RSRC_NFOUND != m_Status)
			throw m_Status;

		while (numInstrs-- && m_Status == VI_SUCCESS)
		{
			rList.push_back(tResourceName(rsrcName));
			m_Status = viFindNext(fList, rsrcName);
			if (VI_SUCCESS != m_Status && VI_ERROR_RSRC_NFOUND != m_Status)
				throw m_Status;
		}

        if (rList.size() > 0)
            m_Status = VI_SUCCESS;
	}
    catch (I32 & rS)
	{
		switch (rS)
		{
		case VI_ERROR_INV_OBJECT:	// The given session reference is invalid
		case VI_ERROR_NSUP_OPER:	// The given sesn does not support this operation.This operation is supported only by a Resource Manager session
		case VI_ERROR_INV_EXPR:		// Invalid expression specified for search
		default:
			l_DebugCheckStatus(m_DefaultRM, rS);
			l_Ret = false;
			break;

		case VI_SUCCESS:
		case VI_ERROR_RSRC_NFOUND:	// Specified expression does not match any devices
			l_Ret = true;
			break;
		}
	}

	viClose(fList);
	return l_Ret;
}
bool CVISAResourceManager::FindResources(CParsedResourceList & rList, std::string Filter)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	bool l_Ret = true;
	rList.clear();
	ViFindList	fList;

	try
	{
		ViChar		rsrcName[VI_FIND_BUFLEN];
		ViUInt32	numInstrs;

		// Find all instruments
		ViString l_Filter = const_cast<ViString>(Filter.c_str());
		m_Status = viFindRsrc(m_DefaultRM, l_Filter, &fList, &numInstrs, rsrcName);
		if (VI_SUCCESS != m_Status && VI_ERROR_RSRC_NFOUND != m_Status)
			throw m_Status;

		while (numInstrs-- && m_Status == VI_SUCCESS)
		{
			ViUInt16 l_Type, l_Number;
			ViChar l_Class[100], l_ExpName[100], l_Alias[100];
			m_Status = viParseRsrcEx(m_DefaultRM, static_cast<ViRsrc>(rsrcName), &l_Type, &l_Number, l_Class, l_ExpName, l_Alias);
			if (VI_SUCCESS == m_Status)
			{
				tResourceName		l_ResourceName(rsrcName);
				VISAResourceInfo	l_VISAResourceInfo(	static_cast<eInterfaceType>(l_Type),
														static_cast<tInterfaceNumber>(l_Number),
														static_cast<VISAResourceInfo::tClass>(l_Class),
														static_cast<VISAResourceInfo::tExpandedName>(l_ExpName),
														static_cast<VISAResourceInfo::tAlias>(l_Alias));
				rList.push_back(sParsedResource(l_ResourceName, l_VISAResourceInfo));

				m_Status = viFindNext(fList, rsrcName);
				if (VI_SUCCESS != m_Status && VI_ERROR_RSRC_NFOUND != m_Status)
					throw m_Status;
			}
			else if (VI_ERROR_RSRC_NFOUND != m_Status)
				throw m_Status;
		}
	}
    catch (I32 & rS)
	{
		switch (rS)
		{
		default:
			l_DebugCheckStatus(m_DefaultRM, rS);
			l_Ret = false;
			break;

		case VI_SUCCESS:
		case VI_ERROR_RSRC_NFOUND:	// Specified expression does not match any devices
			l_Ret = true;
			break;
		}
	}

	viClose(fList);
	return l_Ret;
}
bool CVISAResourceManager::ResourceInfo(tResourceName & rRsrc, eInterfaceType & rIntfType, tInterfaceNumber & rIntfNumber)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	bool l_Ret = true;
	try
	{
		ViUInt16 l_Type, l_Number;
		m_Status = viParseRsrc(m_DefaultRM, const_cast<ViRsrc>(rRsrc.c_str()), &l_Type, &l_Number);
		if (VI_SUCCESS != m_Status)
			throw m_Status;

		rIntfType = static_cast<eInterfaceType>(l_Type);
		rIntfNumber = static_cast<tInterfaceNumber>(l_Number);
	}

    catch (I32 & rS)
	{
		switch (rS)
		{
		case VI_SUCCESS:
			l_Ret =  true;
			break;
		case VI_ERROR_INV_OBJECT:		// The given session reference is invalid
		case VI_ERROR_NSUP_OPER:		// The given sesn does not support this operation. For VISA, this operation is supported only by the Default Resource Manager session.
		case VI_ERROR_INV_RSRC_NAME:	// Invalid resource reference specified. Parsing error.
		case VI_ERROR_RSRC_NFOUND:		// Insufficient location information or resource not present in the system.
		case VI_ERROR_ALLOC:			// Insufficient system resources to parse the string.
		case VI_ERROR_LIBRARY_NFOUND:	// A code library required by VISA could not be located or loaded.
		case VI_ERROR_INTF_NUM_NCONFIG:	// The interface type is valid, but the specified interface number is not configured.
		default:
			l_DebugCheckStatus(m_DefaultRM, rS);
			l_Ret = false;
			break;
		}
	}

	return l_Ret;
}
bool CVISAResourceManager::ResourceInfo(tResourceName & rRsrc, VISAResourceInfo & rReturnedInfo)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	bool l_Ret = true;
	try
	{
		ViUInt16 l_Type, l_Number;
		ViChar l_Class[100], l_ExpName[100], l_Alias[100];
		m_Status = viParseRsrcEx(m_DefaultRM, const_cast<ViRsrc>(rRsrc.c_str()), &l_Type, &l_Number, l_Class, l_ExpName, l_Alias);
		if (VI_SUCCESS != m_Status)
			throw m_Status;
		rReturnedInfo = VISAResourceInfo(
			static_cast<eInterfaceType>(l_Type),
			static_cast<tInterfaceNumber>(l_Number),
			static_cast<VISAResourceInfo::tClass>(l_Class),
			static_cast<VISAResourceInfo::tExpandedName>(l_ExpName),
			static_cast<VISAResourceInfo::tAlias>(l_Alias));
	}
    catch (I32 & rS)
	{
		switch (rS)
		{
		case VI_SUCCESS:
		case VI_WARN_EXT_FUNC_NIMPL:	// The operation succeeded, but a lower level driver did not implement the extended functionality.
			l_Ret = true;
			break;
		case VI_ERROR_INV_OBJECT:		// The given session reference is invalid
		case VI_ERROR_NSUP_OPER:		// The given sesn does not support this operation. For VISA, this operation is supported only by the Default Resource Manager session.
		case VI_ERROR_INV_RSRC_NAME:	// Invalid resource reference specified. Parsing error.
		case VI_ERROR_RSRC_NFOUND:		// Insufficient location information or resource not present in the system.
		case VI_ERROR_ALLOC:			// Insufficient system resources to parse the string.
		case VI_ERROR_LIBRARY_NFOUND:	// A code library required by VISA could not be located or loaded.
		case VI_ERROR_INTF_NUM_NCONFIG:	// The interface type is valid, but the specified interface number is not configured.
		default:
			l_DebugCheckStatus(m_DefaultRM, rS);
			l_Ret = false;
			break;
		}
	}
	return l_Ret;
}

//----------------------------------------------------------------------//
//								Instrument								//
//----------------------------------------------------------------------//
//------------------------------------------//
// Constructors / destructors
CVISAInstrument::CVISAInstrument(CVISAResourceManager & rRM, tResourceName Rsrc)
    : CIEEE488Instrument(rRM, Rsrc), m_InstrSession(static_cast<ViSession>(NULL)), m_ExclusiveLock(false)
{
	m_Status = VI_SUCCESS;
}
CVISAInstrument::~CVISAInstrument()
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	viClose(m_InstrSession);
}

//------------------------------------------//
// Connexion
bool CVISAInstrument::Open(void)
{
    return Open(eOpenAccessMode::NoLock, 0);
}
bool CVISAInstrument::Open(eOpenAccessMode AccessMode, ViUInt32 Timeout_ms)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try {
		if (!IsOpen())
		{
			ViSession l_Session = dynamic_cast<CVISAResourceManager *>(&m_rRrsrcMan)->Session();
			m_Status = viOpen(l_Session, const_cast<ViRsrc>(m_Rsrc.c_str()), static_cast<ViAccessMode>(AccessMode), Timeout_ms, &m_InstrSession);
			if (VI_SUCCESS != m_Status)
				throw m_Status;
		}
	}
    catch (I32 & rS)
	{
		MTL_Unused(rS);
		l_DebugCheckStatus(m_InstrSession, rS);
		return false;
	}
	return true;
}
void CVISAInstrument::Close()
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	if (IsOpen())
	{
		m_Status = viClose(m_InstrSession);
		MTL_Assert(VI_SUCCESS == m_Status || VI_ERROR_CLOSING_FAILED == m_Status);
		m_InstrSession = 0;
		l_DebugCheckStatus(m_InstrSession, m_Status);
	}
}
bool CVISAInstrument::IsOpen(void)
{
	return (0 != m_InstrSession);
}

//------------------------------------------//
// Info
std::string CVISAInstrument::StatusDescription (I32 Status)
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	// According to the NI-VISA Programmer Reference Manual:
	//	"If the string cannot be interpreted, the operation returns the warning
	//	code VI_WARN_UNKNOWN_STATUS. However, the output string desc is valid
	//	regardless of the status return value."
	// So, regardless of the return error code, we return the description.
	static ViChar Desc[VI_STATUS_DESC_MAX_LEN];
	viStatusDesc(m_InstrSession, Status, Desc);
	return std::string(Desc);
}

const ViSession & CVISAInstrument::Session()
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return m_InstrSession;
}
bool CVISAInstrument::Timeout()
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	return m_Status == VI_ERROR_TMO;
}

//------------------------------------------//
// Write / Read
bool CVISAInstrument::Write(const char * Str)
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return Write(reinterpret_cast<ViBuf>(const_cast<char *>(Str)), static_cast<ViUInt32>(std::strlen(Str)));
}
bool CVISAInstrument::Write(const std::string & rStr)
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return Write(reinterpret_cast<ViBuf>(const_cast<char *>(rStr.c_str())), static_cast<ViUInt32>(rStr.length()));
}
bool CVISAInstrument::Write(const CSCPIBuffer & rBuf)
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return Write(reinterpret_cast<ViBuf>(const_cast<char *>(rBuf.data())), static_cast<ViUInt32>(rBuf.size()));
}
bool CVISAInstrument::Write(const ViBuf WriteBuf, ViUInt32 BuffLen)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	ViUInt32	retCount;
	try {
		m_Status = viWrite(m_InstrSession, WriteBuf, BuffLen, &retCount);
		if (VI_SUCCESS != m_Status)
			throw m_Status;
	}
    catch (I32 & rS)
	{
		MTL_Unused(rS);
		l_DebugCheckStatus(m_InstrSession, rS);
        viClear(m_InstrSession);
		return false;
	}
	return (m_Status == VI_SUCCESS && BuffLen == retCount);
}
bool CVISAInstrument::Read(CSCPIBuffer & rBuf, bool Append)
{
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	bool l_Ret;
	ViUInt32 l_RetLen;
	if (!Append)
	{
		l_Ret = Read(reinterpret_cast<ViPBuf>(rBuf.data()), static_cast<ViUInt32>(rBuf.capacity()), l_RetLen);
		rBuf.resize(l_RetLen);
	}
	else
	{
		// Append. First increase capacity if buffer is full.
		size_t l_Size		= rBuf.size();
		size_t l_Capacity	= rBuf.capacity();
		if (l_Size >= l_Capacity)
		{
			l_Capacity *= 2;
			rBuf.reserve(l_Capacity);
		}
		l_Ret = Read(reinterpret_cast<ViPBuf>(rBuf.data() + l_Size), static_cast<ViUInt32>(l_Capacity - l_Size), l_RetLen);
		rBuf.resize(l_Size + l_RetLen);
	}
	return l_Ret;
}
bool CVISAInstrument::Read(ViPBuf ReadBuf, ViUInt32 ReadLen, ViUInt32 & rRetLen)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viRead(m_InstrSession, ReadBuf, ReadLen, &rRetLen);
	switch (m_Status)
	{
	case VI_SUCCESS:			// The operation completed successfully and the END indicator was received(for interfaces that have END indicators).This completion code is returned regardless of whether the termination character is received or the number of bytes read is equal to count.
	case VI_SUCCESS_TERM_CHAR:	// The specified termination character was read but no END indicator was received. This completion code is returned regardless of whether the number of bytes read is equal to count.
	case VI_SUCCESS_MAX_CNT:	// The number of bytes read is equal to count.No END indicator was received and no termination character was read.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}

//------------------------------------------//
// Other operations
bool CVISAInstrument::SetTimeout(ViUInt32 Timeout)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viSetAttribute(m_InstrSession, VI_ATTR_TMO_VALUE, Timeout);
	if (m_Status == VI_SUCCESS)
		CIEEE488Instrument::SetTimeout(Timeout);
	
	l_DebugCheckStatus(m_InstrSession, m_Status);
	return (m_Status == VI_SUCCESS);
}
bool CVISAInstrument::Clear()
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viClear(m_InstrSession);
	l_DebugCheckStatus(m_InstrSession, m_Status);
	return (m_Status == VI_SUCCESS);
}
bool CVISAInstrument::ReadSTB(ViUInt16 & rSTB)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

#if (defined(ACTIVATE_READ_STB_WORKAROUND) && ACTIVATE_READ_STB_WORKAROUND)
	// A bug was introduced in VISA 14. Despite our efforts to convince NI to fix the bug, we today have to deal with it...
	// This bug is a timeout error returned sometimes by the VISA Read STB VI.When the problem occurs, the VI spends the current timeout time to finally return the above error code.
	// Our workaround :
	//	We reduce the timeout to 10 ms to avoid blocking into the Read STB VI for a long time.
	//	If we get the timeout error, we allow 10 retries to retrieve the STB before giving up.Actually, 1 retry seems to be enough to get the Read STB working.
	//	To prevent from asking the STB right after if failed, a 5ms delay is performed between each retry.
	try {
		// Save current timeout
		ViUInt32 l_CurrentTimeout;
		m_Status = viGetAttribute(m_InstrSession, VI_ATTR_TMO_VALUE, &l_CurrentTimeout);
		if (m_Status != VI_SUCCESS)		throw false;
		// Set current timeout to avoid blocking too long
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_TMO_VALUE, READ_STB_WORKAROUND_POLL_PERIOD_MS);
		if (m_Status != VI_SUCCESS)		throw false;
		// Try to read STB while there is a known error
		for (unsigned char NoRetries = 5; NoRetries > 0; NoRetries--)
		{
			m_Status = viReadSTB(m_InstrSession, &rSTB);
			// Sleep for 5 ms in case of error to reduce polling period
			if (m_Status == VI_ERROR_TMO || m_Status == VI_ERROR_INV_PROT)
			{
				l_DebugCheckStatus(m_InstrSession, m_Status);
				MTL_VISA_INSTRUMENT_DEBUG_CERR("CVISAInstrument::ReadSTB failed. Retrying." << std::endl);
				MTL_Assert(READ_STB_WORKAROUND_POLL_PERIOD_MS);
			}
			// Break the loop if we finally got our STB
			else
			{
				break;
			}
		}
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_TMO_VALUE, l_CurrentTimeout);
		if (m_Status != VI_SUCCESS)		throw false;
	}
	catch (bool & rE)
	{
		MTL_Unused(rE);
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
#else
	m_Status = viReadSTB(m_InstrSession, &rSTB);
#endif
	l_DebugCheckStatus(m_InstrSession, m_Status);
	return (m_Status == VI_SUCCESS);
}
bool CVISAInstrument::AssertTrigger(void)
{
	return AssertTrigger(eTriggerProtocol::Default);
}
bool CVISAInstrument::AssertTrigger(eTriggerProtocol Protocol)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viAssertTrigger(m_InstrSession, static_cast<ViUInt16>(Protocol));
	l_DebugCheckStatus(m_InstrSession, m_Status);
	return (m_Status == VI_SUCCESS);
}

//------------------------------------------//
// Lock / Unlock
bool CVISAInstrument::LockExclusive(U32 Timeout)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viLock(m_InstrSession, VI_EXCLUSIVE_LOCK, static_cast<ViUInt32>(Timeout), nullptr, nullptr);
	switch (m_Status)
	{
	case VI_SUCCESS:
	case VI_SUCCESS_NESTED_EXCLUSIVE:
	case VI_SUCCESS_NESTED_SHARED:
        m_ExclusiveLock = true;
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::LockShared(ViUInt32 Timeout, ViKeyId RequestedKey, ViChar AccessKey[])
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viLock(m_InstrSession, VI_SHARED_LOCK, Timeout, RequestedKey, AccessKey);
	switch (m_Status)
	{
	case VI_SUCCESS:					// Lock successfully relinquished.
	case VI_SUCCESS_NESTED_EXCLUSIVE:	// Call succeeded, but this session still has nested exclusive locks.
	case VI_SUCCESS_NESTED_SHARED:		// Call succeeded, but this session still has nested shared locks.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::Unlock()
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viUnlock(m_InstrSession);
	switch (m_Status)
	{
	case VI_SUCCESS:					// Lock successfully relinquished.
	case VI_SUCCESS_NESTED_EXCLUSIVE:	// Call succeeded, but this session still has nested exclusive locks.
	case VI_SUCCESS_NESTED_SHARED:		// Call succeeded, but this session still has nested shared locks.
        m_ExclusiveLock = false;
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::LockedExclusive()
{
    return m_ExclusiveLock;
}

//------------------------------------------//
// Events
bool CVISAInstrument::EnableEvent (void)
{
    return EnableEvent(eEventType::ServiceRequest, eEventMechanism::Queue);
}
bool CVISAInstrument::DisableEvent (void)
{
    return DisableEvent(eEventType::ServiceRequest, eEventMechanism::Queue);
}
bool CVISAInstrument::WaitOnEvent (U32 Timeout)
{
    return WaitOnEvent(eEventType::ServiceRequest, Timeout);
}
bool CVISAInstrument::DiscardEvents (void)
{
    return DiscardEvents(eEventType::ServiceRequest, eEventMechanism::Queue);
}
bool CVISAInstrument::EnableEvent(eEventType Type, eEventMechanism Mechanism)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viEnableEvent(m_InstrSession, static_cast<ViEventType>(Type), static_cast<ViUInt16>(Mechanism), VI_NULL);
	switch (m_Status)
	{
	case VI_SUCCESS:			// Event enabled successfully.
	case VI_SUCCESS_EVENT_EN:	// Specified event is already enabled for at least one of the specified mechanisms.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::DisableEvent(eEventType Type, eEventMechanism Mechanism)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viDisableEvent(m_InstrSession, static_cast<ViEventType>(Type), static_cast<ViUInt16>(Mechanism));
	switch (m_Status)
	{
	case VI_SUCCESS:			// Event disabled successfully.
	case VI_SUCCESS_EVENT_DIS:	// Specified event is already disabled for at least one of the specified mechanisms.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::WaitOnEvent(eEventType Type, ViUInt32 Timeout)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viWaitOnEvent(m_InstrSession, static_cast<ViEventType>(Type), Timeout, nullptr, nullptr);
	switch (m_Status)
	{
	case VI_SUCCESS:				// Wait terminated successfully on receipt of an event occurrence.The queue is empty.
	case VI_SUCCESS_QUEUE_NEMPTY:	// Wait terminated successfully on receipt of an event notification.There is still at least one more event occurrence of the type specified by inEventType available for this session.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::WaitOnAllEvents(ViUInt32 Timeout)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viWaitOnEvent(m_InstrSession, VI_ALL_ENABLED_EVENTS, Timeout, nullptr, nullptr);
	switch (m_Status)
	{
	case VI_SUCCESS:				// Wait terminated successfully on receipt of an event occurrence.The queue is empty.
	case VI_SUCCESS_QUEUE_NEMPTY:	// Wait terminated successfully on receipt of an event notification.There is still at least one more event occurrence of the type specified by inEventType available for this session.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}
bool CVISAInstrument::DiscardEvents(eEventType Type, eEventMechanism Mechanism)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viDiscardEvents(m_InstrSession, static_cast<ViEventType>(Type), static_cast<ViUInt16>(Mechanism));
	switch (m_Status)
	{
	case VI_SUCCESS:				// Event queue flushed successfully.
	case VI_SUCCESS_QUEUE_EMPTY:	// Operation completed successfully, but queue was already empty.
		return true;
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}

//------------------------------------------//
// Attributes
bool CVISAInstrument::GetAttribute(ViAttr Attribute, void * Value)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viGetAttribute(m_InstrSession, Attribute, Value);
	switch (m_Status)
	{
	case VI_SUCCESS:				// Event queue flushed successfully.
		return true;
	case VI_ERROR_INV_OBJECT:		// The given object reference is invalid.
	case VI_ERROR_NSUP_ATTR:		// The specified attribute is not defined by the referenced object.
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}

//------------------------------------------//
// USB
bool CVISAInstrument::UsbControlIn(ViInt16 bmRequestType, ViInt16 bRequest, ViUInt16 wValue, ViUInt16 wIndex, ViUInt16 wLength, ViPBuf buf, ViUInt16 & rretCnt)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viUsbControlIn(m_InstrSession, bmRequestType, bRequest, wValue, wIndex, wLength, buf, &rretCnt);
	switch (m_Status)
	{
	case VI_SUCCESS:
		return true;
	case VI_ERROR_INV_OBJECT:		// The given session reference is invalid.
	case VI_ERROR_RSRC_LOCKED:		// Specified operation could not be performed because the resource identified by vi has been locked for this kind of access.
	case VI_ERROR_TMO:				// Timeout expired before operation completed.
	case VI_ERROR_INV_SETUP:		// Unable to start write operation because setup is invalid(due to attributes being set to an inconsistent state).
	case VI_ERROR_IO:				// An unknown I/O error occurred during transfer.
	case VI_ERROR_CONN_LOST:		// The I/O connection for the given session has been lost.
	case VI_ERROR_INV_PARAMETER:	// The value of some parameter�which parameter is not known�is invalid.
	case VI_ERROR_INV_MASK:			// The bmRequestType parameter contains an invalid mask.
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}

bool CVISAInstrument::UsbControlOut(ViInt16 bmRequestType, ViInt16 bRequest, ViUInt16 wValue, ViUInt16 wIndex, ViUInt16 wLength, ViBuf buf)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	m_Status = viUsbControlOut(m_InstrSession, bmRequestType, bRequest, wValue, wIndex, wLength, buf);
	switch (m_Status)
	{
	case VI_SUCCESS:
		return true;
	case VI_ERROR_INV_OBJECT:		// The given session reference is invalid.
	case VI_ERROR_RSRC_LOCKED:		// Specified operation could not be performed because the resource identified by vi has been locked for this kind of access.
	case VI_ERROR_TMO:				// Timeout expired before operation completed.
	case VI_ERROR_INV_SETUP:		// Unable to start write operation because setup is invalid(due to attributes being set to an inconsistent state).
	case VI_ERROR_IO:				// An unknown I/O error occurred during transfer.
	case VI_ERROR_CONN_LOST:		// The I/O connection for the given session has been lost.
	case VI_ERROR_INV_PARAMETER:	// The value of some parameter�which parameter is not known�is invalid.
	case VI_ERROR_INV_MASK:			// The bmRequestType parameter contains an invalid mask.
	default:
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}
}

//------------------------------------------//
//Serial (ASRL)
bool CVISAInstrument::ConfigSerialPort(const sSerialPortSettings & rPortSettings)
{
	MTL_VISA_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	try {
		// set Baud Rate
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_ASRL_BAUD, static_cast<ViAttrState>(rPortSettings.Baudrate));
		if (m_Status != VI_SUCCESS)		throw false;

		// set Data Bits length
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_ASRL_DATA_BITS, static_cast<ViAttrState>(rPortSettings.DataBits));
		if (m_Status != VI_SUCCESS)		throw false;

		// set Parity
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_ASRL_PARITY, static_cast<ViAttrState>(rPortSettings.Parity));
		if (m_Status != VI_SUCCESS)		throw false;

		// set Stop bits
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_ASRL_STOP_BITS, static_cast<ViAttrState>(rPortSettings.StopBits));
		if (m_Status != VI_SUCCESS)		throw false;

		// set Flow control
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_ASRL_FLOW_CNTRL, static_cast<ViAttrState>(rPortSettings.Handshake));
		if (m_Status != VI_SUCCESS)		throw false;

		// set Read Termination Character Mode
		m_Status = viSetAttribute(m_InstrSession, VI_ATTR_ASRL_END_IN, static_cast<ViAttrState>(rPortSettings.ReadTermMode));
		if (m_Status != VI_SUCCESS)		throw false;

		if (rPortSettings.ReadTermMode == eSerialTermMode::kEndTermChar)
		{
			// set a specific Read Termination Character
			m_Status = viSetAttribute(m_InstrSession, VI_ATTR_TERMCHAR, static_cast<ViAttrState>(rPortSettings.ReadTermChar));
			if (m_Status != VI_SUCCESS)		throw false;

			//  Enable specific Read Termination Character
			m_Status = viSetAttribute(m_InstrSession, VI_ATTR_TERMCHAR_EN, static_cast<ViAttrState>(true));
			if (m_Status != VI_SUCCESS)		throw false;
		}
		else
		{
			//  Disable specific Read Termination Character
			m_Status = viSetAttribute(m_InstrSession, VI_ATTR_TERMCHAR_EN, static_cast<ViAttrState>(false));
			if (m_Status != VI_SUCCESS)		throw false;
		}
	}
	catch (bool & rE)
	{
		MTL_Unused(rE);
		l_DebugCheckStatus(m_InstrSession, m_Status);
		return false;
	}

	return true;
}
