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.

194 lines
5.3 KiB

2 months ago
using System.IO.Ports;
using System.Text;
using System.Text.RegularExpressions;
namespace Housing.Services;
public sealed class SerialBoardClient : IDisposable
{
private readonly BoardHardwareSettings _settings;
private readonly SerialPort _serialPort;
public string PortName => _serialPort.PortName;
public SerialBoardClient(BoardHardwareSettings settings)
{
_settings = settings;
var portName = ResolvePortName(settings);
_serialPort = CreateSerialPort(portName, _settings);
}
public void Open()
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
}
}
public Task<string> SendCommandAsync(string command, int timeoutMilliseconds, bool waitForEndToken)
{
return Task.Run(() => SendCommand(command, timeoutMilliseconds, waitForEndToken));
}
private static SerialPort CreateSerialPort(string portName, BoardHardwareSettings settings)
{
return new SerialPort(portName, settings.BaudRate)
{
Encoding = Encoding.ASCII,
NewLine = "\r\n",
ReadTimeout = settings.ReadTimeout,
WriteTimeout = settings.ReadTimeout,
DtrEnable = true,
RtsEnable = true
};
}
private static string ResolvePortName(BoardHardwareSettings settings)
{
if (!IsAutoPort(settings.PortName))
{
return settings.PortName.Trim();
}
var portNames = GetOrderedPortNames();
if (portNames.Length == 0)
{
throw new InvalidOperationException("사용 가능한 COM 포트를 찾을 수 없습니다.");
}
foreach (var portName in portNames)
{
if (CanConnectBoard(portName, settings))
{
return portName;
}
}
throw new InvalidOperationException($"보드 자동 포트 검색 실패. 확인한 포트: {string.Join(", ", portNames)}");
}
private static bool IsAutoPort(string portName)
{
return string.IsNullOrWhiteSpace(portName) ||
string.Equals(portName.Trim(), "Auto", StringComparison.OrdinalIgnoreCase);
}
private static string[] GetOrderedPortNames()
{
return SerialPort.GetPortNames()
.OrderBy(GetPortNumber)
.ThenBy(portName => portName, StringComparer.OrdinalIgnoreCase)
.ToArray();
}
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 bool CanConnectBoard(string portName, BoardHardwareSettings settings)
{
try
{
using var probe = CreateSerialPort(portName, settings);
probe.Open();
probe.DiscardInBuffer();
probe.DiscardOutBuffer();
if (string.IsNullOrWhiteSpace(settings.ConnectCommand))
{
return true;
}
probe.Write(settings.ConnectCommand.Trim() + "\r\n");
var startedAt = DateTime.UtcNow;
while ((DateTime.UtcNow - startedAt).TotalMilliseconds < settings.ReadTimeout)
{
try
{
var line = probe.ReadLine();
if (IsSuccessLine(line))
{
return true;
}
if (!string.IsNullOrWhiteSpace(settings.EndToken) &&
line.Contains(settings.EndToken, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
catch (TimeoutException)
{
return false;
}
}
}
catch
{
return false;
}
return false;
}
private static bool IsSuccessLine(string line)
{
return string.Equals(line.Trim(), "Success", StringComparison.OrdinalIgnoreCase);
}
private string SendCommand(string command, int timeoutMilliseconds, bool waitForEndToken)
{
if (string.IsNullOrWhiteSpace(command))
{
return string.Empty;
}
Open();
_serialPort.ReadTimeout = Math.Max(500, timeoutMilliseconds);
_serialPort.DiscardInBuffer();
_serialPort.Write(command.Trim() + "\r\n");
var startedAt = DateTime.UtcNow;
var response = new StringBuilder();
while ((DateTime.UtcNow - startedAt).TotalMilliseconds < timeoutMilliseconds)
{
try
{
var line = _serialPort.ReadLine();
response.AppendLine(line);
if (!waitForEndToken)
{
break;
}
if (line.Contains(_settings.EndToken, StringComparison.OrdinalIgnoreCase))
{
break;
}
}
catch (TimeoutException)
{
break;
}
}
return response.ToString();
}
public void Dispose()
{
_serialPort.Dispose();
}
}