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
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);
|
|
}
|
|
}
|
|
}
|