// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Linq;
using System.Threading;
using System.Text;
namespace OpenTap
{
///
/// Implements device address discovery for VISA Keysight instruments and searches for device aliases.
///
class VisaDeviceDiscovery : IDeviceDiscovery
{
static bool IsVisaDiscoveryDisabled()
{
var env = Environment.GetEnvironmentVariable("OPENTAP_NO_VISA_DISCOVERY");
if (env == null)
return false;
return string.Equals(env, "True",StringComparison.OrdinalIgnoreCase) || string.Equals(env, "1");
}
static readonly bool isDisabled = IsVisaDiscoveryDisabled();
private static string[] GetDeviceAddresses()
{
var rm = GetResourceManager();
if (rm == Visa.VI_NULL) return new string[0];
int search, cnt;
StringBuilder sb = new StringBuilder(1024);
if (Visa.viFindRsrc(rm, "?*", out search, out cnt, sb) >= Visa.VI_SUCCESS)
{
string[] res = new string[cnt];
if (cnt > 0)
res[0] = sb.ToString();
int i = 1;
while ((cnt-- > 0) && (Visa.viFindNext(search, sb) == Visa.VI_SUCCESS))
{
res[i++] = sb.ToString();
}
if (i < res.Length)
Array.Resize(ref res, i);
Visa.viClose(search);
return res;
}
return new string[0];
}
static bool getAliasesBroken = false;
static string[] getAliases(int rm, string address)
{
if (getAliasesBroken) return Array.Empty();
using (var semaphore = new Semaphore(0, 1))
{
string[] result = Array.Empty();
TapThread.Start(() =>
{
var aliases = new StringBuilder(1024);
short intfType = 0;
short intfNum = 0;
if (Visa.viParseRsrcEx(rm, address, ref intfType, ref intfNum, null, null, aliases) ==
Visa.VI_SUCCESS)
{
if (aliases.Length != 0)
result = new[] {aliases.ToString()};
}
semaphore.Release();
});
if (!semaphore.WaitOne(5000))
{
getAliasesBroken = true;
log.Warning("VISA timed out when trying to get aliases. Skipping this from now on.");
}
return result;
}
}
/// Ensures updating device addresses is run from the same thread.
static WorkQueue detectAddressQueue = new WorkQueue(WorkQueue.Options.None, nameof(VisaDeviceDiscovery));
static string[] deviceAddresses = null; // not null after first detect.
static void updateDeviceAddresses()
{
try
{
var baseAddresses = GetDeviceAddresses();
// Now expand aliases
var rm = GetResourceManager();
if (rm == Visa.VI_NULL)
{
deviceAddresses = baseAddresses;
}
else
{
deviceAddresses = baseAddresses.SelectMany(addr => getAliases(rm, addr)).Concat(baseAddresses).ToArray();
}
}
catch
{
deviceAddresses = Array.Empty();
}
}
static TraceSource log = Log.CreateSource(nameof(VisaDeviceDiscovery));
public string[] DetectDeviceAddresses(DeviceAddressAttribute AddressType)
{
if (isDisabled) return Array.Empty();
using (var wait = new ManualResetEvent(false))
{
bool doUpdate = detectAddressQueue.QueueSize == 0;
detectAddressQueue.EnqueueWork(() =>
{
if (doUpdate)
updateDeviceAddresses();
if (!wait.SafeWaitHandle.IsClosed)
wait.Set();
});
if (!wait.WaitOne(deviceAddresses == null ? 2000 : 100))
{
// updateDeviceAddresses can hang forever in case of an error in the VISA installation.
// in this case wait for a bit and return no result. Consequent times wait a very short time.
Utils.ErrorOnce(log, log, "Detecting device addresses took longer than expected. This might be caused by a broken VISA installation.");
}
return deviceAddresses ?? Array.Empty();
}
}
/// Returns true if DeviceAddress is a VISA address.
/// .
///
public bool CanDetect(DeviceAddressAttribute DeviceAddress)
{
if (isDisabled) return false;
return (DeviceAddress is VisaAddressAttribute);
}
static int visa_resource;
static bool visa_tried_load;
// Required by .NET to catch AccessViolationException.
[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]
internal static int GetResourceManager()
{
try
{
if (visa_resource == Visa.VI_NULL && !visa_tried_load)
RaiseError2(Visa.viOpenDefaultRM(out visa_resource));
}
catch (Exception)
{
visa_tried_load = true;
}
return visa_resource;
}
private static void RaiseError2(int error)
{
if (error < 0)
throw new Exception("Visa failed with error " + error);
}
}
}