2026-01-26 14:51:28 +09:00

334 lines
14 KiB
C#

using Aurora;
using Aurora.Api;
using Aurora.View;
using MathNet.Numerics.Statistics;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Input;
using System.Windows.Threading;
namespace AuroraPLC.Views
{
public class UCPLCDeviceViewModel : NotifyPropertyChangedBase
{
private static readonly Logger Log = new Logger("PLC Device View");
private int _numBitInput;
public int NumBitInput { get => _numBitInput; set => SetProperty(ref _numBitInput, value); }
private int _numBitOutput;
public int NumBitOutput { get => _numBitOutput; set => SetProperty(ref _numBitOutput, value); }
private int _numMBitInput;
public int NumMBitInput { get => _numMBitInput; set => SetProperty(ref _numMBitInput, value); }
private int _numMBitOutput;
public int NumMBitOutput { get => _numMBitOutput; set => SetProperty(ref _numMBitOutput, value); }
private int _numWordInput;
public int NumWordInput { get => _numWordInput; set => SetProperty(ref _numWordInput, value); }
private int _numWordOutput;
public int NumWordOutput { get => _numWordOutput; set => SetProperty(ref _numWordOutput, value); }
private int _numDWordInput;
public int NumDWordInput { get => _numDWordInput; set => SetProperty(ref _numDWordInput, value); }
private int _numDWordOutput;
public int NumDWordOutput { get => _numDWordOutput; set => SetProperty(ref _numDWordOutput, value); }
private double _updateRate;
public double UpdateRate { get => _updateRate; set => SetProperty(ref _updateRate, value); }
private string _networkType;
public string NetworkType { get => _networkType; set => SetProperty(ref _networkType, value); }
private string _operationMode;
public string OperationMode { get => _operationMode; set => SetProperty(ref _operationMode, value); }
private string _elapsedTime;
public string ElapsedTime { get => _elapsedTime; set => SetProperty(ref _elapsedTime, value); }
TimeSpan tsElaspedTime;
private int _numReadBitFail;
public int NumReadBitFail { get => _numReadBitFail; set => SetProperty(ref _numReadBitFail, value); }
private int _numWriteBitFail;
public int NumWriteBitFail { get => _numWriteBitFail; set => SetProperty(ref _numWriteBitFail, value); }
private int _numReadWordFail;
public int NumReadWordFail { get => _numReadWordFail; set => SetProperty(ref _numReadWordFail, value); }
private int _numWriteWordFail;
public int NumWriteWordFail { get => _numWriteWordFail; set => SetProperty(ref _numWriteWordFail, value); }
private int _numReconnection;
public int NumReconnection { get => _numReconnection; set => SetProperty(ref _numReconnection, value); }
// PLC 통신 통계
private int _numOfMaxRateSample = 100;
public int NumOfMaxRateSample { get => _numOfMaxRateSample;
set {
if (value != _numOfMaxRateSample) {
if (value < 10) { // 최소 개수: 10
value = 10;
}
// 큐 새로 생성..
RateQueueStatus = rateDataCollecting;
MonitorTimer.Stop();
// csv 저장 불가
IsSaveAsCSVVisible = false;
rateHzBuffer = new Queue<double>(value);
MonitorTimer.Start();
SetProperty(ref _numOfMaxRateSample, value);
}
}
}
private double _MeanRateHz;
public double MeanRateHz { get => _MeanRateHz; set => SetProperty(ref _MeanRateHz, value); }
private double _MinRateHz;
public double MinRateHz { get => _MinRateHz; set => SetProperty(ref _MinRateHz, value); }
private double _StdRateHz;
public double StdRateHz { get => _StdRateHz; set => SetProperty(ref _StdRateHz, value); }
private string _RateQueueStatus;
public string RateQueueStatus { get => _RateQueueStatus; set => SetProperty(ref _RateQueueStatus, value); }
const string rateDataCollecting = "Data collecting...";
const string rateDataReady = "Data is ready";
private bool _isStatPauseChecked = true;
public bool IsStatPauseChecked { get => _isStatPauseChecked; set => SetProperty(ref _isStatPauseChecked, value); }
private Queue<double> rateHzBuffer;
private bool _isSaveAsCSVVisible = false;
public bool IsSaveAsCSVVisible { get => _isSaveAsCSVVisible; set => SetProperty(ref _isSaveAsCSVVisible, value); }
public ICommand CMD_SaveAsCSV { get; }
public ICommand CMD_VisibleChanged { get; }
//private IDeviceEntry DeviceManager;
private readonly PLCBase _plcBase;
private readonly DispatcherTimer MonitorTimer;
const double monitorTimerMS = 50.0; // 50 [ms] -> 20 [Hz]
#region 생성자
public UCPLCDeviceViewModel(IAuroraEnvironment aurora, PLCBase plc)
{
//DeviceManager = aurora.DeviceManager;
CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
CMD_SaveAsCSV = new DelegateCommand(SaveAsCSV);
_plcBase = plc;
// 초기화...
NetworkType = _plcBase.NetworkType.ToString();
// 운전 모드
OperationMode = _plcBase.IsSimul ? "Simul" : "Real";
// 연, 일, 시, 분, 초, 밀리초
ElapsedTime = String.Format("00Y,000D,00:00:00.00 [s]");
NumBitInput = _plcBase.BitInputSize;
NumBitOutput = _plcBase.BitOutputSize;
NumMBitInput = _plcBase.MBitInputSize;
NumMBitOutput = _plcBase.MBitOutputSize;
NumWordInput = _plcBase.WordInputWordSize;
NumWordOutput = _plcBase.WordOutputWordSize;
NumDWordInput = _plcBase.DWordInputWordSize;
NumDWordOutput = _plcBase.DWordOutputWordSize;
// 통계 기능은 기본적으로는 off 상태
IsStatPauseChecked = true;
rateHzBuffer = new Queue<double>(NumOfMaxRateSample);
RateQueueStatus = rateDataCollecting;
// csv 저장 불가
IsSaveAsCSVVisible = false;
MonitorTimer = new DispatcherTimer();
MonitorTimer.Tick += OnMonitoring;
MonitorTimer.Interval = TimeSpan.FromMilliseconds(monitorTimerMS); // 50 [ms] => 20 [Hz]
}
#endregion
private void VisibleChanged(object obj) // 화면 전환 시 동작하는 곳
{
bool visible = (bool)obj;
if (visible) {
// 초기화...
NumBitInput = _plcBase.BitInputSize;
NumBitOutput = _plcBase.BitOutputSize;
NumMBitInput = _plcBase.MBitInputSize;
NumMBitOutput = _plcBase.MBitOutputSize;
NumWordInput = _plcBase.WordInputWordSize;
NumWordOutput = _plcBase.WordOutputWordSize;
NumDWordInput = _plcBase.DWordInputWordSize;
NumDWordOutput = _plcBase.DWordOutputWordSize;
MonitorTimer.Start();
} else {
MonitorTimer.Stop();
// csv 저장 불가
IsSaveAsCSVVisible = false;
// 통계 기능 Off
IsStatPauseChecked = true;
rateHzBuffer.Clear();
RateQueueStatus = rateDataCollecting;
}
}
int elapsedYear;
int elapsedDay;
StringBuilder sbElaspedTime = new StringBuilder(30);
private void OnMonitoring(object sender, EventArgs e)
{
if (!_plcBase.IsActivated || _plcBase.IsSimul) {
return;
}
NumReadBitFail = (int)_plcBase.PLCUpdatorInfo.NumberOfRBFail;
NumWriteBitFail = (int)_plcBase.PLCUpdatorInfo.NumberOfWBFail;
NumReadWordFail = (int)_plcBase.PLCUpdatorInfo.NumberOfRWFail;
NumWriteWordFail = (int)_plcBase.PLCUpdatorInfo.NumberOfWWFail;
NumReconnection = (int)_plcBase.PLCUpdatorInfo.NumberOfReconnection;
tsElaspedTime = _plcBase._dataUpdateTimer.TotalRunningTime;
// year 와 day 를 분리하자..
elapsedYear = tsElaspedTime.Days / 365;
elapsedDay = tsElaspedTime.Days - (elapsedYear * 365);
sbElaspedTime.Clear();
if (elapsedYear != 0) {
sbElaspedTime.AppendFormat($"{elapsedYear,2:##}Y,{elapsedDay,3:###}D,{tsElaspedTime.Hours:00}:{tsElaspedTime.Minutes:00}:{tsElaspedTime.Seconds:00}.{tsElaspedTime.Milliseconds:00} [s]");
//ElapsedTime = String.Format("{0,2:##}Y,{1,3:###}D,{2:00}:{3:00}:{4:00}.{5:00} [s]",
// elapsedYear, elapsedDay, tsElaspedTime.Hours, tsElaspedTime.Minutes, tsElaspedTime.Seconds, tsElaspedTime.Milliseconds);
} else if (elapsedDay != 0) {
sbElaspedTime.AppendFormat($"{elapsedDay,3:###}D,{tsElaspedTime.Hours:00}:{tsElaspedTime.Minutes:00}:{tsElaspedTime.Seconds:00}.{tsElaspedTime.Milliseconds:00} [s]");
//ElapsedTime = String.Format("{0,3:###}D,{1:00}:{2:00}:{3:00}.{4:00} [s]",
// elapsedDay, tsElaspedTime.Hours, tsElaspedTime.Minutes, tsElaspedTime.Seconds, tsElaspedTime.Milliseconds);
} else {
sbElaspedTime.AppendFormat($"{tsElaspedTime.Hours:00}:{tsElaspedTime.Minutes:00}:{tsElaspedTime.Seconds:00}.{tsElaspedTime.Milliseconds:00} [s]");
//ElapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00} [s]",
// tsElaspedTime.Hours, tsElaspedTime.Minutes, tsElaspedTime.Seconds, tsElaspedTime.Milliseconds);
}
ElapsedTime = sbElaspedTime.ToString();
UpdateRate = (1000.0 / _plcBase._dataUpdateTimer.ActualUpdateRateMS); // [Hz]
// PLC 통신 속도 통계
if (IsStatPauseChecked == false) { // 통계 동작 중...
if (rateHzBuffer.Count < NumOfMaxRateSample) {
rateHzBuffer.Enqueue(UpdateRate); // 추가
RateQueueStatus = $"{rateDataCollecting} ({rateHzBuffer.Count})";
if (rateHzBuffer.Count == NumOfMaxRateSample) {
RateQueueStatus = rateDataReady;
// csv 저장 가능
IsSaveAsCSVVisible = true;
}
} else {
// 통계 계산...
MeanRateHz = rateHzBuffer.Sum() / rateHzBuffer.Count;
MinRateHz = rateHzBuffer.Min();
StdRateHz = Statistics.StandardDeviation(rateHzBuffer);
// 가장 오래된 것을 제거하고, 새로운 값 추가
rateHzBuffer.Dequeue();
rateHzBuffer.Enqueue(UpdateRate);
}
}
/*
// 개발용: PLC method 테스트 : 커밋 반영시에는 주석 처리 필요
bool needTest = false;
if (needTest) {
// Test1: WriteBitBlock by bool array
//bool[] bit_t1 = { true, true, true };
//_plcBase.WriteBitBlock(0x30, bit_t1.Length, bit_t1);
// Test2: WriteBitBlock by short value
short bit_short = 0x00FF;
int bit_size = 5;
_plcBase.WriteBitBlock(0x30, bit_size, bit_short);
// Test3: ReadBitBlock
int bit_t3_size = 3;
var bit_t3 = _plcBase.ReadBitBlock(0x30, bit_t3_size);
// Test4: ReadBitBlockArr
var bit_t4 = _plcBase.ReadBitBlockArr(0x30, bit_t3_size);
//var test = 100;
//// Test1 : ReadDWord
//int addrOffset = 0x40;
//var temp = _plc.ReadDWord(addrOffset, 10000);
//float tempFloat = (float)temp;
////var test1 = 1;
//// Test2 : ReadDWord
//var temp1 = _plc.ReadDWord(addrOffset);
//// Test3 : WriteWord
//short w1 = 100;
//addrOffset = 0;
//_plc.WriteWord(addrOffset, w1);
//// Test4: WriteDWord
//var t1 = 1.123;
//var t2 = 12.345;
//var t3 = 123.246;
//_plc.WriteLaserMoveData(t1, t2, t3);
}
*/
//============================
}
int _fileIndex = 0;
private void SaveAsCSV(object obj)
{
// 데이터 저장 정지 !!!
IsStatPauseChecked = true;
// 모니터 타이머에서 rate 데이터 갱신이 안되도록 시간 지연 추가
Thread.Sleep((int)(2.0 * monitorTimerMS));
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
string strFilePath = string.Format($"{recipePath}\\External\\PLC\\PLCNetRateData_{_fileIndex++}.csv");
if (File.Exists(strFilePath)) File.Delete(strFilePath);
string strSave = "Net Rate[Hz]";
foreach (var item in rateHzBuffer) {
strSave = string.Format("{0}\n{1}", strSave, item.ToString("F3"));
}
try { // 저장 경로가 무효할 때의 예외 처리용
File.AppendAllText(strFilePath, strSave);
MessageUtil.ShowSaveSuccessDialog();
Log.V("Save as CSV is success");
} catch (Exception ex) {
Log.E($"Save is failed: {ex.Message}");
}
}
}
}