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 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 CurrentCalibData { get; set; } = new ObservableCollection(); public ObservableCollection NewCalibData { get; set; } = new ObservableCollection(); // 그래프 인스턴스 public ObservableCollection 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 observableValuesCurrent; private ObservableCollection observableValuesNew; private ObservableCollection observableValuesPreProcessing; private ObservableCollection observableValuesEstimatedModel; // Plot visible 제어용 public List PlotList { get; set; } = new List(); const string AxisXLabel = "Power [%]"; const string AxisYLabel = "Power [W]"; //====================================================================================== // 파워 교정 모델 셀렉터 public List CalibModelList { get; set; } = new List(); 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 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(); observableValuesNew = new ObservableCollection(); observableValuesPreProcessing = new ObservableCollection(); observableValuesEstimatedModel = new ObservableCollection(); // 그래프 초기화 Series = new ObservableCollection { new LineSeries { 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 { 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 { 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 { 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().ToArray(); var yData = observableValuesPreProcessing.Select(x => x.Y).Cast().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().ToArray(); var xData2 = observableValuesPreProcessing.Where(x => x.Y >= _beginPercFor2ndSegmentMethod).Select(x => x.X).Cast().ToArray(); var yData1 = observableValuesPreProcessing.Where(x => x.Y < _beginPercFor2ndSegmentMethod).Select(x => x.Y).Cast().ToArray(); var yData2 = observableValuesPreProcessing.Where(x => x.Y >= _beginPercFor2ndSegmentMethod).Select(x => x.Y).Cast().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; } } } }