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.

1032 lines
37 KiB

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using measurement_machine.Service;
namespace measurement_machine
{
public partial class MainWindow : Window
{
private enum InspectionState
{
Idle,
WaitingRelay,
TriggeringMeasurement,
WaitingMeasurement,
Judging,
ResultHold,
Error
}
private readonly DispatcherTimer _timer = new();
private readonly DispatcherTimer _stateTimer = new();
private DateTime _startTime;
private DateTime _stateEnteredAt;
private DateTime _cycleStartedAt;
private bool _isInspecting;
private readonly SerialDeviceService _controllerSerialService = new();
private readonly SerialDeviceService _leftSerialService = new();
private readonly SerialDeviceService _rightSerialService = new();
private readonly List<int> _baudRates = new() { 9600, 115200 };
private string _controllerPortName = "";
private int _controllerBaudRate = 9600;
private string _controllerStartCommand = "";
private string _controllerStopCommand = "";
private string _leftPortName = "";
private int _leftBaudRate = 9600;
private string _leftReadCommand = "";
private string _rightPortName = "";
private int _rightBaudRate = 9600;
private string _rightReadCommand = "";
private double _leftMinSpec = 0.00;
private double _leftMaxSpec = 100.00;
private double _rightMinSpec = 0.00;
private double _rightMaxSpec = 100.00;
private int _relayIdleMs = 100;
private int _measurementTimeoutMs = 1000;
private int _resultHoldMs = 100;
private int _okCount;
private int _ngCount;
private int _totalCount;
private string _lastLeftRaw = "";
private string _lastRightRaw = "";
private bool _leftReceived;
private bool _rightReceived;
private double? _lastLeftValue;
private double? _lastRightValue;
private string _saveFilePath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data", "inspection_settings.xml");
private InspectionState _inspectionState = InspectionState.Idle;
private readonly List<InspectionHistoryItem> _history = new();
public MainWindow()
{
InitializeComponent();
_startTime = DateTime.Now;
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += Timer_Tick;
_timer.Start();
_stateTimer.Interval = TimeSpan.FromMilliseconds(50);
_stateTimer.Tick += StateTimer_Tick;
_stateTimer.Start();
InitializeSettingUi();
RefreshAvailablePorts();
_leftSerialService.DataReceived += OnLeftSerialDataReceived;
_rightSerialService.DataReceived += OnRightSerialDataReceived;
_controllerSerialService.ErrorOccurred += OnSerialErrorOccurred;
_leftSerialService.ErrorOccurred += OnSerialErrorOccurred;
_rightSerialService.ErrorOccurred += OnSerialErrorOccurred;
ShowSettingsTab("CONTROLLER");
LoadSavedConfiguration();
SetIdleUi();
}
private void InitializeSettingUi()
{
ControllerBaudComboBox.ItemsSource = _baudRates;
LeftBaudComboBox.ItemsSource = _baudRates;
RightBaudComboBox.ItemsSource = _baudRates;
ControllerBaudComboBox.SelectedItem = 9600;
LeftBaudComboBox.SelectedItem = 9600;
RightBaudComboBox.SelectedItem = 9600;
ControllerStartCommandTextBox.Text = "";
ControllerStopCommandTextBox.Text = "";
LeftReadCommandTextBox.Text = "";
RightReadCommandTextBox.Text = "";
LeftMinSpecTextBox.Text = "0.00";
LeftMaxSpecTextBox.Text = "100.00";
RightMinSpecTextBox.Text = "0.00";
RightMaxSpecTextBox.Text = "100.00";
RelayIdleTextBox.Text = "100";
MeasurementTimeoutTextBox.Text = "1000";
ResultHoldTextBox.Text = "100";
SavePathTextBox.Text = _saveFilePath;
UserConfigStatusTextBlock.Text = "---";
SettingsStatusTextBlock.Text = "---";
}
private void LoadSavedConfiguration()
{
try
{
var loaded = ExcelXmlStorageService.Load(_saveFilePath);
if (loaded == null)
return;
_controllerPortName = loaded.ControllerPortName;
_controllerBaudRate = loaded.ControllerBaudRate;
_controllerStartCommand = loaded.ControllerStartCommand;
_controllerStopCommand = loaded.ControllerStopCommand;
_leftPortName = loaded.LeftPortName;
_leftBaudRate = loaded.LeftBaudRate;
_leftReadCommand = loaded.LeftReadCommand;
_rightPortName = loaded.RightPortName;
_rightBaudRate = loaded.RightBaudRate;
_rightReadCommand = loaded.RightReadCommand;
_leftMinSpec = loaded.LeftMinSpec;
_leftMaxSpec = loaded.LeftMaxSpec;
_rightMinSpec = loaded.RightMinSpec;
_rightMaxSpec = loaded.RightMaxSpec;
_relayIdleMs = loaded.RelayIdleMs;
_measurementTimeoutMs = loaded.MeasurementTimeoutMs;
_resultHoldMs = loaded.ResultHoldMs;
_okCount = loaded.OkCount;
_ngCount = loaded.NgCount;
_totalCount = loaded.TotalCount;
_saveFilePath = string.IsNullOrWhiteSpace(loaded.SaveFilePath)
? _saveFilePath
: loaded.SaveFilePath;
_history.Clear();
if (loaded.History != null)
_history.AddRange(loaded.History);
ApplyConfigToUi();
UpdateCountUi();
SettingsStatusTextBlock.Text = "저장된 설정 불러오기 완료";
}
catch (Exception ex)
{
Debug.WriteLine($"[LOAD CONFIG ERROR] {ex.Message}");
SettingsStatusTextBlock.Text = "저장된 설정 불러오기 실패";
}
}
private void ApplyConfigToUi()
{
RefreshAvailablePorts();
if (!string.IsNullOrWhiteSpace(_controllerPortName))
ControllerPortComboBox.SelectedItem = _controllerPortName;
if (!string.IsNullOrWhiteSpace(_leftPortName))
LeftPortComboBox.SelectedItem = _leftPortName;
if (!string.IsNullOrWhiteSpace(_rightPortName))
RightPortComboBox.SelectedItem = _rightPortName;
ControllerBaudComboBox.SelectedItem = _controllerBaudRate;
LeftBaudComboBox.SelectedItem = _leftBaudRate;
RightBaudComboBox.SelectedItem = _rightBaudRate;
ControllerStartCommandTextBox.Text = _controllerStartCommand;
ControllerStopCommandTextBox.Text = _controllerStopCommand;
LeftReadCommandTextBox.Text = _leftReadCommand;
RightReadCommandTextBox.Text = _rightReadCommand;
LeftMinSpecTextBox.Text = _leftMinSpec.ToString("F2", CultureInfo.InvariantCulture);
LeftMaxSpecTextBox.Text = _leftMaxSpec.ToString("F2", CultureInfo.InvariantCulture);
RightMinSpecTextBox.Text = _rightMinSpec.ToString("F2", CultureInfo.InvariantCulture);
RightMaxSpecTextBox.Text = _rightMaxSpec.ToString("F2", CultureInfo.InvariantCulture);
RelayIdleTextBox.Text = _relayIdleMs.ToString(CultureInfo.InvariantCulture);
MeasurementTimeoutTextBox.Text = _measurementTimeoutMs.ToString(CultureInfo.InvariantCulture);
ResultHoldTextBox.Text = _resultHoldMs.ToString(CultureInfo.InvariantCulture);
SavePathTextBox.Text = _saveFilePath;
UserConfigStatusTextBlock.Text =
$"Left: {_leftMinSpec:F2} ~ {_leftMaxSpec:F2} mm\n" +
$"Right: {_rightMinSpec:F2} ~ {_rightMaxSpec:F2} mm\n" +
$"Relay: {_relayIdleMs} ms\n" +
$"Timeout: {_measurementTimeoutMs} ms\n" +
$"Hold: {_resultHoldMs} ms";
}
private void SaveAllConfiguration()
{
Directory.CreateDirectory(Path.GetDirectoryName(_saveFilePath)!);
var model = new InspectionConfigModel
{
ControllerPortName = _controllerPortName,
ControllerBaudRate = _controllerBaudRate,
ControllerStartCommand = _controllerStartCommand,
ControllerStopCommand = _controllerStopCommand,
LeftPortName = _leftPortName,
LeftBaudRate = _leftBaudRate,
LeftReadCommand = _leftReadCommand,
RightPortName = _rightPortName,
RightBaudRate = _rightBaudRate,
RightReadCommand = _rightReadCommand,
LeftMinSpec = _leftMinSpec,
LeftMaxSpec = _leftMaxSpec,
RightMinSpec = _rightMinSpec,
RightMaxSpec = _rightMaxSpec,
RelayIdleMs = _relayIdleMs,
MeasurementTimeoutMs = _measurementTimeoutMs,
ResultHoldMs = _resultHoldMs,
OkCount = _okCount,
NgCount = _ngCount,
TotalCount = _totalCount,
SaveFilePath = _saveFilePath,
History = new List<InspectionHistoryItem>(_history)
};
ExcelXmlStorageService.Save(model, _saveFilePath);
}
private void RefreshAvailablePorts()
{
var ports = SerialDeviceService.GetAvailablePorts();
ControllerPortComboBox.ItemsSource = ports;
LeftPortComboBox.ItemsSource = ports;
RightPortComboBox.ItemsSource = ports;
if (ports.Length > 0)
{
if (string.IsNullOrWhiteSpace(_controllerPortName))
ControllerPortComboBox.SelectedIndex = 0;
else
ControllerPortComboBox.SelectedItem = _controllerPortName;
if (string.IsNullOrWhiteSpace(_leftPortName))
LeftPortComboBox.SelectedIndex = 0;
else
LeftPortComboBox.SelectedItem = _leftPortName;
if (string.IsNullOrWhiteSpace(_rightPortName))
RightPortComboBox.SelectedIndex = ports.Length > 1 ? 1 : 0;
else
RightPortComboBox.SelectedItem = _rightPortName;
}
SettingsStatusTextBlock.Text = ports.Length > 0
? "COM 포트 목록 갱신 완료"
: "연결된 COM 포트 없음";
}
private void Timer_Tick(object? sender, EventArgs e)
{
if (!_isInspecting)
return;
var elapsed = DateTime.Now - _startTime;
ElapsedTimeTextBlock.Text = elapsed.ToString(@"h\:mm\:ss");
}
private void StateTimer_Tick(object? sender, EventArgs e)
{
if (!_isInspecting)
return;
try
{
switch (_inspectionState)
{
case InspectionState.WaitingRelay:
if (ElapsedStateMs() >= _relayIdleMs)
{
TransitionTo(InspectionState.TriggeringMeasurement);
}
break;
case InspectionState.TriggeringMeasurement:
BeginMeasurementCycle();
TransitionTo(InspectionState.WaitingMeasurement);
break;
case InspectionState.WaitingMeasurement:
if (_leftReceived && _rightReceived)
{
TransitionTo(InspectionState.Judging);
}
else if (ElapsedStateMs() >= _measurementTimeoutMs)
{
TransitionTo(InspectionState.Judging);
}
break;
case InspectionState.Judging:
EvaluateInspectionResultAndCount();
TransitionTo(InspectionState.ResultHold);
break;
case InspectionState.ResultHold:
if (ElapsedStateMs() >= _resultHoldMs)
{
PrepareNextCycle();
TransitionTo(InspectionState.WaitingRelay);
}
break;
}
}
catch (Exception ex)
{
Debug.WriteLine($"[STATE ERROR] {ex.Message}");
ResultTextBlock.Text = "상태오류";
ResultTextBlock.Foreground = Brushes.Red;
SetStatusIndicator("오류");
_inspectionState = InspectionState.Error;
}
}
private void TransitionTo(InspectionState nextState)
{
_inspectionState = nextState;
_stateEnteredAt = DateTime.Now;
switch (_inspectionState)
{
case InspectionState.WaitingRelay:
case InspectionState.TriggeringMeasurement:
case InspectionState.WaitingMeasurement:
ResultTextBlock.Text = "검사진행";
ResultTextBlock.Foreground = Brushes.DarkGoldenrod;
SetStatusIndicator("검사진행");
break;
}
Debug.WriteLine($"[STATE] -> {_inspectionState}");
}
private int ElapsedStateMs()
{
return (int)(DateTime.Now - _stateEnteredAt).TotalMilliseconds;
}
private void HelpButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("도움말 기능은 추후 연결 예정입니다.", "도움말",
MessageBoxButton.OK, MessageBoxImage.Information);
}
private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
MainScreenGrid.Visibility = Visibility.Collapsed;
SettingsScreenGrid.Visibility = Visibility.Visible;
}
private void BackToMainButton_Click(object sender, RoutedEventArgs e)
{
SettingsScreenGrid.Visibility = Visibility.Collapsed;
MainScreenGrid.Visibility = Visibility.Visible;
}
private void ControllerTabButton_Click(object sender, RoutedEventArgs e)
{
ShowSettingsTab("CONTROLLER");
}
private void LeftMeasureTabButton_Click(object sender, RoutedEventArgs e)
{
ShowSettingsTab("LEFT");
}
private void RightMeasureTabButton_Click(object sender, RoutedEventArgs e)
{
ShowSettingsTab("RIGHT");
}
private void UserTabButton_Click(object sender, RoutedEventArgs e)
{
ShowSettingsTab("USER");
}
private void ShowSettingsTab(string tabName)
{
ControllerTabGrid.Visibility = Visibility.Collapsed;
LeftMeasureTabGrid.Visibility = Visibility.Collapsed;
RightMeasureTabGrid.Visibility = Visibility.Collapsed;
UserTabGrid.Visibility = Visibility.Collapsed;
ControllerTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F5F5F2"));
LeftTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F5F5F2"));
RightTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F5F5F2"));
UserTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F5F5F2"));
switch (tabName)
{
case "CONTROLLER":
ControllerTabGrid.Visibility = Visibility.Visible;
ControllerTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EAEAEA"));
break;
case "LEFT":
LeftMeasureTabGrid.Visibility = Visibility.Visible;
LeftTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EAEAEA"));
break;
case "RIGHT":
RightMeasureTabGrid.Visibility = Visibility.Visible;
RightTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EAEAEA"));
break;
case "USER":
UserTabGrid.Visibility = Visibility.Visible;
UserTabButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EAEAEA"));
break;
}
}
private void RefreshPortsButton_Click(object sender, RoutedEventArgs e)
{
RefreshAvailablePorts();
}
private void SaveControllerConfigButton_Click(object sender, RoutedEventArgs e)
{
if (ControllerPortComboBox.SelectedItem == null)
{
MessageBox.Show("컨트롤 보드 COM 포트를 선택하세요.", "컨트롤러 설정",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
_controllerPortName = ControllerPortComboBox.SelectedItem.ToString() ?? "";
_controllerBaudRate = (int)(ControllerBaudComboBox.SelectedItem ?? 9600);
_controllerStartCommand = ControllerStartCommandTextBox.Text?.Trim() ?? "";
_controllerStopCommand = ControllerStopCommandTextBox.Text?.Trim() ?? "";
SaveAllConfiguration();
SettingsStatusTextBlock.Text =
$"컨트롤러 설정 저장 완료\n{_controllerPortName} / {_controllerBaudRate}\nSTART={_controllerStartCommand}\nSTOP={_controllerStopCommand}";
}
private void SaveLeftConfigButton_Click(object sender, RoutedEventArgs e)
{
if (LeftPortComboBox.SelectedItem == null)
{
MessageBox.Show("좌측 COM 포트를 선택하세요.", "좌측 설정",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
_leftPortName = LeftPortComboBox.SelectedItem.ToString() ?? "";
_leftBaudRate = (int)(LeftBaudComboBox.SelectedItem ?? 9600);
_leftReadCommand = LeftReadCommandTextBox.Text?.Trim() ?? "";
SaveAllConfiguration();
SettingsStatusTextBlock.Text =
$"좌측 설정 저장 완료\n{_leftPortName} / {_leftBaudRate}\nREAD={_leftReadCommand}";
}
private void SaveRightConfigButton_Click(object sender, RoutedEventArgs e)
{
if (RightPortComboBox.SelectedItem == null)
{
MessageBox.Show("우측 COM 포트를 선택하세요.", "우측 설정",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
_rightPortName = RightPortComboBox.SelectedItem.ToString() ?? "";
_rightBaudRate = (int)(RightBaudComboBox.SelectedItem ?? 9600);
_rightReadCommand = RightReadCommandTextBox.Text?.Trim() ?? "";
SaveAllConfiguration();
SettingsStatusTextBlock.Text =
$"우측 설정 저장 완료\n{_rightPortName} / {_rightBaudRate}\nREAD={_rightReadCommand}";
}
private void SaveUserConfigButton_Click(object sender, RoutedEventArgs e)
{
try
{
_leftMinSpec = ParseDouble(LeftMinSpecTextBox.Text, "좌측 Min");
_leftMaxSpec = ParseDouble(LeftMaxSpecTextBox.Text, "좌측 Max");
_rightMinSpec = ParseDouble(RightMinSpecTextBox.Text, "우측 Min");
_rightMaxSpec = ParseDouble(RightMaxSpecTextBox.Text, "우측 Max");
_relayIdleMs = ParseInt(RelayIdleTextBox.Text, "Relay Idle Time");
_measurementTimeoutMs = ParseInt(MeasurementTimeoutTextBox.Text, "Measurement Timeout");
_resultHoldMs = ParseInt(ResultHoldTextBox.Text, "Result Hold Time");
if (_leftMinSpec > _leftMaxSpec)
throw new Exception("좌측 치수 기준값에서 Min이 Max보다 클 수 없습니다.");
if (_rightMinSpec > _rightMaxSpec)
throw new Exception("우측 치수 기준값에서 Min이 Max보다 클 수 없습니다.");
var requestedPath = SavePathTextBox.Text?.Trim() ?? "";
if (!string.IsNullOrWhiteSpace(requestedPath))
_saveFilePath = requestedPath;
SaveAllConfiguration();
UserConfigStatusTextBlock.Text =
$"Left: {_leftMinSpec:F2} ~ {_leftMaxSpec:F2} mm\n" +
$"Right: {_rightMinSpec:F2} ~ {_rightMaxSpec:F2} mm\n" +
$"Relay: {_relayIdleMs} ms\n" +
$"Timeout: {_measurementTimeoutMs} ms\n" +
$"Hold: {_resultHoldMs} ms";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "USER 설정", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void TestControllerPortButton_Click(object sender, RoutedEventArgs e)
{
try
{
if (ControllerPortComboBox.SelectedItem == null)
{
MessageBox.Show("컨트롤 보드 COM 포트를 선택하세요.", "포트 테스트",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
string port = ControllerPortComboBox.SelectedItem.ToString() ?? "";
int baud = (int)(ControllerBaudComboBox.SelectedItem ?? 9600);
using var test = new SerialDeviceService();
test.Open(port, baud);
test.Close();
SettingsStatusTextBlock.Text = $"컨트롤러 포트 테스트 성공\n{port} / {baud}";
}
catch (Exception ex)
{
SettingsStatusTextBlock.Text = "컨트롤러 포트 테스트 실패";
MessageBox.Show(ex.Message, "컨트롤러 포트 테스트",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void TestLeftPortButton_Click(object sender, RoutedEventArgs e)
{
try
{
if (LeftPortComboBox.SelectedItem == null)
{
MessageBox.Show("좌측 COM 포트를 선택하세요.", "포트 테스트",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
string port = LeftPortComboBox.SelectedItem.ToString() ?? "";
int baud = (int)(LeftBaudComboBox.SelectedItem ?? 9600);
using var test = new SerialDeviceService();
test.Open(port, baud);
test.Close();
SettingsStatusTextBlock.Text = $"좌측 포트 테스트 성공\n{port} / {baud}";
}
catch (Exception ex)
{
SettingsStatusTextBlock.Text = "좌측 포트 테스트 실패";
MessageBox.Show(ex.Message, "좌측 포트 테스트",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void TestRightPortButton_Click(object sender, RoutedEventArgs e)
{
try
{
if (RightPortComboBox.SelectedItem == null)
{
MessageBox.Show("우측 COM 포트를 선택하세요.", "포트 테스트",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
string port = RightPortComboBox.SelectedItem.ToString() ?? "";
int baud = (int)(RightBaudComboBox.SelectedItem ?? 9600);
using var test = new SerialDeviceService();
test.Open(port, baud);
test.Close();
SettingsStatusTextBlock.Text = $"우측 포트 테스트 성공\n{port} / {baud}";
}
catch (Exception ex)
{
SettingsStatusTextBlock.Text = "우측 포트 테스트 실패";
MessageBox.Show(ex.Message, "우측 포트 테스트",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
StopInspection();
_okCount = 0;
_ngCount = 0;
_totalCount = 0;
_history.Clear();
UpdateCountUi();
SaveAllConfiguration();
SetIdleUi();
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
StartInspection();
}
private void ExitButton_Click(object sender, RoutedEventArgs e)
{
StopInspection();
}
private void StartInspection()
{
if (_isInspecting)
return;
if (string.IsNullOrWhiteSpace(_controllerPortName))
{
MessageBox.Show("설정 화면에서 CONTROLLER 설정을 먼저 저장하세요.",
"설정 필요", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (string.IsNullOrWhiteSpace(_leftPortName) || string.IsNullOrWhiteSpace(_rightPortName))
{
MessageBox.Show("설정 화면에서 좌측/우측 치수 설정을 먼저 저장하세요.",
"설정 필요", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
try
{
_controllerSerialService.Open(_controllerPortName, _controllerBaudRate);
_leftSerialService.Open(_leftPortName, _leftBaudRate);
_rightSerialService.Open(_rightPortName, _rightBaudRate);
_controllerSerialService.DiscardInBuffer();
_leftSerialService.DiscardInBuffer();
_rightSerialService.DiscardInBuffer();
_isInspecting = true;
_startTime = DateTime.Now;
ElapsedTimeTextBlock.Text = "0:00:00";
PrepareNextCycle();
TransitionTo(InspectionState.WaitingRelay);
Debug.WriteLine($"[CONTROLLER OPEN] {_controllerPortName} {_controllerBaudRate}");
Debug.WriteLine($"[LEFT OPEN] {_leftPortName} {_leftBaudRate}");
Debug.WriteLine($"[RIGHT OPEN] {_rightPortName} {_rightBaudRate}");
}
catch (Exception ex)
{
_controllerSerialService.Close();
_leftSerialService.Close();
_rightSerialService.Close();
_isInspecting = false;
_inspectionState = InspectionState.Error;
ResultTextBlock.Text = "오류";
ResultTextBlock.Foreground = Brushes.Red;
SetStatusIndicator("오류");
MessageBox.Show(ex.Message, "통신 시작 실패",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void StopInspection()
{
if (!_isInspecting)
return;
try
{
if (_controllerSerialService.IsOpen && !string.IsNullOrWhiteSpace(_controllerStopCommand))
{
_controllerSerialService.WriteLine(_controllerStopCommand);
}
_controllerSerialService.Close();
_leftSerialService.Close();
_rightSerialService.Close();
_isInspecting = false;
_inspectionState = InspectionState.Idle;
ResultTextBlock.Text = "대기";
ResultTextBlock.Foreground = Brushes.Black;
SetStatusIndicator("대기");
SaveAllConfiguration();
Debug.WriteLine("[CLOSE] 검사 종료");
}
catch (Exception ex)
{
ResultTextBlock.Text = "오류";
ResultTextBlock.Foreground = Brushes.Red;
SetStatusIndicator("오류");
MessageBox.Show(ex.Message, "검사 종료 실패",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void BeginMeasurementCycle()
{
_cycleStartedAt = DateTime.Now;
_leftReceived = false;
_rightReceived = false;
_lastLeftValue = null;
_lastRightValue = null;
_lastLeftRaw = "";
_lastRightRaw = "";
if (_controllerSerialService.IsOpen && !string.IsNullOrWhiteSpace(_controllerStartCommand))
{
_controllerSerialService.WriteLine(_controllerStartCommand);
}
if (_leftSerialService.IsOpen && !string.IsNullOrWhiteSpace(_leftReadCommand))
{
_leftSerialService.WriteLine(_leftReadCommand);
}
if (_rightSerialService.IsOpen && !string.IsNullOrWhiteSpace(_rightReadCommand))
{
_rightSerialService.WriteLine(_rightReadCommand);
}
Debug.WriteLine("[CYCLE] measurement command sent");
}
private void PrepareNextCycle()
{
_leftReceived = false;
_rightReceived = false;
_lastLeftValue = null;
_lastRightValue = null;
_lastLeftRaw = "";
_lastRightRaw = "";
}
private void OnLeftSerialDataReceived(string rawData)
{
Dispatcher.Invoke(() =>
{
Debug.WriteLine($"[LEFT RX] {rawData}");
_lastLeftRaw = rawData;
if (TryExtractNumber(rawData, out double value))
{
_lastLeftValue = value;
_leftReceived = true;
LeftDimensionTextBlock.Text = value.ToString("F2", CultureInfo.InvariantCulture);
}
});
}
private void OnRightSerialDataReceived(string rawData)
{
Dispatcher.Invoke(() =>
{
Debug.WriteLine($"[RIGHT RX] {rawData}");
_lastRightRaw = rawData;
if (TryExtractNumber(rawData, out double value))
{
_lastRightValue = value;
_rightReceived = true;
RightDimensionTextBlock.Text = value.ToString("F2", CultureInfo.InvariantCulture);
}
});
}
private void OnSerialErrorOccurred(string message)
{
Dispatcher.Invoke(() =>
{
Debug.WriteLine($"[SERIAL ERROR] {message}");
ResultTextBlock.Text = "통신오류";
ResultTextBlock.Foreground = Brushes.Red;
SetStatusIndicator("오류");
_inspectionState = InspectionState.Error;
});
}
private void EvaluateInspectionResultAndCount()
{
bool leftTimeout = !_leftReceived || !_lastLeftValue.HasValue;
bool rightTimeout = !_rightReceived || !_lastRightValue.HasValue;
bool leftOk = !leftTimeout &&
_lastLeftValue!.Value >= _leftMinSpec &&
_lastLeftValue!.Value <= _leftMaxSpec;
bool rightOk = !rightTimeout &&
_lastRightValue!.Value >= _rightMinSpec &&
_lastRightValue!.Value <= _rightMaxSpec;
string reason;
bool isOk;
if (leftTimeout || rightTimeout)
{
isOk = false;
reason = $"TIMEOUT (L={_leftReceived}, R={_rightReceived})";
ResultTextBlock.Text = "타임아웃";
ResultTextBlock.Foreground = Brushes.Red;
SetStatusIndicator("오류");
}
else if (leftOk && rightOk)
{
isOk = true;
reason = "OK";
ResultTextBlock.Text = "완료";
ResultTextBlock.Foreground = Brushes.DarkGreen;
SetStatusIndicator("완료");
}
else
{
isOk = false;
if (!leftOk && !rightOk)
reason = "LEFT/RIGHT SPEC NG";
else if (!leftOk)
reason = "LEFT SPEC NG";
else
reason = "RIGHT SPEC NG";
ResultTextBlock.Text = "불량";
ResultTextBlock.Foreground = Brushes.Red;
SetStatusIndicator("오류");
}
_totalCount++;
if (isOk)
_okCount++;
else
_ngCount++;
UpdateCountUi();
_history.Add(new InspectionHistoryItem
{
Timestamp = DateTime.Now,
LeftValue = _lastLeftValue,
RightValue = _lastRightValue,
Result = isOk ? "OK" : "NG",
Reason = reason,
ElapsedMs = (int)(DateTime.Now - _cycleStartedAt).TotalMilliseconds,
LeftRaw = _lastLeftRaw,
RightRaw = _lastRightRaw
});
SaveAllConfiguration();
Debug.WriteLine($"[RESULT] {reason}");
}
private void UpdateCountUi()
{
CurrentCountTextBlock.Text = _okCount.ToString(CultureInfo.InvariantCulture);
DefectCountTextBlock.Text = _ngCount.ToString(CultureInfo.InvariantCulture);
TotalCountTextBlock.Text = _totalCount.ToString(CultureInfo.InvariantCulture);
}
private bool TryExtractNumber(string raw, out double value)
{
value = 0;
if (string.IsNullOrWhiteSpace(raw))
return false;
var match = Regex.Match(raw, @"-?\d+(\.\d+)?");
if (!match.Success)
return false;
return double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out value)
|| double.TryParse(match.Value, out value);
}
private double ParseDouble(string text, string fieldName)
{
if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out double value) &&
!double.TryParse(text, out value))
{
throw new Exception($"{fieldName} 값이 올바르지 않습니다.");
}
return value;
}
private int ParseInt(string text, string fieldName)
{
if (!int.TryParse(text, out int value))
{
throw new Exception($"{fieldName} 값이 올바르지 않습니다.");
}
return value;
}
private void SetIdleUi()
{
_isInspecting = false;
_inspectionState = InspectionState.Idle;
LeftDimensionTextBlock.Text = "0.00";
RightDimensionTextBlock.Text = "0.00";
ResultTextBlock.Text = "대기";
ResultTextBlock.Foreground = Brushes.Black;
ElapsedTimeTextBlock.Text = "0:00:00";
UpdateCountUi();
SetStatusIndicator("대기");
}
private void SetStatusIndicator(string state)
{
IdleStatusBorder.Opacity = 0.45;
InspectingStatusBorder.Opacity = 0.45;
CompletedStatusBorder.Opacity = 0.45;
switch (state)
{
case "대기":
IdleStatusBorder.Opacity = 1.0;
break;
case "검사진행":
InspectingStatusBorder.Opacity = 1.0;
break;
case "완료":
CompletedStatusBorder.Opacity = 1.0;
break;
default:
IdleStatusBorder.Opacity = 1.0;
break;
}
}
protected override void OnClosed(EventArgs e)
{
try
{
SaveAllConfiguration();
}
catch
{
// ignore
}
_controllerSerialService.Close();
_leftSerialService.Close();
_rightSerialService.Close();
base.OnClosed(e);
}
}
}