You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.9 KiB

2 months ago
using System.Collections;
namespace Housing.Services;
public sealed class VisaScpiClient : IScpiClient
{
private readonly dynamic _io;
private VisaScpiClient(dynamic io, string resourceName)
{
_io = io;
ResourceName = resourceName;
}
public string ResourceName { get; }
public static Task<VisaScpiClient> OpenAsync(string resourceName, int timeoutMilliseconds)
{
return Task.Run(() => Open(resourceName, timeoutMilliseconds));
}
public static Task<VisaScpiClient> OpenMatchingAsync(
string resourceName,
IEnumerable<string> idnMatches,
int timeoutMilliseconds)
{
return string.IsNullOrWhiteSpace(resourceName)
? Task.Run(() => OpenFirstMatching(idnMatches, timeoutMilliseconds))
: OpenAsync(resourceName, timeoutMilliseconds);
}
private static VisaScpiClient Open(string resourceName, int timeoutMilliseconds)
{
if (string.IsNullOrWhiteSpace(resourceName))
{
throw new InvalidOperationException("장비 VISA Resource 값을 확인하세요.");
}
var (resourceManagerType, formattedIoType) = GetVisaComTypes();
dynamic resourceManager = Activator.CreateInstance(resourceManagerType)!;
return OpenResource(resourceManager, formattedIoType, resourceName.Trim(), timeoutMilliseconds);
}
private static VisaScpiClient OpenFirstMatching(IEnumerable<string> idnMatches, int timeoutMilliseconds)
{
var tokens = NormalizeIdnMatches(idnMatches);
var (resourceManagerType, formattedIoType) = GetVisaComTypes();
dynamic resourceManager = Activator.CreateInstance(resourceManagerType)!;
var resourceNames = FindResourceNames(resourceManager);
if (resourceNames.Length == 0)
{
throw new InvalidOperationException("VISA 장비를 찾을 수 없습니다. USB/LAN 장비 연결과 VISA 드라이버 설치를 확인하세요.");
}
var checkedResources = new List<string>();
foreach (var resourceName in resourceNames)
{
VisaScpiClient? client = null;
try
{
client = OpenResource(resourceManager, formattedIoType, resourceName, timeoutMilliseconds);
var idn = client.Query("*IDN?");
checkedResources.Add($"{resourceName}: {idn}");
if (tokens.Length == 0 ||
tokens.Any(token => idn.Contains(token, StringComparison.OrdinalIgnoreCase)))
{
return client;
}
}
catch (Exception ex)
{
checkedResources.Add($"{resourceName}: {ex.Message}");
}
client?.Dispose();
}
var tokenMessage = tokens.Length == 0 ? string.Empty : $" IDN 키워드: {string.Join(", ", tokens)}.";
var checkedMessage = checkedResources.Count == 0
? string.Empty
: $" 확인 결과: {string.Join(" / ", checkedResources)}";
throw new InvalidOperationException($"VISA 장비 자동 검색 실패.{tokenMessage}{checkedMessage}");
}
private static (Type ResourceManagerType, Type FormattedIoType) GetVisaComTypes()
{
var resourceManagerType = Type.GetTypeFromProgID("VISA.GlobalRM") ??
Type.GetTypeFromProgID("VisaComLib.ResourceManager");
var formattedIoType = Type.GetTypeFromProgID("VISA.FormattedIO488") ??
Type.GetTypeFromProgID("VisaComLib.FormattedIO488");
if (resourceManagerType is null || formattedIoType is null)
{
throw new InvalidOperationException("VISA 라이브러리를 찾을 수 없습니다. Keysight IO Libraries 또는 NI-VISA 설치가 필요합니다.");
}
return (resourceManagerType, formattedIoType);
}
private static VisaScpiClient OpenResource(
dynamic resourceManager,
Type formattedIoType,
string resourceName,
int timeoutMilliseconds)
{
dynamic session = resourceManager.Open(resourceName);
try
{
session.Timeout = timeoutMilliseconds;
}
catch
{
// Some VISA COM sessions expose timeout through driver-specific properties only.
}
dynamic formattedIo = Activator.CreateInstance(formattedIoType)!;
formattedIo.IO = session;
return new VisaScpiClient(formattedIo, resourceName);
}
private static string[] FindResourceNames(dynamic resourceManager)
{
var resourceNames = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var pattern in new[] { "?*INSTR", "USB?*INSTR", "TCPIP?*INSTR", "GPIB?*INSTR", "ASRL?*INSTR" })
{
try
{
AddResourceNames(resourceNames, resourceManager.FindRsrc(pattern));
}
catch
{
// Resource discovery support differs by VISA COM implementation and backend.
}
}
return resourceNames.ToArray();
}
private static void AddResourceNames(ISet<string> resourceNames, object? value)
{
switch (value)
{
case null:
return;
case string resourceName:
AddResourceName(resourceNames, resourceName);
return;
case IEnumerable enumerable:
foreach (var item in enumerable)
{
AddResourceName(resourceNames, item?.ToString());
}
return;
default:
AddResourceName(resourceNames, value.ToString());
return;
}
}
private static void AddResourceName(ISet<string> resourceNames, string? resourceName)
{
if (!string.IsNullOrWhiteSpace(resourceName))
{
resourceNames.Add(resourceName.Trim());
}
}
private static string[] NormalizeIdnMatches(IEnumerable<string> idnMatches)
{
return idnMatches
.SelectMany(value => value.Split(',', ';'))
.Select(value => value.Trim())
.Where(value => !string.IsNullOrWhiteSpace(value))
.ToArray();
}
public Task SendAsync(string command)
{
return Task.Run(() => Send(command));
}
public Task<string> QueryAsync(string command)
{
return Task.Run(() => Query(command));
}
private void Send(string command)
{
_io.WriteString(command.Trim() + "\n", true);
}
private string Query(string command)
{
Send(command);
return ((string)_io.ReadString()).Trim();
}
public void Dispose()
{
try
{
_io.IO.Close();
}
catch
{
// Closing is best-effort because VISA COM implementations differ by vendor.
}
}
}