using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace OpenTap
{
/// Implements Visa SCPI IO.
class ScpiIO : IScpiIO3
{
private bool sendEnd = true;
private int lockTimeout = 5000;
private int ioTimeout = 2000;
private byte termChar = 10;
private bool useTermChar = false;
private int rm = Visa.VI_NULL;
private int instrument = Visa.VI_NULL;
private bool IsConnected { get { return instrument != Visa.VI_NULL; } }
public int ID => instrument;
public event ScpiIOSrqDelegate SRQ
{
add
{
lock (srqLock)
{
try
{
srqListeners.Add(value);
if ((srqListeners.Count == 1) && IsConnected)
EnableSRQ();
}
catch
{
srqListeners.Remove(value);
if (srqListeners.Count == 0)
DisableSRQ();
throw;
}
}
}
remove
{
lock (srqLock)
{
srqListeners.Remove(value);
if ((srqListeners.Count == 0) && IsConnected)
DisableSRQ();
}
}
}
private void RaiseError(int error)
{
if (error < 0)
throw new VISAException(ID, error);
}
private void invokeSrqListeners()
{
var listeners = srqListeners.ToList();
foreach (var l in listeners)
{
try
{
l.Invoke(this);
}
catch (Exception ex)
{
log.Error($"Unhandled exception in SRQ listener '{l.GetType().FullName}': {ex.Message}");
log.Debug(ex);
}
}
}
private GCHandle srqDelegateHandle;
private static readonly TraceSource log = Log.CreateSource("SCPI");
void EnableSRQ()
{
// We pass this delegate into unmanaged code, so we need to ensure it will not be garbage collected.
// This use of GCAlloc is in line with Microsoft recommendations when passing references to unmanaged libraries.
// See the description of `GCHandleType.Normal' here:
// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.gchandletype?view=net-9.0
// ... This enumeration member is useful when an unmanaged client holds the only reference,
// which is undetectable from the garbage collector, to a managed object.
if (!srqDelegateHandle.IsAllocated)
{
srqDelegateHandle = GCHandle.Alloc(new VisaFunctions.ViEventHandler((_, _, _, _) =>
{
try
{
invokeSrqListeners();
}
catch (Exception ex)
{
log.Error($"Unhandled exception during SRQ event: {ex.Message}");
log.Debug(ex);
}
return Visa.VI_SUCCESS;
}), GCHandleType.Normal);
}
RaiseError(Visa.viInstallHandler(instrument, Visa.VI_EVENT_SERVICE_REQ, (VisaFunctions.ViEventHandler)srqDelegateHandle.Target, 0));
RaiseError(Visa.viEnableEvent(instrument, Visa.VI_EVENT_SERVICE_REQ, Visa.VI_HNDLR, Visa.VI_NULL));
}
void DisableSRQ()
{
if (srqDelegateHandle.IsAllocated)
{
RaiseError(Visa.viDisableEvent(instrument, Visa.VI_EVENT_SERVICE_REQ, Visa.VI_ALL_MECH));
RaiseError(Visa.viUninstallHandler(instrument, Visa.VI_EVENT_SERVICE_REQ, null, 0));
srqDelegateHandle.Free();
srqDelegateHandle = default;
}
}
public void OpenSRQ()
{
lock (srqLock)
if (srqListeners.Count > 0)
EnableSRQ();
}
public void CloseSRQ()
{
lock (srqLock)
DisableSRQ();
}
private List srqListeners = new List();
private object srqLock = new object();
private ScpiIOResult MakeError(int result)
{
switch (result)
{
case Visa.VI_SUCCESS: return ScpiIOResult.Success;
case Visa.VI_SUCCESS_MAX_CNT: return ScpiIOResult.Success_MaxCount;
case Visa.VI_SUCCESS_TERM_CHAR: return ScpiIOResult.Success_TermChar;
case Visa.VI_ERROR_TMO: return ScpiIOResult.Error_Timeout;
case Visa.VI_ERROR_RSRC_LOCKED: return ScpiIOResult.Error_ResourceLocked;
case Visa.VI_ERROR_CONN_LOST: return ScpiIOResult.Error_ConnectionLost;
case Visa.VI_ERROR_RSRC_NFOUND: return ScpiIOResult.Error_ResourceNotFound;
}
if (result >= 0)
return ScpiIOResult.Success;
else
return ScpiIOResult.Error_General;
}
private void SyncSettings()
{
Visa.viSetAttribute(instrument, Visa.VI_ATTR_SEND_END_EN, (byte)(sendEnd ? 1 : 0));
Visa.viSetAttribute(instrument, Visa.VI_ATTR_TMO_VALUE, ioTimeout);
Visa.viSetAttribute(instrument, Visa.VI_ATTR_TERMCHAR, termChar);
Visa.viSetAttribute(instrument, Visa.VI_ATTR_TERMCHAR_EN, (byte)(useTermChar ? 1 : 0));
}
public ScpiIOResult Open(string visaAddress, bool exclusiveLock)
{
if (instrument != Visa.VI_NULL)
throw new Exception("IO is already open");
var res2 = Visa.viOpenDefaultRM(out rm);
if (res2 < 0) return MakeError(res2);
var res = Visa.viOpen(ScpiInstrument.GetResourceManager(), visaAddress, exclusiveLock ? Visa.VI_EXCLUSIVE_LOCK : Visa.VI_NO_LOCK, IOTimeoutMS, out instrument);
// Use sensible defaults
sendEnd = true;
useTermChar = false;
if (res >= 0)
SyncSettings();
else
instrument = Visa.VI_NULL;
return MakeError(res);
}
public ScpiIOResult Close()
{
try
{
return MakeError(Visa.viClose(instrument));
}
finally
{
Visa.viClose(rm);
instrument = Visa.VI_NULL;
}
}
public bool SendEnd
{
get => sendEnd;
set
{
if (sendEnd != value)
{
sendEnd = value;
if (IsConnected) Visa.viSetAttribute(instrument, Visa.VI_ATTR_SEND_END_EN, (byte)(value ? 1 : 0));
}
}
}
public int IOTimeoutMS
{
get => ioTimeout;
set
{
if (ioTimeout != value)
{
ioTimeout = value;
if (IsConnected) Visa.viSetAttribute(instrument, Visa.VI_ATTR_TMO_VALUE, ioTimeout);
}
}
}
public int LockTimeoutMS
{
get { return lockTimeout; }
set { lockTimeout = value; }
}
public byte TerminationCharacter
{
get => termChar;
set
{
if (termChar != value)
{
termChar = value;
if (IsConnected) Visa.viSetAttribute(instrument, Visa.VI_ATTR_TERMCHAR, value);
}
}
}
public bool UseTerminationCharacter
{
get => useTermChar;
set
{
if (useTermChar != value)
{
useTermChar = value;
if (IsConnected) Visa.viSetAttribute(instrument, Visa.VI_ATTR_TERMCHAR_EN, (byte)(value ? 1 : 0));
}
}
}
public string ResourceClass
{
get
{
var sb = new StringBuilder();
Visa.viGetAttribute(instrument, Visa.VI_ATTR_RSRC_CLASS, sb);
return sb.ToString();
}
}
public ScpiIOResult DeviceClear()
{
return MakeError(Visa.viClear(instrument));
}
public ScpiIOResult Lock(ScpiLockType lockType, string sharedKey = null)
{
var sb = new StringBuilder();
return MakeError(Visa.viLock(instrument, lockType == ScpiLockType.Exclusive ? Visa.VI_EXCLUSIVE_LOCK : Visa.VI_SHARED_LOCK, LockTimeoutMS, sharedKey, sb));
}
public ScpiIOResult Read(ArraySegment buffer, int count, ref bool eoi, ref int read)
{
var res = Visa.viRead(instrument, buffer, count, out read);
eoi = (res == Visa.VI_SUCCESS);
return MakeError(res);
}
public ScpiIOResult ReadSTB(ref byte stb)
{
short statusByte = 0;
var res = Visa.viReadSTB(instrument, ref statusByte);
stb = (byte)statusByte;
return MakeError(res);
}
public ScpiIOResult Unlock()
{
return MakeError(Visa.viUnlock(instrument));
}
public ScpiIOResult Write(ArraySegment buffer, int count, ref int written)
{
return MakeError(Visa.viWrite(instrument, buffer, count, out written));
}
public ScpiIOResult EnableEvent(ScpiEvent eventType, ScpiEventMechanism mechanism)
{
return MakeError(Visa.viEnableEvent(instrument, (int)eventType, (short)mechanism, Visa.VI_NULL));
}
public ScpiIOResult DisableEvent(ScpiEvent eventType, ScpiEventMechanism mechanism)
{
return MakeError(Visa.viDisableEvent(instrument, (int)eventType, (short)mechanism));
}
public ScpiIOResult WaitOnEvent(ScpiEvent eventType, int timeout, out ScpiEvent outEventType)
{
var result = Visa.viWaitOnEvent(instrument, (int)eventType, timeout, out int outEvent, IntPtr.Zero);
outEventType = (ScpiEvent)outEvent;
return MakeError(result);
}
}
}