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

498 lines
20 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 _leftZmdi;
private IIdSensorService _rightZmdi;
private SerialProvider _leftZmdiSerial;
private SerialProvider _rightZmdiSerial;
private TestProcessService _testProcess;
#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 _leftId = "";
public string LeftId { get => _leftId; set => SetProperty(ref _leftId, value); }
private string _leftLowId = "";
public string LeftLowId { get => _leftLowId; set => SetProperty(ref _leftLowId, value); }
private string _leftDate = "";
public string LeftDate { get => _leftDate; set => SetProperty(ref _leftDate, value); }
private string _leftSerial_ = "";
public string LeftSerialNo { get => _leftSerial_; set => SetProperty(ref _leftSerial_, value); }
private string _leftMcLine = "";
public string LeftMcLine { get => _leftMcLine; set => SetProperty(ref _leftMcLine, value); }
private string _leftItem = "";
public string LeftItem { get => _leftItem; set => SetProperty(ref _leftItem, 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 _rightId = "";
public string RightId { get => _rightId; set => SetProperty(ref _rightId, value); }
private string _rightLowId = "";
public string RightLowId { get => _rightLowId; set => SetProperty(ref _rightLowId, value); }
private string _rightDate = "";
public string RightDate { get => _rightDate; set => SetProperty(ref _rightDate, value); }
private string _rightSerial_ = "";
public string RightSerialNo { get => _rightSerial_; set => SetProperty(ref _rightSerial_, value); }
private string _rightMcLine = "";
public string RightMcLine { get => _rightMcLine; set => SetProperty(ref _rightMcLine, value); }
private string _rightItem = "";
public string RightItem { get => _rightItem; set => SetProperty(ref _rightItem, 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
public HomeViewModel(IDioBoard dioBoard)
{
_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. ZMDI 시리얼 포트 및 서비스 해제
_leftZmdi?.Dispose();
_rightZmdi?.Dispose();
_leftZmdiSerial?.Dispose();
_rightZmdiSerial?.Dispose();
_leftZmdi = null;
_rightZmdi = null;
_leftZmdiSerial = null;
_rightZmdiSerial = null;
// 3. 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);
_sentinelService.OnFinalResultParsed += (s, data) => ProcessFinalResult(data);
if (!_sentinelService.Connect())
{
string msg = $"리크 센서 포트 연결 실패 ({config.SensorPort})";
LeftError = msg;
RightError = msg;
}
}
private void InitializeTestProcess(AppConfig config)
{
// DIO 보드 초기화 (MainViewModel에서 생성된 보드 사용)
if (_dioBoard == null) return;
// DIO 보드 에러 구독
_dioBoard.ErrorOccurred += (s, msg) => _dispatcher.Invoke(() => {
LeftError = msg;
RightError = msg;
AppendLog(true, $"[DIO Board Error] {msg}");
});
// ID 센서 서비스 (LEFT/RIGHT)
if (config.SelectedIdSensor == IdSensorType.Board4253)
{
_leftZmdiSerial = new SerialProvider(config.Board4253Port, config.Board4253BaudRate);
var sharedService = new Board4253Service(_leftZmdiSerial) { TimeoutMs = config.Board4253Timeout };
_leftZmdi = new Board4253SensorService(sharedService, 0);
_rightZmdi = new Board4253SensorService(sharedService, 1);
}
else
{
_leftZmdiSerial = new SerialProvider(config.LeftPort, config.ZmdiBaudRate);
_rightZmdiSerial = new SerialProvider(config.RightPort, config.ZmdiBaudRate);
_leftZmdi = new ZmdiSensorService(_leftZmdiSerial, 0);
_rightZmdi = new ZmdiSensorService(_rightZmdiSerial, 1);
}
string sensorName = config.SelectedIdSensor == IdSensorType.Board4253 ? "4253 보드" : "ZMDI 센서";
string logPrefix = config.SelectedIdSensor == IdSensorType.Board4253 ? "[4253 Error]" : "[ZMDI Error]";
if (!_leftZmdi.Connect())
{
string port = config.SelectedIdSensor == IdSensorType.Board4253 ? config.Board4253Port : config.LeftPort;
string msg = $"{sensorName} 포트 연결 실패 ({port})";
LeftError = string.IsNullOrEmpty(LeftError) ? msg : $"{LeftError}\n{msg}";
}
if (!_rightZmdi.Connect())
{
string port = config.SelectedIdSensor == IdSensorType.Board4253 ? config.Board4253Port : config.RightPort;
string msg = $"{sensorName} 포트 연결 실패 ({port})";
RightError = string.IsNullOrEmpty(RightError) ? msg : $"{RightError}\n{msg}";
}
_leftZmdi.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => LeftStatus = msg);
_leftZmdi.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
LeftError = msg;
AppendLog(true, $"{logPrefix} {msg}");
});
_rightZmdi.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => RightStatus = msg);
_rightZmdi.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
RightError = msg;
AppendLog(false, $"{logPrefix} {msg}");
});
// 자동 시험 프로세스
_testProcess = new TestProcessService(_dioBoard, _leftZmdi, _rightZmdi, _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)
{
LeftId = d.ID;
LeftLowId = d.LowID;
LeftDate = d.Year > 0 ? $"{d.Year}/{d.Month}/{d.Day}" : "";
LeftSerialNo = d.Serial;
LeftMcLine = d.McLine;
LeftItem = d.Item;
}
else
{
RightId = d.ID;
RightLowId = d.LowID;
RightDate = d.Year > 0 ? $"{d.Year}/{d.Month}/{d.Day}" : "";
RightSerialNo = d.Serial;
RightMcLine = d.McLine;
RightItem = d.Item;
}
});
_testProcess.TestCompleted += (s, e) => _dispatcher.Invoke(() =>
{
if (e.TestIndex == 0)
{
LeftValue = e.MeasuredValue;
LeftJudgment = e.Judgment;
IsLeftOk = e.Judgment == "OK";
LeftStatus = "시험 완료";
}
else
{
RightValue = e.MeasuredValue;
RightJudgment = e.Judgment;
IsRightOk = e.Judgment == "OK";
RightStatus = "시험 완료";
}
// 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;
MessageBox.Show(
"프로그램 스팩과 센서의 스팩이 서로 맞지 않습니다.",
"SPEC 교차 검증 경고",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
});
_testProcess.Start();
}
private void ClearLeftResult()
{
LeftValue = ""; LeftJudgment = ""; IsLeftOk = false;
LeftStartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
LeftId = ""; LeftLowId = ""; LeftDate = ""; LeftSerialNo = "";
LeftMcLine = ""; LeftItem = "";
LeftError = "";
}
private void ClearRightResult()
{
RightValue = ""; RightJudgment = ""; IsRightOk = false;
RightStartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
RightId = ""; RightLowId = ""; RightDate = ""; RightSerialNo = "";
RightMcLine = ""; RightItem = "";
RightError = "";
}
private void UpdateMeasurement(ParsedData data)
{
_dispatcher.Invoke(() => {
// ChannelNo(C01=LEFT, C02=RIGHT)에 따라 데이터 라우팅
if (data.ChannelNo == "C01" || data.ChannelNo == "1")
LeftValue = data.MeasuredValue.ToString("F3");
else if (data.ChannelNo == "C02" || data.ChannelNo == "2")
RightValue = data.MeasuredValue.ToString("F3");
else
{
// 채널 정보가 없으면 양쪽 모두 갱신
LeftValue = data.MeasuredValue.ToString("F3");
RightValue = data.MeasuredValue.ToString("F3");
}
});
}
private void ProcessFinalResult(ParsedData data)
{
_dispatcher.Invoke(() => {
double.TryParse(SpecUL, out double ul);
double.TryParse(SpecLL, out double ll);
string judgment = SentinelParser.EvaluateJudgment(data.MeasuredValue, ul, ll);
bool isOk = judgment == "OK";
bool isLeft = (data.ChannelNo == "C01" || data.ChannelNo == "1");
string side = isLeft ? "LEFT" : "RIGHT";
if (isLeft)
{
LeftValue = data.MeasuredValue.ToString("F3");
LeftJudgment = judgment;
IsLeftOk = isOk;
LeftStatus = "TEST COMPLETE";
LeftStartTime = data.TestTime ?? "";
LeftDate = data.TestDate ?? "";
LeftId = data.UniqueId ?? "";
LeftLowId = data.LowID ?? "";
LeftSerialNo = data.SerialNo ?? "";
LeftMcLine = data.ChannelNo ?? "";
LeftItem = data.ProgramNo ?? "";
}
else
{
RightValue = data.MeasuredValue.ToString("F3");
RightJudgment = judgment;
IsRightOk = isOk;
RightStatus = "TEST COMPLETE";
RightStartTime = data.TestTime ?? "";
RightDate = data.TestDate ?? "";
RightId = data.UniqueId ?? "";
RightLowId = data.LowID ?? "";
RightSerialNo = data.SerialNo ?? "";
RightMcLine = data.ChannelNo ?? "";
RightItem = data.ProgramNo ?? "";
}
// SPEC 교차 검증 (C28 수동 수신 시)
if (data.SensorJudgment != null)
{
string normalizedProgram = (judgment == "OK") ? "A" : "R";
string normalizedSensor = data.SensorJudgment;
if (normalizedProgram != normalizedSensor)
{
string msg = $"SPEC 불일치 - 프로그램: {judgment}, 센서: {data.SensorJudgment}";
if (isLeft) LeftError = msg;
else RightError = msg;
}
}
// 로그 기록 (일일 CSV 파일)
var inspectData = new InspectData
{
InspectDate = data.TestDate ?? DateTime.Now.ToString("yyyy-MM-dd"),
InspectTime = data.TestTime ?? DateTime.Now.ToString("HH:mm:ss"),
Channel = isLeft ? "LEFT" : "RIGHT",
ProductId = data.UniqueId ?? "",
MeasuredValue = data.MeasuredValue.ToString("F3"),
Judgment = judgment,
Mode = "양산", // 기본값
LineNo = data.ChannelNo ?? "",
ProductType = data.ProgramNo ?? "",
SpecUL = ul.ToString("F2"),
SpecLL = ll.ToString("F2"),
Retest = "N"
};
FileLogger.LogInspectData(inspectData);
});
}
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();
_dioBoard?.Dispose();
}
}
}