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.
140 lines
5.1 KiB
140 lines
5.1 KiB
|
4 weeks ago
|
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();
|
||
|
|
};
|
||
|
|
|
||
|
|
// 5초마다 연결 상태를 확인하고 재연결 시도
|
||
|
|
_reconnectTimer = new Timer(5000);
|
||
|
|
_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 void Connect()
|
||
|
|
{
|
||
|
|
_shouldBeConnected = true;
|
||
|
|
_communication.Open();
|
||
|
|
}
|
||
|
|
|
||
|
|
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"))
|
||
|
|
{
|
||
|
|
Console.WriteLine("[Service] Failed to send command: Communication channel closed.");
|
||
|
|
}
|
||
|
|
|
||
|
|
_sequence = (_sequence >= 255) ? 1 : _sequence + 1;
|
||
|
|
} catch (Exception ex) {
|
||
|
|
Console.WriteLine($"[Service] 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': // 일반 메시지
|
||
|
|
Console.WriteLine($"[C28 Message] {body}");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} catch (Exception ex) {
|
||
|
|
Console.WriteLine($"[Service] Error parsing received data: {ex.Message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose()
|
||
|
|
{
|
||
|
|
Disconnect();
|
||
|
|
_reconnectTimer?.Stop();
|
||
|
|
_reconnectTimer?.Dispose();
|
||
|
|
_reconnectTimer = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|