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.
420 lines
16 KiB
420 lines
16 KiB
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
|
|
{
|
|
/// <summary>
|
|
/// Home 화면의 비즈니스 로직을 담당하는 ViewModel.
|
|
/// 좌/우 채널 통신 관리, 측정값 표시, 판정 로직, 자동 시험 프로세스를 포함.
|
|
/// </summary>
|
|
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<string> _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();
|
|
}
|
|
}
|
|
}
|
|
|