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.
 
 
 

151 lines
5.1 KiB

using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
namespace Housing.Services;
public sealed class TcpStartSignalWatcher
{
private readonly StartSignalSettings _settings;
public TcpStartSignalWatcher(StartSignalSettings settings)
{
_settings = settings;
}
public async Task WaitForStartAsync(CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(_settings.Host))
{
throw new InvalidOperationException("Hardware.ini [StartSignal] Host 값을 LAN 장비 IP로 설정하세요.");
}
using var client = new TcpClient();
await ConnectAsync(client, cancellationToken);
using var stream = client.GetStream();
var stopwatch = Stopwatch.StartNew();
if (string.IsNullOrWhiteSpace(_settings.ReadCommand))
{
await WaitForEventMessageAsync(stream, stopwatch, cancellationToken);
return;
}
await WaitForPolledResponseAsync(stream, stopwatch, cancellationToken);
}
private async Task ConnectAsync(TcpClient client, CancellationToken cancellationToken)
{
try
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
if (_settings.TimeoutMilliseconds > 0)
{
timeout.CancelAfter(_settings.TimeoutMilliseconds);
}
await client.ConnectAsync(_settings.Host, _settings.Port, timeout.Token);
}
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
{
throw new TimeoutException($"LAN 시작 신호 장비 연결 시간 초과: {_settings.Host}:{_settings.Port}", ex);
}
catch (SocketException ex)
{
throw new InvalidOperationException($"LAN 시작 신호 장비 연결 실패: {_settings.Host}:{_settings.Port}, {ex.Message}", ex);
}
}
private async Task WaitForEventMessageAsync(NetworkStream stream, Stopwatch stopwatch, CancellationToken cancellationToken)
{
while (true)
{
ThrowIfTimedOut(stopwatch, "LAN 메시지 수신 대기");
var response = await ReadResponseAsync(stream, cancellationToken);
if (_settings.IsActive(response))
{
return;
}
}
}
private async Task WaitForPolledResponseAsync(NetworkStream stream, Stopwatch stopwatch, CancellationToken cancellationToken)
{
if (_settings.RequireInactiveBeforeStart)
{
while (_settings.IsActive(await QueryAsync(stream, cancellationToken)))
{
ThrowIfTimedOut(stopwatch, "기존 LAN 시작 상태 해제 대기");
await DelayAsync(cancellationToken);
}
}
while (!_settings.IsActive(await QueryAsync(stream, cancellationToken)))
{
ThrowIfTimedOut(stopwatch, "LAN 시작 응답 대기");
await DelayAsync(cancellationToken);
}
}
private async Task<string> QueryAsync(NetworkStream stream, CancellationToken cancellationToken)
{
var command = _settings.ReadCommand.Trim() + _settings.GetTerminator();
var bytes = Encoding.ASCII.GetBytes(command);
await stream.WriteAsync(bytes, cancellationToken);
await stream.FlushAsync(cancellationToken);
return await ReadResponseAsync(stream, cancellationToken);
}
private async Task<string> ReadResponseAsync(NetworkStream stream, CancellationToken cancellationToken)
{
var buffer = new byte[1024];
var response = new StringBuilder();
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeout.CancelAfter(_settings.ReadTimeoutMilliseconds);
try
{
while (true)
{
var count = await stream.ReadAsync(buffer, timeout.Token);
if (count == 0)
{
break;
}
var chunk = Encoding.ASCII.GetString(buffer, 0, count);
response.Append(chunk);
if (chunk.Contains('\n') || chunk.Contains('\r') || !stream.DataAvailable)
{
break;
}
}
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
return response.ToString();
}
return response.ToString();
}
private async Task DelayAsync(CancellationToken cancellationToken)
{
await Task.Delay(_settings.PollIntervalMilliseconds, cancellationToken);
}
private void ThrowIfTimedOut(Stopwatch stopwatch, string state)
{
if (_settings.TimeoutMilliseconds <= 0 ||
stopwatch.ElapsedMilliseconds <= _settings.TimeoutMilliseconds)
{
return;
}
throw new TimeoutException(
$"LAN 시작 신호 대기 시간 초과: {state}, 대상={_settings.Host}:{_settings.Port}, 제한={_settings.TimeoutMilliseconds}ms");
}
}