LGES-OC5-PtoP-Welder/AuroraDevices/AuroraLaser/Coherent/Compact/Views/UCLaserCompactCalibrationViewModel.cs
2026-06-05 13:44:39 +09:00

1618 lines
68 KiB
C#

using Aurora;
using Aurora.Api;
using Aurora.View;
using AuroraLaser.Coherent;
using AuroraPowerMeter;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.Painting.Effects;
using MathNet.Numerics;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
namespace AuroraLaser.Views
{
public class PowerCalibData : NotifyPropertyChangedBase
{
public double PowerPerc { get; set; }
public double PowerWatt { get; set; }
private bool _use = true;
public bool Use {
get { return _use; }
set { SetProperty(ref _use, value); }
} // DataGridRow의 IsEnable과 Binding되는 property
}
public class PlotCheckBox : NotifyPropertyChangedBase
{
private bool _isSelected = true;
public bool IsSelected {
get => _isSelected;
set {
if (_isSelected != value) {
_plotItem.IsVisible = value; // 여기서 Plot 을 제어하면 될 것 같은데....
SetProperty(ref _isSelected, value);
}
}
}
public string PlotName { get; set; }
public int Index { get; set; }
private ISeries _plotItem;
public PlotCheckBox(int index, string name, ISeries plotItem)
{
Index = index;
PlotName = name;
_plotItem = plotItem;
IsSelected = true;
}
}
public class CalibRadioButton : NotifyPropertyChangedBase
{
public string Id { get; set; }
private bool _isEnabledRadio;
public bool IsEnabledRadio {
get { return _isEnabledRadio; }
set {
if (_isEnabledRadio != value) {
_isEnabledRadio = value;
NotifyPropertyChanged("IsEnabledRadio");
}
}
}
private bool _isSelected;
public bool IsSelected {
get { return _isSelected; }
set {
if (_isSelected != value) {
_isSelected = value;
if (_isSelected) { // 선택된 경우만 레이저 설정을 변경
// 레이저의 추정모델타입을 변경한다.
_laser?.SetLaserPowerCalibMethod(Id);
}
NotifyPropertyChanged("IsSelected");
}
}
}
// 공용 필드
public static LaserCompact _laser;
public CalibRadioButton(string id)
{
Id = id;
IsEnabledRadio = true;
IsSelected = false;
}
}
internal enum ePowerCalibPhase // 실질적으로는 파워 데이터 측정 페이즈임
{
Idle = -1, // 대기 상태
Init = 0,
Collecting,
LaserPowerSet,
Finish,
}
internal enum eInitBy
{
LaserActivate = 0,
LaserChange,
VisibleChange,
}
public enum ePowerModelCalib
{
LUT = 0,
Lin1Seg,
Lin2Seg,
}
public struct stPowerModelEnable
{
public bool IsLUTEnable { get; set; }
public bool IsLin1SegEnable { get; set; }
public bool IsLin2SegEnable { get; set; }
public stPowerModelEnable(bool isLUTEnable, bool isLinSegEnalbe, bool isLin2SegEanble)
{
IsLUTEnable = isLUTEnable;
IsLin1SegEnable = isLinSegEnalbe;
IsLin2SegEnable = isLin2SegEanble;
}
}
public class UCLaserCompactCalibrationViewModel : NotifyPropertyChangedBase
{
private static readonly Logger Log = new Logger("Laser Calib");
#region 커맨드
public ICommand CMD_VisibleChanged { get; }
public ICommand CMD_CopyFromCurernt2New { get; }
//--- 파워미터
public ICommand CMD_PowerMeterSetZero { get; }
//--- Laser 관련
public ICommand CMD_LaserTargetPowerSet { get; }
public ICommand CMD_LaserTargetPowerWattSet { get; }
//==============================================
//------ For LiveCharts2
public ICommand CMD_PlotZoomReset { get; }
public ICommand CMD_AxesSwitch { get; }
//=================================================
// --- 파워 측정 for collecting calibration data
public ICommand CMD_LaserPowerMeasureStart { get; }
public ICommand CMD_LaserPowerMeasureStop { get; }
public ICommand CMD_SaveApply { get; }
public ICommand CMD_SaveAsCSV { get; }
//======================================
//--- 레이저 파워 교정 관련
public ICommand CMD_SolveModel { get; }
public ICommand CMD_SetROI { get; }
#endregion
#region 프로퍼티 & 필드
// 해당 뷰 enable 상태
private bool _isUCViewEnabled = true;
public bool IsUCViewEnabled { get => _isUCViewEnabled; set => SetProperty(ref _isUCViewEnabled, value); }
//--- 전체 UC view enable 제어가 목적이었으나, 부분적으로만 사용됨..
private bool _isEnabled = false;
public bool IsEnabled { get => _isEnabled; set => SetProperty(ref _isEnabled, value); }
//--- 전체 uc view visible 정보
public bool IsVisible { get; private set; }
//--- 레이저 장치 선택/변경 enalbe 제어
private bool _isLaserDeviceSwitchEnabled = true;
public bool IsLaserDeviceSwitchEnabled { get => _isLaserDeviceSwitchEnabled; set => SetProperty(ref _isLaserDeviceSwitchEnabled, value); }
// Laser 0 or Laser1
List<string> laserIDs;
private bool _isLaser1 = false;
public bool IsLaser1 {
get => _isLaser1;
set {
if (_isLaser1 != value) {
// 레이저가 변경된 경우, 그에 맞는 초기화 필요!!!
// 1. 타이머 종료
if (MonitorTimer.IsEnabled) {
Log.V($"IsLaser1: {value}, MonitorTimer is enabled");
MonitorTimer.Stop();
// 레이저 변경
if (value) { // Laser1
//_laser = _aurora.DeviceManager.GetDevice(CommonDeviceIds.LaserWire1) as WireLaser;
} else { // Laser0
_laser = _aurora.DeviceManager.GetDevice(laserIDs[0]) as LaserCompact;
}
InitByLaserChange();
MonitorTimer.Start();
} else {
Log.V($"IsLaser1: {value}, MonitorTimer is disabled");
// 레이저 변경
if (value) { // Laser1
//_laser = _aurora.DeviceManager.GetDevice(CommonDeviceIds.LaserWire1) as WireLaser;
} else { // Laser0
_laser = _aurora.DeviceManager.GetDevice(laserIDs[0]) as LaserCompact;
}
InitByLaserChange();
}
SetProperty(ref _isLaser1, value);
}
}
}
//------- 파워미터 관련
private string _powerMeterInfo;
public string PowerMeterInfo { get => _powerMeterInfo; set => SetProperty(ref _powerMeterInfo, value); }
private double _powerMeterWatt;
public double PowerMeterWatt {
get => _powerMeterWatt;
set {
SetProperty(ref _powerMeterWatt, Math.Round(value, precision4));
}
}
private bool _isPowerMeterZeroEnabled = false;
public bool IsPowerMeterZeroEnabled { get => _isPowerMeterZeroEnabled; set => SetProperty(ref _isPowerMeterZeroEnabled, value); }
private bool _setPowerMeterZero = false;
//===================================
//----- 레이저 관련
private double _laserActualPower;
public double LaserActualPower { get => _laserActualPower; set => SetProperty(ref _laserActualPower, value); }
//======================
//------ 레이저 파워교정 세팅
private int _startDelay;
public int StartDelay { get => _startDelay; set => SetProperty(ref _startDelay, value); }
private int _measurementDelay;
public int MeasurementDelay {
get => _measurementDelay;
set {
// 파워 측정 중에만 타이머 interval 을 변경할 수 있다
// - 하나의 타이머를 쓰고 있으므로... 일반 상태, 파워 측정 상태로 구분...
if (IsLaserPowerMeasuring && _measurementDelay != value) {
// 타이머의 인터벌을 측정 딜레이로 변경하자.
MonitorTimer.Interval = TimeSpan.FromMilliseconds(value);
}
SetProperty(ref _measurementDelay, value);
}
}
private double _beginPowerPerc;
public double BeginPowerPerc { get => _beginPowerPerc; set => SetProperty(ref _beginPowerPerc, value); }
private double _endPowerPerc;
public double EndPowerPerc { get => _endPowerPerc; set => SetProperty(ref _endPowerPerc, value); }
private double _measurementStepPerc;
public double MeasurementStepPerc {
get => _measurementStepPerc;
set {
SetProperty(ref _measurementStepPerc, value);
}
}
//===================
//----- 레이저 수동 조작
private double _laserTargetPowerWatt;
public double LaserTargetPowerWatt { get => _laserTargetPowerWatt; set => SetProperty(ref _laserTargetPowerWatt, value); }
bool _isLaserTargetPowerWattSetEnabled = true;
public bool IsLaserTargetPowerWattSetEnabled { get => _isLaserTargetPowerWattSetEnabled; set => SetProperty(ref _isLaserTargetPowerWattSetEnabled, value); }
private double _laserTargetPower;
public double LaserTargetPower { get => _laserTargetPower; set => SetProperty(ref _laserTargetPower, value); }
bool _isLaserTargetPowerSetEnabled = true;
public bool IsLaserTargetPowerSetEnabled { get => _isLaserTargetPowerSetEnabled; set => SetProperty(ref _isLaserTargetPowerSetEnabled, value); }
//======================================
//=============================
//---- 파워 교정 관련
ePowerCalibPhase powerCalibPhase = ePowerCalibPhase.Idle;
bool _isLaserPowerMeasureStartEnabled = true;
public bool IsLaserPowerMeasureStartEnabled { get => _isLaserPowerMeasureStartEnabled; set => SetProperty(ref _isLaserPowerMeasureStartEnabled, value); }
bool _isLaserPowerMeasureStopEnabled = false;
public bool IsLaserPowerMeasureStopEnabled { get => _isLaserPowerMeasureStopEnabled; set => SetProperty(ref _isLaserPowerMeasureStopEnabled, value); }
double _threshold = 0.1; // [W]
public double Threshold { get => _threshold; set => SetProperty(ref _threshold, value); }
private bool _isThresholdSetEnabled;
public bool IsThresholdSetEnabled { get => _isThresholdSetEnabled; set => SetProperty(ref _isThresholdSetEnabled, value); }
bool _isTwoSegment;
public bool IsTwoSegment {
get => _isTwoSegment;
set {
// 교정 옵션을 변경하면, 교정 결과를 초기화(?)함, 다른 교정 결과들도 초기화해야 할까..?
IsModelSolved = false; // Save&Apply 교정모델 업데이트 용..
if (_isTwoSegment != value) {
CheckTwoSegmentIsValid();
SetProperty(ref _isTwoSegment, value);
}
}
}
private bool _isSegmentToggleSwitchEnabled = true; // View 정렬 확인 목적 테스트...false 로 바꿔야 함
public bool IsSegmentToggleSwitchEnabled { get => _isSegmentToggleSwitchEnabled; set => SetProperty(ref _isSegmentToggleSwitchEnabled, value); }
bool _canEstimation = false;
public bool CanEstimation { get => _canEstimation; set => SetProperty(ref _canEstimation, value); }
public bool IsROIConfigured { get; private set; }
string _model1Result = "Y [%] = a * X + b;";
public string Model1Result { get => _model1Result; set => SetProperty(ref _model1Result, value); }
string _model2Result = "Y [%] = a * X + b;";
public string Model2Result { get => _model2Result; set => SetProperty(ref _model2Result, value); }
double[] _estModelParamA = new double[2];
double[] _estModelParamB = new double[2];
private bool _isCopyFromCurrent2NewEnabled = true;
public bool IsCopyFromCurrent2NewEnabled { get => _isCopyFromCurrent2NewEnabled; set => SetProperty(ref _isCopyFromCurrent2NewEnabled, value); }
bool _isSaveApplyEnabled = false;
public bool IsSaveApplyEnabled { get => _isSaveApplyEnabled; set => SetProperty(ref _isSaveApplyEnabled, value); }
bool _isSaveAsCSVEnabled = false;
public bool IsSaveAsCSVEnabled { get => _isSaveAsCSVEnabled; set => SetProperty(ref _isSaveAsCSVEnabled, value); }
public bool IsModelSolved { get; set; } = false;
public bool IsLaserPowerMeasuring { get; set; } = false;
// DataGrid 가 clear 되면, 자동으로 바인딩된 값이 -1 이 됨
public int _newSelectedIndex = -1;
public int NewSelectedIndex {
get {
return _newSelectedIndex;
}
set {
SetProperty(ref _newSelectedIndex, value);
}
}
//=======================================
#endregion
const double _beginPercFor2ndSegmentMethod = 50.0; // 2번째 세그먼트 시작값
const double _warningPercFor2ndSegmentMethod = 25.0; // 절반 범위 (이보다 작으면 에러 처리)
private const double EPS5 = 1.0e-5;
private const int precision1 = 1;
private const int precision3 = 3;
private const int precision4 = 4;
const int delayMSForGraph = 200; // LiveCharts2 그래프 처리에 딜레이가 필요해보임 [ms]
//--- LiveCharts2 관련
private bool _isXAxisWatt;
public bool IsXAxisWatt { get => _isXAxisWatt; set => SetProperty(ref _isXAxisWatt, value); }
private bool _isAxesSwitchEnabled = true; // 테스트 : 원래는 false 로...
public bool IsAxesSwitchEnabled { get => _isAxesSwitchEnabled; set => SetProperty(ref _isAxesSwitchEnabled, value); }
// 데이터 그리드 관련
public ObservableCollection<PowerCalibData> CurrentCalibData { get; set; } = new ObservableCollection<PowerCalibData>();
public ObservableCollection<PowerCalibData> NewCalibData { get; set; } = new ObservableCollection<PowerCalibData>();
// 그래프 인스턴스
public ObservableCollection<ISeries> Series { get; set; }
// X axis
public Axis[] XAxes { get; set; }
// Y axis
public Axis[] YAxes { get; set; }
public SolidColorPaint LegendTextPaint { get; set; } = new SolidColorPaint {
Color = new SKColor(255, 255, 255)
};
private ObservableCollection<ObservablePoint> observableValuesCurrent;
private ObservableCollection<ObservablePoint> observableValuesNew;
private ObservableCollection<ObservablePoint> observableValuesPreProcessing;
private ObservableCollection<ObservablePoint> observableValuesEstimatedModel;
// Plot visible 제어용
public List<PlotCheckBox> PlotList { get; set; } = new List<PlotCheckBox>();
const string AxisXLabel = "Power [%]";
const string AxisYLabel = "Power [W]";
//======================================================================================
// 파워 교정 모델 셀렉터
public List<CalibRadioButton> CalibModelList { get; set; } = new List<CalibRadioButton>();
private CalibRadioButton _selectedCalibModelItem;
public CalibRadioButton SelectedCalibModelItem {
get {
return _selectedCalibModelItem;
}
set {
_selectedCalibModelItem = value;
}
}
IAuroraEnvironment _aurora;
LaserCompact _laser;
PowerMeter _pm30; // Wire 솔더링은 레이저는 2개이지만, 파워미터는 하나만 사용하는 것 같다
// 원본: timer_Meter; 해당 타이머의 역할을 포함함
private readonly DispatcherTimer MonitorTimer;
int delayForTimer = 50; // 디스패처 타이머 interval
Stopwatch _swMonitor = new Stopwatch();
Stopwatch _swTest = new Stopwatch();
bool _isLUKASTEST_TimerInterval = false; // 테스트 필요할 때만 true 로 켜자
//bool _isLUKASTEST_TimerInterval = true;
//===================================================
#region 생성자
// 레이저가 몇개 일지 모르니, ID 리스트를 받아야겠는데... 레이저 교체는 콤보박스를 쓰던가..
[Obsolete]
public UCLaserCompactCalibrationViewModel(IAuroraEnvironment aurora, List<string> deviceIdList, string powerMeterId)
{
CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
CMD_CopyFromCurernt2New = new DelegateCommand(CopyFromCurrent2New);
CMD_LaserTargetPowerSet = new DelegateCommand(LaserTargetPowerSet);
CMD_LaserTargetPowerWattSet = new DelegateCommand(LaserTargetPowerWattSet);
CMD_PlotZoomReset = new DelegateCommand(PlotZoomReset);
CMD_AxesSwitch = new DelegateCommand(PlotAxesSwitch);
CMD_SolveModel = new DelegateCommand(SolveModel);
CMD_SetROI = new DelegateCommand(SetROI);
CMD_PowerMeterSetZero = new DelegateCommand(PowerMeterSetZero);
// --- 파워 측정
CMD_LaserPowerMeasureStart = new DelegateCommand(LaserPowerMeasureStart);
CMD_LaserPowerMeasureStop = new DelegateCommand(LaserPowerMeasureStop);
CMD_SaveApply = new DelegateCommand(SaveApply);
CMD_SaveAsCSV = new DelegateCommand(SaveAsCSV);
_aurora = aurora;
// 기본값은 wire laser 0 번
laserIDs = deviceIdList;
_laser = aurora.DeviceManager.GetDevice(laserIDs[0]) as LaserCompact;
_pm30 = aurora.DeviceManager.GetDevice(powerMeterId) as PowerMeter;
Observable.Merge(
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED),
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED))
.Select(msg => msg.Param as string)
.Where(x => x == _laser.DeviceId).ObserveOnDispatcher()
.Subscribe(x => OnLaserDeviceActiveChanged());
Observable.Merge(
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED),
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED))
.Select(msg => msg.Param as string)
.Where(x => x == _pm30.DeviceId).ObserveOnDispatcher()
.Subscribe(x => OnPowerMeterDeviceActiveChanged());
//int stationIndex = 0;
IsUCViewEnabled = true;
//-------------------------------------------------
observableValuesCurrent = new ObservableCollection<ObservablePoint>();
observableValuesNew = new ObservableCollection<ObservablePoint>();
observableValuesPreProcessing = new ObservableCollection<ObservablePoint>();
observableValuesEstimatedModel = new ObservableCollection<ObservablePoint>();
// 그래프 초기화
Series = new ObservableCollection<ISeries> {
new LineSeries<ObservablePoint> {
Name = "Current",
Stroke = null,
Fill = null,
GeometryFill = new SolidColorPaint(SKColors.Green), // 내부색
//GeometryStroke = new SolidColorPaint(SKColors.Green) {StrokeThickness = 1}, // 윤곽선
GeometryStroke = null,
GeometrySize = 4,
LineSmoothness = 0,
Values = observableValuesCurrent,
// Tooltip formatter: 모두 F3 로...: 축 전환시, X 축의 소수 자릿수를 바꿀 방법이 없네...
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
},
new LineSeries<ObservablePoint> {
Name = "New",
//Fill = new SolidColorPaint(SKColors.Tomato),
//GeometrySize = 4,
Stroke = null,
Fill = null,
GeometryFill = new SolidColorPaint(SKColors.Gold),
//GeometryStroke = new SolidColorPaint(SKColors.Tomato) {StrokeThickness = 1},
GeometryStroke = null,
GeometrySize = 4,
LineSmoothness = 0,
Values = observableValuesNew,
// Tooltip formatter
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
},
new LineSeries<ObservablePoint> {
Name = "Train",
//Fill = new SolidColorPaint(SKColors.Blue),
//GeometrySize = 4,
Stroke = null,
Fill = null,
GeometryFill = new SolidColorPaint(SKColors.Blue),
//GeometryStroke = new SolidColorPaint(SKColors.Blue) {StrokeThickness = 1},
GeometryStroke = null,
GeometrySize = 4,
LineSmoothness = 0,
Values = observableValuesPreProcessing,
// Tooltip formatter
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
},
// plot for estimated model
new LineSeries<ObservablePoint> {
Name = "Estimate",
Stroke = new SolidColorPaint(SKColors.Orange) { StrokeThickness = 1 },
//Stroke = new SolidColorPaint(SKColors.Orange),
Fill = null,
//Fill = new SolidColorPaint(SKColors.Orange),
//Stroke = null,
GeometrySize = 4,
GeometryStroke = null,
GeometryFill = new SolidColorPaint(SKColors.Red),
LineSmoothness = 0,
Values = observableValuesEstimatedModel,
// Tooltip formatter
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
},
};
// X axis
XAxes = new Axis[] {
new Axis {
Name = AxisXLabel,
NamePaint = new SolidColorPaint(SKColors.White),
LabelsPaint = new SolidColorPaint(SKColors.White)
}
};
YAxes = new Axis[] {
new Axis {
Name = AxisYLabel,
NamePaint = new SolidColorPaint(SKColors.White),
LabelsPaint = new SolidColorPaint(SKColors.White),
// 배경 눈금선
SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) {
PathEffect = new DashEffect(new float[] { 3, 3})
}
}
};
//-----------------------------------------------------
// 그래프 개수를 Checkbox 리스트에 추가하자.
var _index = 0;
foreach (var item in Series) {
PlotList.Add(new PlotCheckBox(_index, item.Name, item));
_index++;
}
// 교정모델을 RadioButton 리스트에 추가하자.
CalibModelList.Add(new CalibRadioButton(eModelType.LUT.ToString()));
CalibModelList.Add(new CalibRadioButton(eModelType.Lin1Seg.ToString()));
CalibModelList.Add(new CalibRadioButton(eModelType.Lin2Seg.ToString()));
CalibRadioButton._laser = _laser;
MonitorTimer = new DispatcherTimer();
MonitorTimer.Tick += OnMonitoring;
MonitorTimer.Interval = TimeSpan.FromMilliseconds(delayForTimer); // 50 [ms] => 20 [Hz]
}
#endregion
private void PowerMeterSetZero(object obj)
{
_setPowerMeterZero = true;
}
private void CopyFromCurrent2New(object obj)
{
Log.V("CopyFromCurrent2New is clicked");
var res = MessageUtil.ShowYesNoMessageDialog("Test Mode: Copy (⇒ new)", "Would you like to copy Current to New?");
if (res) { // 복사하기
// 1. 그래프를 Clear 하자...
//-----------------------------------------------------
// Train 과 추정치 그래프를 Off 한다.
var _index = 0;
foreach (var item in PlotList) {
if (_index > 1) {
item.IsSelected = false;
}
_index++;
}
// New 그래프도 Clear...
observableValuesNew.Clear();
observableValuesPreProcessing.Clear();
observableValuesEstimatedModel.Clear();
// 2. X 축은 Percent 으로!!!
if (IsXAxisWatt) {
PlotAxesSwitch(null);
}
// 그래프에 적용되도록 시간 지연을 준다.
Thread.Sleep(delayMSForGraph);
//==========================================
// 새로운 측정데이터 초기화
NewCalibData.Clear();
// New 그래프를 표시하자..
PlotList[1].IsSelected = true;
// 복사: Curret --> New 데이터 표
foreach (var item in CurrentCalibData) {
var _perc = item.PowerPerc;
double _wattValue = Math.Round(item.PowerWatt, precision3);
NewCalibData.Add(new PowerCalibData() {
PowerPerc = _perc,
PowerWatt = _wattValue,
Use = true
}
);
observableValuesNew.Add(new ObservablePoint(_perc, _wattValue)); // 그래프에 추가
}
// UI 컨트롤 enable 처리
SetControlEnable();
// Calib. Solve 를 disable 함
CanEstimation = false;
} else {
Log.V("CopyFromCurrent2New is cancled");
}
}
int _fileIndex = 0;
private void SaveAsCSV(object obj)
{
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
string strFilePath = string.Format($"{recipePath}\\External\\Laser\\LaserCalData_{_laser.DeviceId}_{_fileIndex++}.csv");
if (File.Exists(strFilePath)) File.Delete(strFilePath);
string strSave = "Laser Power[%], Power Meter[W]\r\n";
string[] strSetData = new string[2];
for (int i = 0; i < NewCalibData.Count; i++) {
strSetData[0] = NewCalibData[i].PowerPerc.ToString("F1");
strSetData[1] = NewCalibData[i].PowerWatt.ToString("F3");
// 1번째 인자 strSave 를 이용해서, 문자열을 이어붙임
strSave = string.Format("{0}{1},{2}\r\n", strSave, strSetData[0], strSetData[1]);
}
File.AppendAllText(strFilePath, strSave);
MessageUtil.ShowSaveSuccessDialog();
Log.V("Save as CSV is success");
}
private void SaveApply(object obj)
{
// Power Model Enable 업데이트
stPowerModelEnable powerModelEnableInfo;
if (IsModelSolved) {
// 교정 옵션을 보고 선정...
if (IsTwoSegment) {
powerModelEnableInfo = new stPowerModelEnable(true, false, true);
} else {
powerModelEnableInfo = new stPowerModelEnable(true, true, false);
}
} else {
powerModelEnableInfo = new stPowerModelEnable(true, false, false);
}
// 다이얼로그에 LUT, ModelType 에 대한 선택지를 추가하자...
if (MessageUtilLaserCompact.ShowYesNoMessageDialogWireLaserCalib("Save & Apply", "Please select the Power Calib. type.", powerModelEnableInfo) == false) {
return;
}
//var test = MessageUtilWireLaser.ResultCalibType;
Stopwatch _sw = new Stopwatch();
string strTime = "";
try {
_sw.Restart();
IsSaveApplyEnabled = false;
if (NewCalibData.Count < 50) return;
MonitorTimer.Stop();
_laser._rawConfig.StartDelay = StartDelay;
_laser._rawConfig.MeasureDelay = MeasurementDelay;
_laser._rawConfig.StartPower = Math.Round(BeginPowerPerc, precision1);
_laser._rawConfig.EndPower = Math.Round(EndPowerPerc, precision1);
_laser._rawConfig.StepPerc = Math.Round(MeasurementStepPerc, precision1);
// 저장할 모델 선택
_laser._rawConfig.ModelType = (int)MessageUtilLaserCompact.ResultCalibType;
_laser._rawConfig.ModelParamA0 = Math.Round(_estModelParamA[0], precision3);
_laser._rawConfig.ModelParamB0 = Math.Round(_estModelParamB[0], precision3);
_laser._rawConfig.ModelParamA1 = Math.Round(_estModelParamA[1], precision3);
_laser._rawConfig.ModelParamB1 = Math.Round(_estModelParamB[1], precision3);
strTime = _sw.ElapsedMilliseconds.ToString();
string[] strSetData = new string[2];
// Config 의 PowerValue 비우기..
_laser._rawConfig.PowerValue.Clear();
foreach (var item in NewCalibData) {
_laser._rawConfig.PowerValue.Add(item.PowerPerc.ToString("F1"), item.PowerWatt);
}
// Config json 저장
_aurora.DeviceManager.SaveDeviceConfig(_laser.DeviceId);
Log.I($"[{_laser.DeviceId}] [LASER{_laser.Index}] Calibration Data Saved");
strTime = strTime + "," + _sw.ElapsedMilliseconds.ToString();
MessageUtil.ShowSaveSuccessDialog();
} catch (Exception ex) {
Log.E($"Exception: {ex.Message}");
MessageUtil.ShowSaveFailedDialog();
} finally {
_laser.LoadLaserCalibrationData(); // 레이저 설정 업데이트
RefreshCalibrationData(); // 화면 업데이트
IsSaveApplyEnabled = true;
strTime = strTime + "," + _sw.ElapsedMilliseconds.ToString();
MonitorTimer.Start();
Log.V($"Save elasped time: {strTime} [ms]");
}
}
private void CheckTwoSegmentIsValid()
{
// 예외처리...
if (observableValuesPreProcessing is null || observableValuesPreProcessing.Count == 0) {
Log.W("No Train data");
return;
}
if (IsTwoSegment) { // 2 segment
// 만약 2 segment 옵션 상태라면, Train 데이터의 시작값이 25 [%] 이상인지 검사
// 만약 넘어서면, 데이터가 충분하지 않으므로, 최적화 버튼을 비활성화한다.
if (observableValuesPreProcessing[0].Y > _warningPercFor2ndSegmentMethod) {
// first segment 의 범위가 너무 작다...!!!
Log.W("ROI for 2nd Segment method is too small");
CanEstimation = false;
} else {
CanEstimation = true;
}
} else { // 1 segment
if (IsROIConfigured) {
CanEstimation = true;
}
}
}
private void SetROI(object obj) // New 측정 데이터 기준, Train 그래프 표시
{
// 예외처리...
// 1. 교정할 측정 데이터가 없는 경우
if (NewCalibData is null || NewCalibData.Count == 0) {
Log.W("No New Calibration data");
return;
}
// X 축이 Watt 가 아니면, 축 스위칭한다.
if (IsXAxisWatt != true) {
PlotAxesSwitch(null);
}
// Train 그래프를 visible 로!!!
PlotList[2].IsSelected = true;
// Plot data clear
observableValuesPreProcessing.Clear();
// 1. Data 전처리
// 최적화에 사용할 유효한 데이터를 추출하자...
// ※ 보통 최적화는 train 데이터와 test 데이터로 분류하는데, 여긴 굳이 필요없으니 모든 데이터를 train 데이터로 사용한다.
var _rawData = NewCalibData;
var trainDataSet = from item in _rawData
where item.PowerWatt > Threshold
select (item.PowerWatt, item.PowerPerc);
foreach (var item in trainDataSet) {
observableValuesPreProcessing.Add(new ObservablePoint(item.PowerWatt, item.PowerPerc));
}
IsROIConfigured = true;
// 만약 2 segment 옵션 상태라면, 전처리된 데이터가 50% 을 넘어서는지 검사한다.
// 만약 넘어서면, 최적화 버튼을 비활성화한다.
CheckTwoSegmentIsValid();
}
private void SolveModel(object obj) // New 측정 데이터 기준
{
// 방어 코드 추가... Train 데이터가 없으면 취소
if (observableValuesPreProcessing.Count == 0) {
Log.E("No Train Data");
CanEstimation = false;
return;
}
// 다른 곳에서 데이터 전처리 완료됨, 여기서는 최적화만 수행한다.
// 데이터 전처리가 수행되었는지 체크한다..
if (IsROIConfigured == false) {
SetROI(null);
}
// X 축이 Watt 가 아니면, 축 스위칭한다.
if (IsXAxisWatt != true) {
PlotAxesSwitch(null);
}
// Estimate 그래프를 visible 로!!!
PlotList[3].IsSelected = true;
// 1 segment 인지 2 segment 인지 조건에 따라 최적화 수행...
if (IsTwoSegment != true) { // 1 segment
var xData = observableValuesPreProcessing.Select(x => x.X).Cast<double>().ToArray();
var yData = observableValuesPreProcessing.Select(x => x.Y).Cast<double>().ToArray();
// Math.NET Numerics 의 Fit 함수는 y = a + b*x 기준임.
// y = a * x + b 기준에 맞추기 위해서, 리턴값의 순서를 변경함
(double b, double a) linModel = Fit.Line(xData, yData);
Model1Result = $"Y [%] = {linModel.a:f3} * X + {linModel.b:f3}";
_estModelParamA[0] = linModel.a;
_estModelParamB[0] = linModel.b;
observableValuesEstimatedModel.Clear();
// 추정에서는 입력으로 [W], 출력으로 [%] 를 사용했다.
// 그러나, 그래프를 그리기 위해서는 입력은 [%], 출력을 [W] 로 계산하는게 편하다 ???
// - 왜냐하면, % 의 범위는 정해져있기때문임.... [0, 100] 이고, 0.1 단위임..
// 추정된 모델은 [%] = a * ([W]) + b 이므로, 역계산을 유도하면,
// y - b = a * x
// x = (y - b) / a | x: [W], y: [%]
int maxIter = 1000;
for (int i = 0; i <= maxIter; i++) {
var tempXPerc = i * 0.1;
double tempYWatt = (tempXPerc - linModel.b) / linModel.a;
if (tempYWatt < 0) {
tempYWatt = 0.0;
}
// 그래프 데이터에 저장: x 축: [W], y축: [%]
observableValuesEstimatedModel.Add(new ObservablePoint(Math.Round(tempYWatt, precision3), tempXPerc));
}
} else { // 2 segment
// 전처리된 데이터를 절반으로 나누자...
// Y 값이 [%] 에 해당한다.
var xData1 = observableValuesPreProcessing.Where(x => x.Y < _beginPercFor2ndSegmentMethod).Select(x => x.X).Cast<double>().ToArray();
var xData2 = observableValuesPreProcessing.Where(x => x.Y >= _beginPercFor2ndSegmentMethod).Select(x => x.X).Cast<double>().ToArray();
var yData1 = observableValuesPreProcessing.Where(x => x.Y < _beginPercFor2ndSegmentMethod).Select(x => x.Y).Cast<double>().ToArray();
var yData2 = observableValuesPreProcessing.Where(x => x.Y >= _beginPercFor2ndSegmentMethod).Select(x => x.Y).Cast<double>().ToArray();
Log.I($"Total size: {observableValuesPreProcessing.Count}, x1: {xData1.Length}, x2: {xData2.Length}");
// 모델 최적화
// Math.NET Numerics 의 Fit 함수는 y = a + b*x 기준임.
// y = a * x + b 기준에 맞추기 위해서, 리턴값의 순서를 변경함
(double b, double a) linModel1 = Fit.Line(xData1, yData1);
(double b, double a) linModel2 = Fit.Line(xData2, yData2);
Model1Result = $"Y [%] = {linModel1.a:f3} * X + {linModel1.b:f3}";
Model2Result = $"Y [%] = {linModel2.a:f3} * X + {linModel2.b:f3}";
_estModelParamA[0] = linModel1.a;
_estModelParamB[0] = linModel1.b;
_estModelParamA[1] = linModel2.a;
_estModelParamB[1] = linModel2.b;
// 그래프 비우기
observableValuesEstimatedModel.Clear();
// 추정에서는 입력으로 [W], 출력으로 [%] 를 사용했다.
// 그러나, 그래프를 그리기 위해서는 입력은 [%], 출력을 [W] 로 계산하는게 편하다 ???
// - 왜냐하면, % 의 범위는 정해져있기때문임.... [0, 100] 이고, 0.1 단위임..
// 추정된 모델은 [%] = a * ([W]) + b 이므로, 역계산을 유도하면,
// y - b = a * x
// x = (y - b) / a
// x: [W], y: [%]
int maxIter = 1000;
double tempYWatt = 0.0;
for (int i = 0; i <= maxIter; i++) {
var tempXPerc = i * 0.1;
if (tempXPerc < _beginPercFor2ndSegmentMethod) {
tempYWatt = (tempXPerc - linModel1.b) / linModel1.a;
} else {
tempYWatt = (tempXPerc - linModel2.b) / linModel2.a;
}
if (tempYWatt < 0) {
tempYWatt = 0.0;
}
// 그래프 데이터에 저장: x 축: [W], y축: [%]
observableValuesEstimatedModel.Add(new ObservablePoint(Math.Round(tempYWatt, precision3), tempXPerc));
}
}
IsModelSolved = true;
}
private void PlotAxesSwitch(object obj)
{
// X - Y 스위치
// 1. 데이터 스위칭
// 2. 축 정보 스위칭
// 모든 그래프의 축을 스위칭 !!!
foreach (var plotItem in Series) {
if (plotItem is null || plotItem.Values is null) {
continue;
}
foreach (ObservablePoint plotData in plotItem.Values) {
var _tempX = plotData.X;
plotData.X = plotData.Y;
plotData.Y = _tempX;
}
}
// 축 정보 바꾸기
IsXAxisWatt = !IsXAxisWatt;
if (IsXAxisWatt) { // X - Y 스위칭
XAxes[0].Name = AxisYLabel;
YAxes[0].Name = AxisXLabel;
} else { // 복원
XAxes[0].Name = AxisXLabel;
YAxes[0].Name = AxisYLabel;
}
}
private void PlotZoomReset(object obj)
{
// X, Y zoom, panning reset
XAxes[0].MaxLimit = null;
XAxes[0].MinLimit = null;
YAxes[0].MaxLimit = null;
YAxes[0].MinLimit = null;
}
// 레이저가 변경될 때마다 호출 ???
private void DrawCalibrationData(eInitBy condition)
{
try {
// LUKAS: Setting 부분
StartDelay = _laser._rawConfig.StartDelay;
MeasurementDelay = _laser._rawConfig.MeasureDelay;
BeginPowerPerc = _laser._rawConfig.StartPower;
EndPowerPerc = _laser._rawConfig.EndPower;
MeasurementStepPerc = _laser._rawConfig.StepPerc;
// 교정모델 셀렉터 뷰 세팅
UpdateCalibModelSelector();
// 기존 측정표 비우기
CurrentCalibData.Clear();
switch (condition) {
case eInitBy.LaserActivate: {
// LUKAS 추가.....New 데이터가 있다면, 그래프 표시하기
if (NewCalibData.Count > 50) { // 50 개 정도면 되지 않을까?
// New 그래프를 표시하자..
PlotList[1].IsSelected = true;
foreach (var item in NewCalibData) {
observableValuesNew.Add(new ObservablePoint(item.PowerPerc, item.PowerWatt));
}
// New 데이터가 존재한다면, Calibration 이 가능하도록 뷰 컨트롤 제어...
SetControlEnable();
}
break;
}
case eInitBy.LaserChange: {
// New 측정 데이터를 지운다
NewCalibData.Clear();
// New 데이터가 지워지면, 관련 컨트롤을 disabled 처리한다.
IsThresholdSetEnabled = CanEstimation = false;
IsSaveApplyEnabled = IsSaveAsCSVEnabled = false;
// 기존 데이터만 읽어와서 표 및 그래프에 표시하자 (아래에 공통으로)
break;
}
}
// Current 그래프를 표시하자..
PlotList[0].IsSelected = true;
var _powerCalDataArr = _laser.GetPowerCalData();
foreach (var item in _powerCalDataArr) {
var _perc = Math.Round(item.perc, precision1);
double _wattValue = Math.Round(item.watt, precision3); // LUKAS: ini 에서 읽어온 정보, 4자리수 정확도
CurrentCalibData.Add(new PowerCalibData() {
PowerPerc = _perc,
PowerWatt = _wattValue,
Use = true
}
);
observableValuesCurrent.Add(new ObservablePoint(_perc, _wattValue)); // 그래프에 추가
}
} catch (Exception ex) {
Log.E($"Exception: {ex.Message}");
}
}
void UpdateCalibModelSelector()
{
// 레이저가 변경된건지 검사
if (string.Equals(CalibRadioButton._laser.DeviceId, _laser.DeviceId) == false) {
// 레이저 업데이트
CalibRadioButton._laser = _laser;
}
string curretPowerCalibType = _laser.GetLaserPowerCalibMethod();
foreach (var item in CalibModelList) {
if (string.Equals(item.Id, curretPowerCalibType)) {
// _1. 이 라디오 버튼 선택 및 활성화
item.IsEnabledRadio = true;
item.IsSelected = true;
switch (item.Id) {
case "LUT": {
// 나머지 라디오버튼 비활성화
CalibModelList[1].IsEnabledRadio = false;
CalibModelList[2].IsEnabledRadio = false;
break;
}
case "Lin1Seg": {
// LUT 활성화, 다른 하나는 비활성화
CalibModelList[0].IsEnabledRadio = true;
CalibModelList[2].IsEnabledRadio = false;
break;
}
case "Lin2Seg": {
// LUT 활성화, 다른 하나는 비활성화
CalibModelList[0].IsEnabledRadio = true;
CalibModelList[1].IsEnabledRadio = false;
break;
}
}
// 매칭을 찾았으므로, 나간다.
break;
}
}
}
// LUKAS: 측정된 데이터를 ini 파일에 저장후 ini 파일 정보로, 화면을 다시 업데이트함
// 즉, New 데이터가 Current 데이터가 됨..
private void RefreshCalibrationData()
{
try {
StartDelay = _laser._rawConfig.StartDelay;
MeasurementDelay = _laser._rawConfig.MeasureDelay;
BeginPowerPerc = _laser._rawConfig.StartPower;
EndPowerPerc = _laser._rawConfig.EndPower;
MeasurementStepPerc = _laser._rawConfig.StepPerc;
// 레이저 교정 모델 타입 업데이트
UpdateCalibModelSelector();
// Current 그래프 수정...
observableValuesCurrent.Clear();
// Current 데이터 그리드 수정
CurrentCalibData.Clear();
// X 축은 [W] 로...
if (IsXAxisWatt != true) {
PlotAxesSwitch(null);
}
// 딜레이 주기..
Thread.Sleep(delayMSForGraph);
// 흠, New 데이터 그리드 정보를 이용해도 될 것 같긴 하지만...
var _powerCalDataArr = _laser.GetPowerCalData();
foreach (var item in _powerCalDataArr) {
var _perc = item.perc;
double _wattValue = Math.Round(item.watt, precision3); // LUKAS: ini 에서 읽어온 정보
CurrentCalibData.Add(new PowerCalibData() {
PowerPerc = _perc,
PowerWatt = _wattValue,
Use = true
}
);
// X 축: [W]
observableValuesCurrent.Add(new ObservablePoint(_wattValue, _perc));
}
} catch (Exception ex) {
Log.E($"Exception: {ex.Message}");
}
}
private void InitByLaserActivate()
{
if (_laser.IsActivated) {
// 기존 및 (있을 경우) New 데이터만 표시
InitGraph(eInitBy.LaserActivate);
// 일단 그래프는 다 지웠다.
DrawCalibrationData(eInitBy.LaserActivate);
if (_pm30.IsActivated != true) {
_pm30.Activate();
PowerMeterInfo = "Activated";
}
}
}
private void InitByLaserChange()
{
if (_laser.IsActivated) {
// 모든 그래프 Clear
InitGraph(eInitBy.LaserChange);
// 기존 측정 데이터만 표시
DrawCalibrationData(eInitBy.LaserChange);
if (_pm30.IsActivated != true) {
_pm30.Activate();
PowerMeterInfo = "Activated";
}
}
IsModelSolved = false;
}
private void InitByVisibleChange()
{
// 화면 전환인 경우는 그래프는 그대로 두자
InitGraph(eInitBy.VisibleChange);
// 화면이 전환되었을 뿐이니, 다시 그릴 필요는 없을 것 같지만, 장치는 활성화 되어 있는데, 처음 뷰에 진입시의 드로잉은 필요하다!!
if (CurrentCalibData.Count == 0) { // 만약 기존 데이터그리드가 비어있는 경우, 화면을 그리자 !!!
DrawCalibrationData(eInitBy.VisibleChange);
}
if (_pm30.IsActivated != true) {
_pm30.Activate();
PowerMeterInfo = "Activated";
}
}
private void InitGraph(eInitBy condition)
{
switch (condition) {
case eInitBy.LaserActivate:
case eInitBy.LaserChange: {
// 그래프를 모두 clear 할거면, visible 체크박스도 해제하자...
foreach (var item in PlotList) {
item.IsSelected = false;
}
// 일단 그래프 모두 지우기 ??
observableValuesCurrent.Clear();
observableValuesNew.Clear();
observableValuesPreProcessing.Clear();
observableValuesEstimatedModel.Clear();
// X 축은 [%] 로...
if (IsXAxisWatt) {
PlotAxesSwitch(null);
}
Log.V("Graph is cleared");
// 딜레이를 줘보자..
// 파워 교정 후 레이저 변경 시, 그래프가 남아 있는 문제가 있음...LiveCharts2 도 이벤트(?) 방식이라, 대기가 필요한 것 같아보임
Thread.Sleep(delayMSForGraph);
break;
}
case eInitBy.VisibleChange: { // 화면 전환인 경우, 아무런 처리를 하지 않는다.
// Nothing
break;
}
}
}
private void OnLaserDeviceActiveChanged()
{
if (_laser.IsActivated) {
IsEnabled = true;
if (IsVisible) {
InitByLaserActivate();
}
} else {
IsEnabled = false;
// 메모리 해제를 여기에 넣을까??
// invisible 상태에서만 메모리를 해제하자!!
if (IsVisible == false) {
InitGraph(eInitBy.LaserActivate);
// 데이터 그리드에 있는 데이터도 해제하자..
if (NewCalibData.Count != 0) {
NewCalibData.Clear();
}
if (CurrentCalibData.Count != 0) {
CurrentCalibData.Clear();
}
}
}
}
private void OnPowerMeterDeviceActiveChanged()
{
if (_pm30.IsActivated) {
PowerMeterInfo = "Activated";
} else {
PowerMeterInfo = "Not activated";
}
}
private void OnMonitoring(object sender, EventArgs e)
{
if (!_laser.IsActivated) {
return;
}
UpdateActualLaserPower();
UpdatePowerMeter();
// 파워 측정 프로세스
LaserPowerMeasuring();
// LUKAS_TEST : 타이머 시간 모니터링
if (_isLUKASTEST_TimerInterval) {
Debug.WriteLine($"OnMonitoring: {_swTest.ElapsedMilliseconds} [ms] elapsed");
_swTest.Restart();
}
}
private async void LaserPowerMeasureStart(object obj)
{
if (!_pm30.IsActivated || PowerMeterInfo.Length < 1) {
Log.E("PowerMeter Connection Error");
return;
}
// 파워 측정이 시작되었으므로, 특정 view 컨트롤을 비활성화!!
SetControlEnable(false);
//-----------------------------------------------------
// 그래프 처리 관련
// Train 과 추정치 그래프를 Off 한다.
var _index = 0;
foreach (var item in PlotList) {
if (_index > 1) {
item.IsSelected = false;
}
_index++;
}
// New 그래프도 초기화...
observableValuesNew.Clear();
observableValuesPreProcessing.Clear();
observableValuesEstimatedModel.Clear();
// 2. X 축은 Percent 으로!!!
if (IsXAxisWatt) {
PlotAxesSwitch(null);
}
// 타이머의 인터벌을 측정 딜레이로 변경하자.
MonitorTimer.Interval = TimeSpan.FromMilliseconds(MeasurementDelay);
// 그래프가 적용되도록 시간 지연을 준다.
//Thread.Sleep(delayMSForGraph);
await Task.Delay(delayMSForGraph); // UI 스레드로 리턴되고, 여기서 대기..
//==========================================
// LUKAS: 새로운 측정데이터 초기화
NewCalibData.Clear();
// 차라리 시작 딜레이를 여기에 넣자..!!!
LaserTargetPower = BeginPowerPerc;
_laser.SetOutputPower(LaserTargetPower);
await Task.Delay(StartDelay);
// async/await 에 의해서, UI 스레드 동기화가 이뤄짐..
powerCalibPhase = ePowerCalibPhase.Init; // 시작
//--- 메뉴 전환 불가하도록 !!!
SetMenuEnable(false);
IsLaserPowerMeasuring = true;
}
private void SetMenuEnable(bool IsEnabled)
{
/*
if (IsEnabled) {
_aurora.EventPublisher.Publish(new EventMessage("", AuroraSysEvent.SYS_REQ_MAIN_MENU_ENABLE, IsEnabled));
} else {
_aurora.EventPublisher.Publish(new EventMessage("", AuroraSysEvent.SYS_REQ_MAIN_MENU_ENABLE, IsEnabled));
}
*/
_aurora.EventPublisher.Publish(new EventMessage("", AuroraSysEvent.SYS_REQ_MAIN_MENU_ENABLE, IsEnabled));
}
// 여기 UI 스레드인 것 같다.
// 여기서 Thread.Sleep 를 걸어버리면, DispatcherTimer 도 같이 블럭이 걸리는 문제 발생
// 일단 async/await 로 대기 사용
private async void LaserPowerMeasureStop(object obj)
{
// 중복으로 못 누르도록 비활성화..
IsLaserPowerMeasureStopEnabled = false;
// 파워 측정 버튼의 비동기 로직이 끝날때까지 대기 후, 진행 필요
// 즉, LaserPowerMeasureStart 메서드의 마지막 코드가 실행될 때까지 대기
while (IsLaserPowerMeasuring == false) {
await Task.Delay(delayForTimer);
}
if (IsLaserPowerMeasuring) {
// 타이머에서 파워 측정 종료하도록 flag 세팅
IsLaserPowerMeasuring = false;
// 파워 측정이 정상 종료될때까지 대기...
while (powerCalibPhase != ePowerCalibPhase.Idle) {
//Thread.Sleep(10);
await Task.Delay(delayForTimer);
}
// 레이저 파워를 0 으로 초기화...
LaserTargetPower = 0.0;
_laser.SetOutputPower(LaserTargetPower);
//--- 메뉴 전환 가능하도록 !!!
SetMenuEnable(true);
}
// 타이머의 인터벌을 원복하자
MonitorTimer.Interval = TimeSpan.FromMilliseconds(delayForTimer);
IsLaserTargetPowerSetEnabled = IsLaserTargetPowerWattSetEnabled = true;
IsLaserPowerMeasureStartEnabled = IsLaserPowerMeasureStopEnabled = true;
// 측정 정지 버튼 비활성화
IsLaserPowerMeasureStopEnabled = false;
// 측정 시작에 의해 변경된 뷰 컨트롤 enable 상태 복구
SetControlEnable();
powerCalibPhase = ePowerCalibPhase.Idle;
}
double fPowerMeterValue = 0.0;
//private async void LaserPowerMeasuring()
private void LaserPowerMeasuring()
{
// 측정 도중에 취소 대응
// - 파워 측정 도중 취소 버튼이 눌렸을 때!!
if (IsLaserPowerMeasuring == false) {
//_laser.SetOutputPower(0.0);
powerCalibPhase = ePowerCalibPhase.Idle;
return;
}
if (_pm30.IsActivated != true) {
Log.E("PowerMeter is not activated");
powerCalibPhase = ePowerCalibPhase.Finish;
} else {
fPowerMeterValue = Math.Round(PowerMeterWatt, precision3);
}
switch (powerCalibPhase) {
case ePowerCalibPhase.Idle: // 대기 상태
break;
case ePowerCalibPhase.Init: // Start
// 파워 측정이 시작되었으므로, 특정 view 컨트롤을 비활성화!! // 시작 버튼으로 이동...
//SetControlEnable(false);
// New 그래프를 표시하자...
PlotList[1].IsSelected = true;
// LUKAS: 윈도우의 시작 Power 값 (설정값)
LaserTargetPower = BeginPowerPerc;
_laser.SetOutputPower(LaserTargetPower);
// 시작 딜레이...
//Thread.Sleep(StartDelay);
//await Task.Delay(StartDelay); // 시작 딜레이는 굳이 필요 없을 것 같은데...?
powerCalibPhase = ePowerCalibPhase.Collecting;
break;
case ePowerCalibPhase.Collecting: // Measure Data Add
//Thread.Sleep(MeasurementDelay);
NewCalibData.Add(
new PowerCalibData() {
PowerPerc = LaserTargetPower,
PowerWatt = fPowerMeterValue,
Use = true
}
);
// 데이터 그리드를 추가한 행을 보이도록 해보자...될까..??
++NewSelectedIndex;
// New 그래프에 추가
observableValuesNew.Add(new ObservablePoint(LaserTargetPower, fPowerMeterValue));
// LUKAS: 종료조건: 교정범위 마지막에 도달했는지 여부
// 소수점 첫째자리까지만: xx.x
// 실수 비교이므로, eps 추가..
if (Math.Round(LaserTargetPower, 1) + EPS5 >= EndPowerPerc) powerCalibPhase = ePowerCalibPhase.Finish;
else powerCalibPhase = ePowerCalibPhase.LaserPowerSet;
break;
case ePowerCalibPhase.LaserPowerSet: // Laser Send
LaserTargetPower = Math.Round(LaserTargetPower + MeasurementStepPerc, precision1);
_laser.SetOutputPower(LaserTargetPower);
powerCalibPhase = ePowerCalibPhase.Collecting;
// 파워미터값이 switch 문 밖에 있으므로, 측정 딜레이는 여기로 와야 함
//Thread.Sleep(MeasurementDelay);
//await Task.Delay(MeasurementDelay); // 타이머 인터벌로 커버해야 할 것 같은데....
break;
case ePowerCalibPhase.Finish: // Flow End
_laser.SetOutputPower(0.0);
SetControlEnable();
powerCalibPhase = ePowerCalibPhase.Idle;
// 타이머의 인터벌을 원복하자
MonitorTimer.Interval = TimeSpan.FromMilliseconds(delayForTimer);
if (IsLaserPowerMeasuring) {
//--- 메뉴 전환 가능하도록 !!!
SetMenuEnable(true);
IsLaserPowerMeasuring = false;
}
break;
}
}
private void UpdateActualLaserPower()
{
// LUKAS: 팝업 윈도우의 Power Meter 부분: 레이저의 power 값을 가져옴
if (!_laser.IsNetworkError()) {
var _laserStatus = _laser.GetLaserStatusInfo();
LaserActualPower = _laserStatus.PowerValue * 0.1;
} else {
LaserActualPower = 0.0;
}
}
private void UpdatePowerMeter()
{
if (!_swMonitor.IsRunning) _swMonitor.Restart();
if (_pm30.IsActivated && !_pm30.IsCommError) { // 정상 상태
// LUKAS: calibration 팝업 윈도우의 Power Meter 부분 : 이거 연결 초기 부분으로 이동시키는게 좋을 것 같긴 한데...
// Activate 코드 아래에 배치하면 될 것 같음...
//PowerMeterInfo = string.Format("[ {0} ] {1}", _pm30.ComPort, _pm30.SerialNo);
IsPowerMeterZeroEnabled = IsLaserPowerMeasuring ? false : true;
// Sensor Zero
if (_setPowerMeterZero) {
_pm30.SetZero();
_setPowerMeterZero = false;
}
// Query 현재 파워
if (_pm30.IsSimul) { // 시뮬 모드
// 레이저 actual power 를 넣어서, [W] 로 변환..
// 구현한 모델은 Y [%] = a * [W] + b 이므로,
var _laserActualPowerWatt = _laser.Power2Watt(LaserActualPower);
_pm30.SimSetActualPowerWatt(_laserActualPowerWatt);
}
// LUKAS: 파워 미터의 파워값 [W]
_pm30.Measure(out double measuredPower);
PowerMeterWatt = measuredPower;
_swMonitor.Restart();
} else { // 파워미터 에러 상태....
//PowerMeterInfo = "";
PowerMeterWatt = 0.0;
IsPowerMeterZeroEnabled = false;
if (_pm30.IsActivated == false) {
PowerMeterInfo = "Not activated";
} else {
PowerMeterInfo = "ComPort Error";
}
// 1초는 너무 빈번한거 같으니, 4초로 변경...
if (_swMonitor.ElapsedMilliseconds >= 4000) {
if (_pm30.IsActivated == false) {
_pm30.Activate();
PowerMeterInfo = "Activated";
}
// DeviceMonitoring 쪽으로 옮길까 하다가, PowerMeterInfo 업데이트때문에 여기에 처리함...
else {
var res = _pm30.Reconnect();
if (res) {
PowerMeterInfo = "Activated";
Log.I("ResetComPortError success");
} else {
Log.E("ResetComPortError failed");
}
}
_swMonitor.Restart();
}
}
}
private void VisibleChanged(object obj) // 화면 전환 시 동작하는 곳
{
IsVisible = (bool)obj;
if (IsVisible) {
Log.D("Visible: True");
if (_laser.IsActivated) {
InitByVisibleChange();
}
// 파워미터 정보
if (_pm30.IsActivated == false) {
PowerMeterInfo = "Not activated";
}
// 초기화...
if (MonitorTimer.IsEnabled == false) {
MonitorTimer.Start();
}
} else {
Log.D("Visible: False");
if (MonitorTimer.IsEnabled) {
MonitorTimer.Stop();
}
// 화면에 안보이니, 그래프 등 리소스를 없애야 할까???
if (_swMonitor.IsRunning) {
_swMonitor.Stop();
}
if (_swTest.IsRunning) {
_swTest.Stop();
}
// 파워 교정이 종료되면, 파워미터를 비활성화
if (_pm30.IsActivated) {
_pm30.Deactivate();
PowerMeterInfo = "Not activated";
IsPowerMeterZeroEnabled = false;
}
}
}
// 버튼 조작
private void LaserTargetPowerWattSet(object obj)
{
// [W] --> [%] 변환
LaserTargetPower = _laser.Watt2Power(LaserTargetPowerWatt);
_laser.SetOutputPower(LaserTargetPower);
Log.I($"[{_laser.DeviceId}] : [{LaserTargetPowerWatt:f1} [W] ({LaserTargetPower:f1} [%])]");
}
private void LaserTargetPowerSet(object obj)
{
_laser.SetOutputPower(LaserTargetPower);
Log.I($"[{_laser.DeviceId}] : [{LaserTargetPower:f1}]");
}
// 주로 파워 측정 중
private void SetControlEnable(bool IsEnable = true)
{
if (IsEnable) {
IsLaserTargetPowerSetEnabled = true;
IsLaserTargetPowerWattSetEnabled = true;
IsLaserPowerMeasureStartEnabled = true;
IsLaserPowerMeasureStopEnabled = false;
IsLaserDeviceSwitchEnabled = true;
IsSaveApplyEnabled = IsSaveAsCSVEnabled = true;
IsThresholdSetEnabled = IsSegmentToggleSwitchEnabled = true;
IsAxesSwitchEnabled = true;
IsCopyFromCurrent2NewEnabled = true;
} else { // 비활성화
IsLaserTargetPowerSetEnabled = false;
IsLaserTargetPowerWattSetEnabled = false;
IsLaserPowerMeasureStartEnabled = false;
IsLaserPowerMeasureStopEnabled = true;
// 레이저 교체 불가
IsLaserDeviceSwitchEnabled = false;
IsSaveApplyEnabled = IsSaveAsCSVEnabled = false;
IsThresholdSetEnabled = IsSegmentToggleSwitchEnabled = false;
// 축 스위칭 불가
IsAxesSwitchEnabled = false;
IsCopyFromCurrent2NewEnabled = false;
// Calib Solver 버튼
CanEstimation = false;
}
}
}
}