리크 테스트 gui
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.

142 lines
5.3 KiB

using System;
using System.Timers;
using leak_test_project.Infrastructure;
using leak_test_project.Utils;
namespace leak_test_project.Services
{
/// <summary>
/// Sentinel C28 기기와의 통신 프로토콜을 관리하는 서비스.
/// 자동 재연결(Auto-Reconnect) 및 예외 처리 포함.
/// </summary>
public class SentinelC28Service : IDisposable
{
private readonly ICommunication _communication;
private int _sequence = 1;
private Timer _reconnectTimer;
private bool _shouldBeConnected = false;
public event EventHandler<string> RawDataReceived;
public event EventHandler<string> ResultReceived;
public event EventHandler<string> StreamingReceived;
public event EventHandler<bool> ConnectionChanged;
/// <summary> 파싱된 최종 검사 결과 알림 </summary>
public event EventHandler<Models.ParsedData> OnFinalResultParsed;
/// <summary> 파싱된 실시간 스트리밍 데이터 알림 </summary>
public event EventHandler<Models.ParsedData> OnStreamingParsed;
public SentinelC28Service(ICommunication communication)
{
_communication = communication;
_communication.DataReceived += OnDataReceived;
_communication.ConnectionStatusChanged += (s, isConnected) => {
ConnectionChanged?.Invoke(this, isConnected);
if (!isConnected && _shouldBeConnected) StartReconnectTimer();
};
// 1초(1000ms)마다 연결 상태를 확인하고 재연결 시도
_reconnectTimer = new Timer(1000);
_reconnectTimer.AutoReset = false; // 재진입 방지
_reconnectTimer.Elapsed += (s, e) => {
if (_shouldBeConnected && !_communication.IsOpen)
{
Console.WriteLine($"[Service] Attempting to reconnect to {_communication.Name}...");
if (!_communication.Open())
{
// 실패 시 다시 타이머 시작
if (_shouldBeConnected) _reconnectTimer.Start();
}
}
else if (_shouldBeConnected)
{
_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();
}
private void StartReconnectTimer()
{
if (!_reconnectTimer.Enabled) _reconnectTimer.Start();
}
public void SendCommand(string command, string dataTypeCode)
{
if (!_communication.IsOpen) return;
try {
string sequenceHex = _sequence.ToString("X2");
string lengthHex = command.Length.ToString("X3");
string payload = $"{sequenceHex}{lengthHex} {dataTypeCode}\t{command}";
string crc = SentinelCrc8.CalculateHex(payload);
if (!_communication.Write($"{crc}{payload}\r\n"))
{
FileLogger.Log("ERROR", "[SentinelC28] Failed to send command: Communication channel closed.");
}
_sequence = (_sequence >= 255) ? 1 : _sequence + 1;
} catch (Exception ex) {
FileLogger.Log("ERROR", $"[SentinelC28] Error sending command: {ex.Message}");
}
}
private void OnDataReceived(object sender, string rawData)
{
try {
RawDataReceived?.Invoke(this, rawData);
if (string.IsNullOrWhiteSpace(rawData)) return;
// 헤더 분석 및 본문 추출
string body = SentinelParser.ExtractBody(rawData, out char typeCode);
switch (typeCode)
{
case 'R': // 최종 결과 (Result Value)
ResultReceived?.Invoke(this, rawData);
var finalParsed = SentinelParser.ParseFinalResult(rawData);
OnFinalResultParsed?.Invoke(this, finalParsed);
break;
case 'S': // 스트리밍 데이터 (Streaming Value)
StreamingReceived?.Invoke(this, rawData);
var streamParsed = SentinelParser.ParseStreamingValue(rawData);
OnStreamingParsed?.Invoke(this, streamParsed);
break;
case 'M': // 일반 메시지
FileLogger.Log("INFO", $"[SentinelC28 Message] {body}");
break;
}
} catch (Exception ex) {
FileLogger.Log("ERROR", $"[SentinelC28] Error parsing received data: {ex.Message}");
}
}
public void Dispose()
{
Disconnect();
_reconnectTimer?.Stop();
_reconnectTimer?.Dispose();
_reconnectTimer = null;
}
}
}