using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using leak_test_project.Infrastructure;
using leak_test_project.Models;
using leak_test_project.Services;
using leak_test_project.Utils;
using leak_test_project.ViewModels.Core;
namespace leak_test_project.ViewModels
{
///
/// Home 화면의 비즈니스 로직을 담당하는 ViewModel.
/// 좌/우 채널 통신 관리, 측정값 표시, 판정 로직, 자동 시험 프로세스를 포함.
///
public class HomeViewModel : ObservableObject, IDisposable
{
private SentinelC28Service _sentinelService;
private SerialProvider _sentinelSerial;
private readonly Dispatcher _dispatcher;
// 자동 시험 프로세스 관련
private IDioBoard _dioBoard;
private IIdSensorService _leftSensor;
private IIdSensorService _rightSensor;
private SerialProvider _boardSerial;
private TestProcessService _testProcess;
private EventHandler _dioErrorHandler;
#region Left Channel Properties
private string _leftValue = "";
public string LeftValue { get => _leftValue; set => SetProperty(ref _leftValue, value); }
private string _leftJudgment = "";
public string LeftJudgment { get => _leftJudgment; set => SetProperty(ref _leftJudgment, value); }
private bool _isLeftOk;
public bool IsLeftOk { get => _isLeftOk; set => SetProperty(ref _isLeftOk, value); }
private string _leftStatus = "";
public string LeftStatus { get => _leftStatus; set => SetProperty(ref _leftStatus, value); }
private string _leftStartTime = "";
public string LeftStartTime { get => _leftStartTime; set => SetProperty(ref _leftStartTime, value); }
private string _leftActualStartTime = "";
private string _leftIcSn = "-";
public string LeftIcSn { get => _leftIcSn; set => SetProperty(ref _leftIcSn, value); }
private string _leftPcbBarcode = "-";
public string LeftPcbBarcode { get => _leftPcbBarcode; set => SetProperty(ref _leftPcbBarcode, value); }
private string _leftError = "";
public string LeftError { get => _leftError; set => SetProperty(ref _leftError, value); }
#endregion
#region Right Channel Properties
private string _rightValue = "";
public string RightValue { get => _rightValue; set => SetProperty(ref _rightValue, value); }
private string _rightJudgment = "";
public string RightJudgment { get => _rightJudgment; set => SetProperty(ref _rightJudgment, value); }
private bool _isRightOk;
public bool IsRightOk { get => _isRightOk; set => SetProperty(ref _isRightOk, value); }
private string _rightStatus = "";
public string RightStatus { get => _rightStatus; set => SetProperty(ref _rightStatus, value); }
private string _rightStartTime = "";
public string RightStartTime { get => _rightStartTime; set => SetProperty(ref _rightStartTime, value); }
private string _rightActualStartTime = "";
private string _rightIcSn = "-";
public string RightIcSn { get => _rightIcSn; set => SetProperty(ref _rightIcSn, value); }
private string _rightPcbBarcode = "-";
public string RightPcbBarcode { get => _rightPcbBarcode; set => SetProperty(ref _rightPcbBarcode, value); }
private string _rightError = "";
public string RightError { get => _rightError; set => SetProperty(ref _rightError, value); }
#endregion
#region Spec Properties
private string _specUL = "";
public string SpecUL { get => _specUL; set => SetProperty(ref _specUL, value); }
private string _specLL = "";
public string SpecLL { get => _specLL; set => SetProperty(ref _specLL, value); }
#endregion
private readonly IDialogService _dialogService;
public HomeViewModel(IDioBoard dioBoard, IDialogService dialogService = null)
{
_dialogService = dialogService ?? new DefaultDialogService();
_dioBoard = dioBoard;
_dispatcher = Dispatcher.CurrentDispatcher;
var config = ConfigManager.Current;
UpdateSpecFromConfig(config);
InitializeCommunication(config);
InitializeTestProcess(config);
ConfigManager.ConfigChanged += OnConfigChanged;
}
private void OnConfigChanged(object sender, EventArgs e)
{
_dispatcher.Invoke(() => {
var newConfig = ConfigManager.Current;
UpdateSpecFromConfig(newConfig);
ApplyConfig();
});
}
private void UpdateSpecFromConfig(AppConfig config)
{
SpecUL = config.SpecUL.ToString("F2");
SpecLL = config.SpecLL.ToString("F2");
}
public void ApplyConfig()
{
CleanupAll();
// 통신 재시작 전 기존 오류 및 상태 메시지 초기화
LeftError = "";
RightError = "";
LeftStatus = "통신 대기 중";
RightStatus = "통신 대기 중";
var config = ConfigManager.Current;
InitializeCommunication(config);
InitializeTestProcess(config);
}
private void CleanupAll()
{
// 1. 시험 프로세스 정지 (스레드 종료 및 이벤트 해제)
_testProcess?.Dispose();
_testProcess = null;
// 2. DIO 에러 핸들러 해제 (중복 구독 방지)
if (_dioErrorHandler != null && _dioBoard != null)
{
_dioBoard.ErrorOccurred -= _dioErrorHandler;
_dioErrorHandler = null;
}
// 3. 4251 보드 시리얼 포트 및 센서 서비스 해제
_leftSensor?.Dispose();
_rightSensor?.Dispose();
_boardSerial?.Dispose();
_leftSensor = null;
_rightSensor = null;
_boardSerial = null;
// 4. Sentinel C28 해제
_sentinelService?.Disconnect();
_sentinelSerial?.Dispose();
_sentinelService = null;
_sentinelSerial = null;
}
private void InitializeCommunication(AppConfig config)
{
// Sentinel C28 (Leak Sensor) - 단일 포트 사용
_sentinelSerial = new SerialProvider(config.SensorPort, config.SensorBaudRate);
_sentinelService = new SentinelC28Service(_sentinelSerial);
_sentinelService.RawDataReceived += (s, data) => {
System.Diagnostics.Debug.WriteLine($"[SENTINEL RAW] {data}");
};
_sentinelService.OnStreamingParsed += (s, data) => UpdateMeasurement(data);
if (!_sentinelService.Connect())
{
string msg = $"Sentinel C28 포트 연결 실패 ({config.SensorPort})";
LeftError = msg;
RightError = msg;
}
}
private void InitializeTestProcess(AppConfig config)
{
// DIO 보드 초기화 (MainViewModel에서 생성된 보드 사용)
if (_dioBoard == null) return;
// DIO 보드 에러 구독 (중복 방지를 위해 핸들러 참조 보관)
_dioErrorHandler = (s, msg) => _dispatcher.Invoke(() => {
LeftError = msg;
RightError = msg;
AppendLog(true, $"[DIO Board Error] {msg}");
});
_dioBoard.ErrorOccurred += _dioErrorHandler;
// ID 센서 서비스 (4251 보드 단일 통신)
_boardSerial = new SerialProvider(config.Board4251Port, config.Board4251BaudRate);
var sharedService = new Board4251Service(_boardSerial) { TimeoutMs = config.Board4251Timeout };
_leftSensor = new Board4251SensorService(sharedService, 0);
_rightSensor = new Board4251SensorService(sharedService, 1);
if (!_leftSensor.Connect())
{
string msg = $"4251 보드 포트 연결 실패 ({config.Board4251Port})";
LeftError = string.IsNullOrEmpty(LeftError) ? msg : $"{LeftError}\n{msg}";
}
if (!_rightSensor.Connect())
{
string msg = $"4251 보드 포트 연결 실패 ({config.Board4251Port})";
RightError = string.IsNullOrEmpty(RightError) ? msg : $"{RightError}\n{msg}";
}
_leftSensor.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => LeftStatus = msg);
_leftSensor.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
LeftError = msg;
AppendLog(true, $"[4251 Error] {msg}");
});
_rightSensor.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => RightStatus = msg);
_rightSensor.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
RightError = msg;
AppendLog(false, $"[4251 Error] {msg}");
});
// 자동 시험 프로세스
_testProcess = new TestProcessService(_dioBoard, _leftSensor, _rightSensor, _sentinelService);
_testProcess.ProgressChanged += (s, e) => _dispatcher.Invoke(() =>
{
if (e.TestIndex == 0) LeftStatus = e.Message;
else RightStatus = e.Message;
});
_testProcess.ErrorOccurred += (s, e) => _dispatcher.Invoke(() =>
{
if (e.TestIndex == 0)
{
LeftStatus = "오류 발생";
LeftError = e.Message;
AppendLog(true, $"[ERROR] {e.Message}");
}
else
{
RightStatus = "오류 발생";
RightError = e.Message;
AppendLog(false, $"[ERROR] {e.Message}");
}
});
_testProcess.ResultClearRequested += (s, testIndex) => _dispatcher.Invoke(() =>
{
if (testIndex == 0) ClearLeftResult();
else ClearRightResult();
});
_testProcess.SensorReadComplete += (s, args) => _dispatcher.Invoke(() =>
{
var d = args.Data;
if (args.TestIndex == 0)
{
LeftIcSn = d.ID;
LeftPcbBarcode = "-";
}
else
{
RightIcSn = d.ID;
RightPcbBarcode = "-";
}
});
_testProcess.BarcodeMapped += (s, args) => _dispatcher.Invoke(() =>
{
if (args.TestIndex == 0)
{
LeftIcSn = args.IcSn;
LeftPcbBarcode = args.PcbBarcode;
}
else
{
RightIcSn = args.IcSn;
RightPcbBarcode = args.PcbBarcode;
}
});
_testProcess.TestCompleted += (s, e) => _dispatcher.Invoke(() =>
{
string endTimeStr = e.ProductionDate.ToString("yyyy-MM-dd HH:mm:ss");
if (e.TestIndex == 0)
{
LeftValue = e.MeasuredValue;
LeftJudgment = e.Judgment;
IsLeftOk = e.Judgment == "OK";
LeftStatus = "시험 완료";
LeftStartTime = $"{_leftActualStartTime} / {endTimeStr}";
LeftIcSn = e.SensorData.ID;
LeftPcbBarcode = e.PcbBarcode;
}
else
{
RightValue = e.MeasuredValue;
RightJudgment = e.Judgment;
IsRightOk = e.Judgment == "OK";
RightStatus = "시험 완료";
RightStartTime = $"{_rightActualStartTime} / {endTimeStr}";
RightIcSn = e.SensorData.ID;
RightPcbBarcode = e.PcbBarcode;
}
// SPEC 교차 검증 불일치 경고
if (e.SpecMismatch)
{
string side = e.TestIndex == 0 ? "LEFT" : "RIGHT";
string msg = $"SPEC 불일치 - 프로그램: {e.Judgment}, 센서: {e.SensorJudgment}";
if (e.TestIndex == 0) LeftError = msg;
else RightError = msg;
_dialogService.ShowWarning("프로그램 스팩과 센서의 스팩이 서로 맞지 않습니다.", "SPEC 교차 검증 경고");
}
});
_testProcess.Start();
}
private void ClearLeftResult()
{
LeftValue = ""; LeftJudgment = ""; IsLeftOk = false;
_leftActualStartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
LeftStartTime = $"{_leftActualStartTime} / -";
LeftIcSn = "-";
LeftPcbBarcode = "-";
LeftError = "";
}
private void ClearRightResult()
{
RightValue = ""; RightJudgment = ""; IsRightOk = false;
_rightActualStartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
RightStartTime = $"{_rightActualStartTime} / -";
RightIcSn = "-";
RightPcbBarcode = "-";
RightError = "";
}
private void UpdateMeasurement(ParsedData data)
{
_dispatcher.Invoke(() => {
// ChannelNo(C01=LEFT, C02=RIGHT)에 따라 데이터 라우팅
if (data.ChannelNo == "C01" || data.ChannelNo == "1")
{
LeftValue = data.MeasuredValueString;
}
else if (data.ChannelNo == "C02" || data.ChannelNo == "2")
{
RightValue = data.MeasuredValueString;
}
else
{
// 레거시 포맷 등 채널 정보가 없을 경우, 현재 시험 중인 채널에만 업데이트
if (_testProcess != null)
{
if (_testProcess.ActiveTestIndex == 0)
{
LeftValue = data.MeasuredValueString;
}
else if (_testProcess.ActiveTestIndex == 1)
{
RightValue = data.MeasuredValueString;
}
}
}
});
}
public async System.Threading.Tasks.Task TestReadIdAsync(int testIndex)
{
if (_testProcess != null)
{
await _testProcess.ExecuteSensorTestAsync(testIndex);
}
}
private const int MaxLogLines = 500;
private void AppendLog(bool isLeft, string message)
{
// 이 기능은 이제 Status/Error 필드로 대체되거나 파일 로그로 대체됨
// 현재는 UI에서 제거되었으므로 Debug 출력만 남김
System.Diagnostics.Debug.WriteLine($"[LOG][{(isLeft ? "LEFT" : "RIGHT")}] {message}");
_dispatcher.Invoke(() => {
if (message.Contains("ERROR") || message.Contains("Error"))
{
if (isLeft) LeftError = message;
else RightError = message;
}
else
{
if (isLeft) LeftStatus = message;
else RightStatus = message;
}
});
}
public void Dispose()
{
ConfigManager.ConfigChanged -= OnConfigChanged;
CleanupAll();
}
}
}