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); } } }