|
|
|
|
using System.IO.Ports;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
|
|
|
|
namespace Housing.Services;
|
|
|
|
|
|
|
|
|
|
public sealed class SerialBoardClient : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private const int SendOnlyDrainTimeoutMilliseconds = 1000;
|
|
|
|
|
private const int SendOnlyDrainIdleMilliseconds = 100;
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task SendOnlyAsync(string command)
|
|
|
|
|
{
|
|
|
|
|
return Task.Run(() =>
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(command))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Open();
|
|
|
|
|
_serialPort.DiscardInBuffer();
|
|
|
|
|
_serialPort.Write(command.Trim() + "\r\n");
|
|
|
|
|
DrainInputBuffer(SendOnlyDrainTimeoutMilliseconds, SendOnlyDrainIdleMilliseconds);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 = settings.DtrEnable,
|
|
|
|
|
RtsEnable = settings.RtsEnable
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrainInputBuffer(int timeoutMilliseconds, int idleMilliseconds)
|
|
|
|
|
{
|
|
|
|
|
var originalReadTimeout = _serialPort.ReadTimeout;
|
|
|
|
|
var startedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_serialPort.ReadTimeout = Math.Max(50, idleMilliseconds);
|
|
|
|
|
|
|
|
|
|
while ((DateTime.UtcNow - startedAt).TotalMilliseconds < timeoutMilliseconds)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var line = _serialPort.ReadLine();
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(_settings.EndToken) &&
|
|
|
|
|
line.Contains(_settings.EndToken, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (TimeoutException)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_serialPort.ReadTimeout = originalReadTimeout;
|
|
|
|
|
_serialPort.DiscardInBuffer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_serialPort.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|