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.
260 lines
9.2 KiB
260 lines
9.2 KiB
using System;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using leak_test_project.Infrastructure;
|
|
using leak_test_project.Utils;
|
|
|
|
namespace leak_test_project.Services
|
|
{
|
|
/// <summary>
|
|
/// 신규 4253 보드와의 통신을 관리하는 서비스.
|
|
/// <end> 키워드를 기준으로 데이터를 수신하며, 상태 확인 및 ID 읽기 기능을 제공함.
|
|
/// </summary>
|
|
public class Board4253Service : IDisposable
|
|
{
|
|
private readonly ICommunication _communication;
|
|
private readonly StringBuilder _receiveBuffer = new StringBuilder();
|
|
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
|
|
private TaskCompletionSource<string> _responseTcs;
|
|
public int TimeoutMs { get; set; } = 5000; // 보드가 응답을 주는데 2초 이상 걸리므로 무조건 길게 대기
|
|
private bool _shouldBeConnected = false;
|
|
private System.Timers.Timer _reconnectTimer;
|
|
|
|
public event EventHandler<string> ErrorOccurred;
|
|
public event EventHandler<bool> ConnectionChanged;
|
|
|
|
public string LastResponse { get; private set; } = "";
|
|
|
|
public string GetLastBuffer()
|
|
{
|
|
string currentBuf = _receiveBuffer.ToString().Trim();
|
|
return string.IsNullOrEmpty(currentBuf) ? LastResponse : currentBuf;
|
|
}
|
|
|
|
public Board4253Service(ICommunication communication)
|
|
{
|
|
_communication = communication;
|
|
_communication.DataReceived += OnDataReceived;
|
|
_communication.ConnectionStatusChanged += (s, isConnected) => {
|
|
ConnectionChanged?.Invoke(this, isConnected);
|
|
if (!isConnected && _shouldBeConnected) StartReconnectTimer();
|
|
};
|
|
|
|
_reconnectTimer = new System.Timers.Timer(1000);
|
|
_reconnectTimer.AutoReset = false;
|
|
_reconnectTimer.Elapsed += (s, e) => {
|
|
if (_shouldBeConnected && !_communication.IsOpen)
|
|
{
|
|
if (!_communication.Open())
|
|
{
|
|
if (_shouldBeConnected) _reconnectTimer.Start();
|
|
}
|
|
}
|
|
else if (_shouldBeConnected)
|
|
{
|
|
_reconnectTimer.Start();
|
|
}
|
|
};
|
|
}
|
|
|
|
private void StartReconnectTimer()
|
|
{
|
|
if (_reconnectTimer != null && !_reconnectTimer.Enabled) _reconnectTimer.Start();
|
|
}
|
|
|
|
public bool Connect()
|
|
{
|
|
_shouldBeConnected = true;
|
|
bool opened = _communication.Open();
|
|
if (!opened) StartReconnectTimer();
|
|
return opened;
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
_shouldBeConnected = false;
|
|
_reconnectTimer?.Stop();
|
|
_communication.Close();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 4253 보드의 상태를 확인합니다.
|
|
/// </summary>
|
|
/// <returns>성공 여부</returns>
|
|
public async Task<bool> CheckStatusAsync(int channel = 1)
|
|
{
|
|
string response = await SendCommandAsync($"x00c_00{channel}101:owt28006727ea97c7801\r\n");
|
|
|
|
if (response == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (response.IndexOf("Success", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (response.IndexOf("Fail", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Success도 Fail도 없는 알 수 없는 응답인 경우
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 4253 보드로부터 16자리 ID를 읽어옵니다.
|
|
/// </summary>
|
|
/// <returns>16자리 ID 문자열, 실패 시 null</returns>
|
|
public async Task<string> ReadIdAsync(int channel = 1)
|
|
{
|
|
string response = await SendCommandAsync($"x00c_00{channel}101:ow2800326003e\r\n");
|
|
|
|
if (response == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (response.Contains("Fail"))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string id = ExtractId(response);
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
private string ExtractId(string response)
|
|
{
|
|
if (string.IsNullOrEmpty(response)) return null;
|
|
|
|
// 보드 응답에는 보낸 명령어가 에코되어 포함되므로, 줄바꿈으로 나누어 실제 데이터 라인을 찾음
|
|
string[] lines = response.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
foreach (var line in lines)
|
|
{
|
|
string trimmed = line.Trim();
|
|
if (trimmed.Contains("x00c_") || trimmed.IndexOf("<end>", StringComparison.OrdinalIgnoreCase) >= 0 || trimmed.IndexOf("Success", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
continue;
|
|
|
|
// 16자리 영숫자 ID 추출
|
|
var match = System.Text.RegularExpressions.Regex.Match(trimmed, @"[A-Za-z0-9]{16}");
|
|
if (match.Success) return match.Value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private async Task<string> SendCommandAsync(string command)
|
|
{
|
|
await _lock.WaitAsync();
|
|
try
|
|
{
|
|
int retryCount = 0;
|
|
while (retryCount <= 3)
|
|
{
|
|
if (!_communication.IsOpen)
|
|
{
|
|
if (!_communication.Open())
|
|
{
|
|
retryCount++;
|
|
await Task.Delay(300);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
_receiveBuffer.Clear();
|
|
LastResponse = "";
|
|
_responseTcs = new TaskCompletionSource<string>();
|
|
|
|
_communication.ClearBuffer(); // 이전에 남아있던 패킷 조각 완벽히 제거
|
|
|
|
_communication.Write(command);
|
|
|
|
using (var cts = new CancellationTokenSource(TimeoutMs))
|
|
{
|
|
cts.Token.Register(() => _responseTcs.TrySetCanceled());
|
|
try
|
|
{
|
|
return await _responseTcs.Task;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
retryCount++;
|
|
string currentBuffer = _receiveBuffer.ToString().Trim();
|
|
FileLogger.Log("WARNING", $"[Board4253] Timeout waiting for response (Retry {retryCount}/3). Command: {command.Trim()}, ReceivedSoFar: {currentBuffer}");
|
|
_receiveBuffer.Clear();
|
|
if (retryCount <= 3) await Task.Delay(300);
|
|
}
|
|
}
|
|
}
|
|
|
|
FileLogger.Log("ERROR", $"[Board4253] Failed to receive response after 3 retries: {command.Trim()}");
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
_responseTcs = null;
|
|
_lock.Release();
|
|
}
|
|
}
|
|
|
|
private void OnDataReceived(object sender, string data)
|
|
{
|
|
_receiveBuffer.Append(data);
|
|
string currentContent = _receiveBuffer.ToString();
|
|
|
|
bool isComplete = false;
|
|
|
|
if (currentContent.IndexOf("<end>", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
|
currentContent.IndexOf("Success", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
|
currentContent.IndexOf("Fail", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
isComplete = true;
|
|
}
|
|
else
|
|
{
|
|
// <end>가 오지 않았더라도, 명령어 에코가 아닌 줄에서 16자리 ID를 발견하면 즉시 완료 처리
|
|
string[] lines = currentContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
foreach (var line in lines)
|
|
{
|
|
string trimmed = line.Trim();
|
|
if (trimmed.Contains("x00c_")) continue;
|
|
|
|
var match = System.Text.RegularExpressions.Regex.Match(trimmed, @"[A-Za-z0-9]{16}");
|
|
if (match.Success)
|
|
{
|
|
isComplete = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isComplete)
|
|
{
|
|
if (_responseTcs != null && !_responseTcs.Task.IsCompleted)
|
|
{
|
|
LastResponse = currentContent.Trim();
|
|
_responseTcs.TrySetResult(currentContent);
|
|
}
|
|
_receiveBuffer.Clear();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Disconnect();
|
|
_communication.DataReceived -= OnDataReceived;
|
|
_reconnectTimer?.Dispose();
|
|
_lock.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|