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.
436 lines
17 KiB
436 lines
17 KiB
|
4 weeks ago
|
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 ZmdiSensorService _leftZmdi;
|
||
|
|
private ZmdiSensorService _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()
|
||
|
|
{
|
||
|
|
CleanupCommunication();
|
||
|
|
var config = ConfigManager.Current;
|
||
|
|
InitializeCommunication(config);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void CleanupCommunication()
|
||
|
|
{
|
||
|
|
_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) => {
|
||
|
|
// Raw 데이터는 UI 로그 보다는 파일 로그나 디버그 창으로 전송 가능
|
||
|
|
System.Diagnostics.Debug.WriteLine($"[SENTINEL RAW] {data}");
|
||
|
|
};
|
||
|
|
|
||
|
|
_sentinelService.OnStreamingParsed += (s, data) => UpdateMeasurement(data);
|
||
|
|
_sentinelService.OnFinalResultParsed += (s, data) => ProcessFinalResult(data);
|
||
|
|
|
||
|
|
_sentinelService.Connect();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void InitializeTestProcess(AppConfig config)
|
||
|
|
{
|
||
|
|
// DIO 보드 초기화 (MainViewModel에서 생성된 보드 사용)
|
||
|
|
// _dioBoard는 생성자에서 이미 할당됨.
|
||
|
|
if (_dioBoard == null) return;
|
||
|
|
// _dioBoard.Initialize(); // MainViewModel이나 Factory에서 이미 수행됨.
|
||
|
|
|
||
|
|
// ZMDI 센서 서비스 (LEFT/RIGHT)
|
||
|
|
_leftZmdiSerial = new SerialProvider(config.LeftPort, config.ZmdiBaudRate);
|
||
|
|
_rightZmdiSerial = new SerialProvider(config.RightPort, config.ZmdiBaudRate);
|
||
|
|
_leftZmdi = new ZmdiSensorService(_leftZmdiSerial, 0);
|
||
|
|
_rightZmdi = new ZmdiSensorService(_rightZmdiSerial, 1);
|
||
|
|
|
||
|
|
_leftZmdi.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => LeftStatus = msg);
|
||
|
|
_leftZmdi.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
|
||
|
|
LeftError = msg;
|
||
|
|
AppendLog(true, $"[ZMDI Error] {msg}");
|
||
|
|
});
|
||
|
|
_rightZmdi.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => RightStatus = msg);
|
||
|
|
_rightZmdi.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
|
||
|
|
RightError = msg;
|
||
|
|
AppendLog(false, $"[ZMDI Error] {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");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
_testProcess?.Dispose();
|
||
|
|
_dioBoard?.Dispose();
|
||
|
|
|
||
|
|
_leftZmdiSerial?.Dispose();
|
||
|
|
_rightZmdiSerial?.Dispose();
|
||
|
|
|
||
|
|
CleanupCommunication();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|