using System;
using System.Threading;
using leak_test_project.Infrastructure;
using leak_test_project.Models;
using leak_test_project.Utils;
namespace leak_test_project.Services
{
///
/// 시험 진행 상황 이벤트 인자.
///
public class TestProgressEventArgs : EventArgs
{
public int TestIndex { get; set; } // 0=LEFT, 1=RIGHT
public string Message { get; set; }
public string ColorHint { get; set; } // LightSeaGreen, LightYellow, LightBlue
}
///
/// 시험 결과 이벤트 인자.
///
public class TestResultEventArgs : EventArgs
{
public int TestIndex { get; set; }
public SensorIdData SensorData { get; set; }
public string MeasuredValue { get; set; }
public string Judgment { get; set; } // 프로그램 판정 (OK/NG)
public string SensorJudgment { get; set; } // 센서 자체 판정
public bool SpecMismatch { get; set; } // SPEC 교차 검증 불일치 여부
}
///
/// 자동 시험 프로세스를 관리하는 서비스.
/// 레거시 ClsTester.ProcessProc()를 새 아키텍처로 포팅.
/// DIO 시작 신호 대기 → ZMDI 센서 읽기 → LEAK 시험 → 결과 출력 사이클.
///
public class TestProcessService : IDisposable
{
private readonly IDioBoard _dioBoard;
private readonly ZmdiSensorService _leftSensor;
private readonly ZmdiSensorService _rightSensor;
private readonly SentinelC28Service _sentinelService;
private Thread _leftThread;
private Thread _rightThread;
private volatile bool _leftTestStart = false;
private volatile bool _rightTestStart = false;
private volatile bool _running = true;
// LEAK 시험 결과를 수신하기 위한 필드
private ParsedData _leftResult;
private ParsedData _rightResult;
private readonly ManualResetEventSlim _leftResultReady = new ManualResetEventSlim(false);
private readonly ManualResetEventSlim _rightResultReady = new ManualResetEventSlim(false);
/// 시험 진행 상황 알림
public event EventHandler ProgressChanged;
/// 시험 완료 결과 알림
public event EventHandler TestCompleted;
/// 오류 메시지 알림
public event EventHandler ErrorOccurred;
/// 센서 ID 판독 완료 알림
public event EventHandler<(int TestIndex, SensorIdData Data)> SensorReadComplete;
/// 결과 초기화 요청 알림
public event EventHandler ResultClearRequested;
public TestProcessService(
IDioBoard dioBoard,
ZmdiSensorService leftSensor,
ZmdiSensorService rightSensor,
SentinelC28Service sentinelService)
{
_dioBoard = dioBoard;
_leftSensor = leftSensor;
_rightSensor = rightSensor;
_sentinelService = sentinelService;
// DIO START 신호 이벤트 연결
_dioBoard.InputChanged += OnDioInputChanged;
// C28 최종 결과 수신 이벤트 연결 (통합 서비스)
_sentinelService.OnFinalResultParsed += OnSentinelFinalResult;
}
/// 자동 시험 프로세스를 시작합니다.
public void Start()
{
_running = true;
_leftThread = new Thread(() => ProcessProc(0)) { IsBackground = true, Name = "TestProc_LEFT" };
_rightThread = new Thread(() => ProcessProc(1)) { IsBackground = true, Name = "TestProc_RIGHT" };
_leftThread.Start();
_rightThread.Start();
Console.WriteLine("[TestProcess] Started LEFT and RIGHT test threads.");
}
/// 시뮬레이션용: 수동으로 시험 시작 트리거
public void TriggerTestStart(int testIndex)
{
if (testIndex == 0) _leftTestStart = true;
else _rightTestStart = true;
}
private void OnDioInputChanged(object sender, DioEventArgs e)
{
// DIO 시작 신호 감지 (OFF→ON)
if (e.PointName == "LEFT_START" && e.NewValue)
{
// OK/NG 출력 초기화
_dioBoard.WriteOutput("LEFT_OK", false);
_dioBoard.WriteOutput("LEFT_NG", false);
_leftTestStart = true;
}
else if (e.PointName == "RIGHT_START" && e.NewValue)
{
_dioBoard.WriteOutput("RIGHT_OK", false);
_dioBoard.WriteOutput("RIGHT_NG", false);
_rightTestStart = true;
}
}
///
/// 시험 프로세스 메인 루프 (레거시 ClsTester.ProcessProc 포팅).
/// 백그라운드 스레드에서 실행됩니다.
///
private void ProcessProc(int testIndex)
{
bool isLeft = (testIndex == 0);
string side = isLeft ? "LEFT" : "RIGHT";
while (_running)
{
Thread.Sleep(2);
// 시작 신호 대기
bool shouldStart = isLeft ? _leftTestStart : _rightTestStart;
if (!shouldStart) continue;
// 플래그 초기화
if (isLeft) _leftTestStart = false;
else _rightTestStart = false;
NotifyProgress(testIndex, "시험 시작", "LightSeaGreen");
ResultClearRequested?.Invoke(this, testIndex);
// === 1단계: 센서 정보 읽기 ===
NotifyProgress(testIndex, "센서 정보 읽는 중", "LightSeaGreen");
var sensor = isLeft ? _leftSensor : _rightSensor;
var sensorData = sensor.ReadSensor();
if (sensorData != null)
{
SensorReadComplete?.Invoke(this, (testIndex, sensorData));
}
else
{
// 센서 읽기 실패해도 시험은 계속 진행
sensorData = new SensorIdData { ID = "-", PrevResult = "F" };
SensorReadComplete?.Invoke(this, (testIndex, sensorData));
}
// === 2단계: 불량 제품 필터링 ===
if (sensorData.PrevResult != "F")
{
NotifyError(testIndex, "불량 제품 투입 (이전 결과: " + sensorData.PrevResult + ")");
continue;
}
// === 3단계: LEAK 시험 수행 ===
NotifyProgress(testIndex, "LEAK 시험중", "LightYellow");
// 결과 대기 리셋
var resultReady = isLeft ? _leftResultReady : _rightResultReady;
resultReady.Reset();
if (isLeft) _leftResult = null;
else _rightResult = null;
// C28에서 결과를 수신할 때까지 최대 30초 대기 (레거시와 동일)
bool received = resultReady.Wait(TimeSpan.FromSeconds(30));
if (!received)
{
NotifyError(testIndex, "LEAK 센서 통신 타임아웃 (30초)");
_dioBoard.WriteOutput(side + "_NG", true);
continue;
}
var leakResult = isLeft ? _leftResult : _rightResult;
if (leakResult == null)
{
NotifyError(testIndex, "LEAK 센서 데이터 오류");
_dioBoard.WriteOutput(side + "_NG", true);
continue;
}
// === 4단계: 판정 ===
double specUL = ConfigManager.Current.SpecUL;
double specLL = ConfigManager.Current.SpecLL;
string programJudgment = SentinelParser.EvaluateJudgment(leakResult.MeasuredValue, specUL, specLL);
string sensorJudgment = leakResult.SensorJudgment ?? leakResult.Judgment ?? "-";
// === 5단계: SPEC 교차 검증 ===
bool specMismatch = false;
string normalizedProgram = (programJudgment == "OK") ? "A" : "R";
string normalizedSensor = sensorJudgment;
// 센서 판정이 Accept/Reject 형식인 경우
if (sensorJudgment == "OK" || sensorJudgment == "A") normalizedSensor = "A";
else if (sensorJudgment == "NG" || sensorJudgment == "R") normalizedSensor = "R";
if (normalizedProgram != normalizedSensor && normalizedSensor != "-")
{
specMismatch = true;
Console.WriteLine($"[TestProcess] {side} SPEC Mismatch! Program={programJudgment}, Sensor={sensorJudgment}");
}
// === 6단계: 로그 기록 ===
var inspectData = new InspectData
{
InspectDate = DateTime.Now.ToString("yyyy-MM-dd"),
InspectTime = DateTime.Now.ToString("HH:mm:ss"),
Channel = side,
ProductId = sensorData.ID,
MeasuredValue = leakResult.MeasuredValue.ToString("F3"),
Judgment = programJudgment,
Mode = sensorData.McLine == "0" ? "개발" : "양산",
LineNo = sensorData.LineNo,
ProductType = sensorData.Item,
SpecUL = specUL.ToString("F2"),
SpecLL = specLL.ToString("F2"),
Retest = "N"
};
FileLogger.LogInspectData(inspectData);
// === 7단계: DIO 출력 ===
if (programJudgment == "OK")
{
_dioBoard.WriteOutput(side + "_OK", true);
}
else
{
_dioBoard.WriteOutput(side + "_NG", true);
}
// 완료 알림
NotifyProgress(testIndex, "시험 완료", "LightBlue");
TestCompleted?.Invoke(this, new TestResultEventArgs
{
TestIndex = testIndex,
SensorData = sensorData,
MeasuredValue = leakResult.MeasuredValue.ToString("F3"),
Judgment = programJudgment,
SensorJudgment = sensorJudgment,
SpecMismatch = specMismatch
});
}
}
private void OnSentinelFinalResult(object sender, ParsedData data)
{
// ChannelNo(C01=LEFT, C02=RIGHT)에 따라 결과 라우팅
if (data.ChannelNo == "C01" || data.ChannelNo == "1")
{
_leftResult = data;
_leftResultReady.Set();
}
else if (data.ChannelNo == "C02" || data.ChannelNo == "2")
{
_rightResult = data;
_rightResultReady.Set();
}
}
private void NotifyProgress(int testIndex, string message, string colorHint)
{
ProgressChanged?.Invoke(this, new TestProgressEventArgs
{
TestIndex = testIndex,
Message = message,
ColorHint = colorHint
});
}
private void NotifyError(int testIndex, string message)
{
ErrorOccurred?.Invoke(this, new TestProgressEventArgs
{
TestIndex = testIndex,
Message = message,
ColorHint = "Red"
});
}
public void Dispose()
{
_running = false;
_leftResultReady.Set(); // 대기 중인 스레드 해제
_rightResultReady.Set();
_dioBoard.InputChanged -= OnDioInputChanged;
_sentinelService.OnFinalResultParsed -= OnSentinelFinalResult;
_leftResultReady?.Dispose();
_rightResultReady?.Dispose();
Console.WriteLine("[TestProcess] Disposed.");
}
}
}