using System.IO.Ports; using System.Text; using System.Text.RegularExpressions; namespace Housing.Services; public sealed class SerialScpiClient : IScpiClient { private readonly SerialPort _serialPort; public string PortName => _serialPort.PortName; public SerialScpiClient(string portName, int baudRate, int timeoutMilliseconds, IEnumerable? idnMatches = null) { var resolvedPortName = ResolvePortName(portName, baudRate, timeoutMilliseconds, idnMatches); _serialPort = CreateSerialPort(resolvedPortName, baudRate, timeoutMilliseconds); _serialPort.Open(); _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); } public Task SendAsync(string command) { return Task.Run(() => _serialPort.Write(command.Trim() + "\n")); } public Task QueryAsync(string command) { return Task.Run(() => { _serialPort.DiscardInBuffer(); _serialPort.Write(command.Trim() + "\n"); return _serialPort.ReadLine().Trim(); }); } private static SerialPort CreateSerialPort(string portName, int baudRate, int timeoutMilliseconds) { return new SerialPort(portName, baudRate) { Encoding = Encoding.ASCII, NewLine = "\n", ReadTimeout = timeoutMilliseconds, WriteTimeout = timeoutMilliseconds, DtrEnable = true, RtsEnable = true }; } private static string ResolvePortName(string portName, int baudRate, int timeoutMilliseconds, IEnumerable? idnMatches) { if (!IsAutoPort(portName)) { return portName.Trim(); } var portNames = SerialPort.GetPortNames() .OrderBy(GetPortNumber) .ThenBy(candidate => candidate, StringComparer.OrdinalIgnoreCase) .ToArray(); if (portNames.Length == 0) { throw new InvalidOperationException("사용 가능한 장비 COM 포트를 찾을 수 없습니다."); } var tokens = NormalizeIdnMatches(idnMatches); if (tokens.Length == 0 && portNames.Length == 1) { return portNames[0]; } foreach (var candidate in portNames) { if (MatchesIdn(candidate, baudRate, timeoutMilliseconds, tokens)) { return candidate; } } throw new InvalidOperationException($"SCPI 장비 자동 포트 검색 실패. 확인한 포트: {string.Join(", ", portNames)}"); } private static bool IsAutoPort(string portName) { return string.IsNullOrWhiteSpace(portName) || string.Equals(portName.Trim(), "Auto", StringComparison.OrdinalIgnoreCase); } private static int GetPortNumber(string portName) { var match = Regex.Match(portName, @"\d+"); return match.Success && int.TryParse(match.Value, out var number) ? number : int.MaxValue; } private static string[] NormalizeIdnMatches(IEnumerable? idnMatches) { return idnMatches? .SelectMany(value => value.Split(',', ';')) .Select(value => value.Trim()) .Where(value => !string.IsNullOrWhiteSpace(value)) .ToArray() ?? Array.Empty(); } private static bool MatchesIdn(string portName, int baudRate, int timeoutMilliseconds, string[] idnMatches) { try { using var probe = CreateSerialPort(portName, baudRate, timeoutMilliseconds); probe.Open(); probe.DiscardInBuffer(); probe.DiscardOutBuffer(); probe.Write("*IDN?\n"); var idn = probe.ReadLine().Trim(); return idnMatches.Length == 0 || idnMatches.Any(token => idn.Contains(token, StringComparison.OrdinalIgnoreCase)); } catch { return false; } } public void Dispose() { _serialPort.Dispose(); } }