2838 lines
118 KiB
C#
2838 lines
118 KiB
C#
using JinYuan.Helper;
|
||||
|
|
using JinYuan.MES.Models;
|
|||
|
|
using JinYuan.Models;
|
|||
|
|
using JinYuan.VirtualDataLibrary;
|
|||
|
|
using Language;
|
|||
|
|
using MiniExcelLibs;
|
|||
|
|
using PLC;
|
|||
|
|
using PLC.PLCAlarm;
|
|||
|
|
using PLCCommunication;
|
|||
|
|
using PLCCommunication.Common;
|
|||
|
|
using PLCCommunication.Common.DataConvert;
|
|||
|
|
using SqlSugar;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Concurrent;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Diagnostics;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Net;
|
|||
|
|
using System.Net.NetworkInformation;
|
|||
|
|
using System.Reflection;
|
|||
|
|
using System.Text;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using System.Windows.Forms;
|
|||
|
|
using static System.Runtime.CompilerServices.RuntimeHelpers;
|
|||
|
|
using AlarmData = JinYuan.Models.AlarmData;
|
|||
|
|
using DataType = PLC.DataType;
|
|||
|
|
|
|||
|
|
namespace JinYuan.ControlCenters
|
|||
|
|
{
|
|||
|
|
public partial class ControlCenter : IDisposable
|
|||
|
|
{
|
|||
|
|
#region 单例
|
|||
|
|
private static readonly object uiLock = new object();
|
|||
|
|
private static ControlCenter _instance;
|
|||
|
|
public static ControlCenter Instance
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
lock (uiLock)
|
|||
|
|
{
|
|||
|
|
if (_instance == null)
|
|||
|
|
_instance = new ControlCenter();
|
|||
|
|
return _instance;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 变量
|
|||
|
|
private Dictionary<string, Func<JYResult<byte[]>, PlcGroup, Task>> _stationFunctions;
|
|||
|
|
private Dictionary<string, Thread> _groupThreads;
|
|||
|
|
private readonly Dictionary<PlcReadWriteBase, List<PlcDevice>> _plcDeviceMapping = new Dictionary<PlcReadWriteBase, List<PlcDevice>>();
|
|||
|
|
private readonly Dictionary<PlcReadWriteBase, PLCAlarmReader> _plcAlarmReaders = new Dictionary<PlcReadWriteBase, PLCAlarmReader>();
|
|||
|
|
|
|||
|
|
private CancellationTokenSource _cts;
|
|||
|
|
private Task _heartbeatTask;
|
|||
|
|
private const int HeartbeatInterval = 1000;
|
|||
|
|
|
|||
|
|
private readonly object _statusLock = new object();
|
|||
|
|
private Dictionary<string, int> _groupStatus = new Dictionary<string, int>();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定时器-定时刷新状态和数据
|
|||
|
|
/// </summary>
|
|||
|
|
private System.Timers.Timer _timerRefreshData;
|
|||
|
|
|
|||
|
|
#region 独立任务队列
|
|||
|
|
private readonly ConcurrentQueue<Action> _taskQueue = new ConcurrentQueue<Action>();
|
|||
|
|
private Thread _workerThread;
|
|||
|
|
private readonly ManualResetEvent _taskEvent = new ManualResetEvent(false);
|
|||
|
|
private bool _isWorkerRunning = true;
|
|||
|
|
|
|||
|
|
// 队列监控
|
|||
|
|
private int _maxQueueSize = 1000;
|
|||
|
|
private DateTime _lastQueueWarningTime = DateTime.MinValue;
|
|||
|
|
private int _totalProcessedCount = 0;
|
|||
|
|
private int _totalRejectedCount = 0;
|
|||
|
|
|
|||
|
|
// 线程健康监控
|
|||
|
|
private DateTime _lastHeartbeat = DateTime.Now;
|
|||
|
|
private int _stuckDetectCount = 0;
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 资源管理
|
|||
|
|
//private readonly SemaphoreSlim _dbSemaphore = new SemaphoreSlim(10); // 限制数据库并发
|
|||
|
|
private readonly SemaphoreSlim _plcWriteSemaphore = new SemaphoreSlim(5); // 限制PLC写入并发
|
|||
|
|
private bool _isDisposed = false;
|
|||
|
|
private System.Timers.Timer _healthMonitorTimer;
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 2、初始化函数
|
|||
|
|
private ControlCenter()
|
|||
|
|
{
|
|||
|
|
_groupThreads = new Dictionary<string, Thread>();
|
|||
|
|
_cts = new CancellationTokenSource();
|
|||
|
|
|
|||
|
|
if (CommonMethods.plcDevices == null)
|
|||
|
|
{
|
|||
|
|
CommonMethods.plcDevices = new List<PlcReadWriteBase>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动独立任务处理线程
|
|||
|
|
StartWorkerThread();
|
|||
|
|
InitializeDevicesFromExcel();
|
|||
|
|
GetAlarmData();
|
|||
|
|
InitFunc();
|
|||
|
|
|
|||
|
|
_timerRefreshData = new System.Timers.Timer(1000);
|
|||
|
|
_timerRefreshData.Elapsed += Timer_RefreshData_Elapsed;
|
|||
|
|
_timerRefreshData.Start();
|
|||
|
|
|
|||
|
|
Thread thread = new Thread(ShowStateEven_Run);
|
|||
|
|
thread.IsBackground = true;
|
|||
|
|
thread.Start();
|
|||
|
|
|
|||
|
|
string[] clrPath = new[] { CommonMethods.strAlarmLogspath, CommonMethods.strMesLogspath, CommonMethods.strWattrMeterpath, @"D:\APILog\Logs\" };
|
|||
|
|
short[] seltWay = new[] { (short)0, (short)0, (short)0, (short)1 };
|
|||
|
|
DeleteLog.Start(clrPath, seltWay);
|
|||
|
|
|
|||
|
|
if (!CommonMethods.IsRun)
|
|||
|
|
{
|
|||
|
|
CommonMethods.ResetEventRecv.Reset();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动健康监控
|
|||
|
|
StartHealthMonitor();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 开始健康监控
|
|||
|
|
/// </summary>
|
|||
|
|
private void StartHealthMonitor()
|
|||
|
|
{
|
|||
|
|
_healthMonitorTimer = new System.Timers.Timer(30000); // 每30秒检查一次
|
|||
|
|
_healthMonitorTimer.Elapsed += HealthMonitorCallback;
|
|||
|
|
_healthMonitorTimer.AutoReset = true;
|
|||
|
|
_healthMonitorTimer.Start();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void HealthMonitorCallback(object sender, System.Timers.ElapsedEventArgs e)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var now = DateTime.Now;
|
|||
|
|
var timeSinceLastHeartbeat = now - _lastHeartbeat;
|
|||
|
|
|
|||
|
|
// 检查工作线程是否卡死
|
|||
|
|
if (timeSinceLastHeartbeat.TotalSeconds > 60)
|
|||
|
|
{
|
|||
|
|
_stuckDetectCount++;
|
|||
|
|
LogHelper.Instance.WriteError($"健康监控:工作线程可能卡死,最后心跳:{_lastHeartbeat}, 卡死计数:{_stuckDetectCount}");
|
|||
|
|
|
|||
|
|
if (_stuckDetectCount >= 3)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"健康监控:工作线程已卡死,尝试恢复...");
|
|||
|
|
RecoverWorkerThread();
|
|||
|
|
_stuckDetectCount = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_stuckDetectCount = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录系统状态
|
|||
|
|
int queueSize = _taskQueue.Count;
|
|||
|
|
if (queueSize > _maxQueueSize * 0.8)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"健康监控:任务队列接近溢出,当前大小:{queueSize}/{_maxQueueSize}, 已处理:{_totalProcessedCount}, 已拒绝:{_totalRejectedCount}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录线程状态
|
|||
|
|
int activeThreads = Process.GetCurrentProcess().Threads.Count;
|
|||
|
|
LogHelper.Instance.WriteLog($"健康监控 - 队列:{queueSize}, 线程数:{activeThreads}, 内存:{GC.GetTotalMemory(false) / 1024 / 1024}MB");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"健康监控异常:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 恢复工作线程
|
|||
|
|
/// </summary>
|
|||
|
|
private void RecoverWorkerThread()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
_isWorkerRunning = false;
|
|||
|
|
_taskEvent.Set();
|
|||
|
|
|
|||
|
|
if (_workerThread != null && _workerThread.IsAlive)
|
|||
|
|
{
|
|||
|
|
_workerThread.Join(5000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清空队列
|
|||
|
|
while (_taskQueue.TryDequeue(out _)) { }
|
|||
|
|
|
|||
|
|
// 重新启动
|
|||
|
|
_isWorkerRunning = true;
|
|||
|
|
StartWorkerThread();
|
|||
|
|
|
|||
|
|
LogHelper.Instance.WriteLog("健康监控:工作线程已恢复");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"恢复工作线程失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 独立工作线程
|
|||
|
|
/// </summary>
|
|||
|
|
private void StartWorkerThread()
|
|||
|
|
{
|
|||
|
|
_workerThread = new Thread(delegate ()
|
|||
|
|
{
|
|||
|
|
var lastProgressLog = DateTime.Now;
|
|||
|
|
|
|||
|
|
while (_isWorkerRunning && !(_cts?.IsCancellationRequested ?? true))
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 更新心跳
|
|||
|
|
_lastHeartbeat = DateTime.Now;
|
|||
|
|
|
|||
|
|
// 等待事件,添加超时防止永久阻塞
|
|||
|
|
bool hasEvent = _taskEvent.WaitOne(1000);
|
|||
|
|
|
|||
|
|
if (!hasEvent)
|
|||
|
|
{
|
|||
|
|
// 超时继续循环,保持心跳
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量处理任务,提高效率
|
|||
|
|
int batchSize = Math.Min(10, _taskQueue.Count);
|
|||
|
|
for (int i = 0; i < batchSize; i++)
|
|||
|
|
{
|
|||
|
|
if (_taskQueue.TryDequeue(out Action task))
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 添加超时控制
|
|||
|
|
var taskWithTimeout = Task.Run(() => task());
|
|||
|
|
if (!taskWithTimeout.Wait(30000)) // 30秒超时
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"任务执行超时(30秒),已跳过");
|
|||
|
|
}
|
|||
|
|
_totalProcessedCount++;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"业务执行异常:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 定期记录处理统计
|
|||
|
|
if ((DateTime.Now - lastProgressLog).TotalMinutes >= 5)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteLog($"任务处理统计 - 已处理:{_totalProcessedCount}, 已拒绝:{_totalRejectedCount}, 当前队列:{_taskQueue.Count}");
|
|||
|
|
lastProgressLog = DateTime.Now;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"工作线程异常:{ex.Message}");
|
|||
|
|
Thread.Sleep(1000);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
_workerThread.IsBackground = true;
|
|||
|
|
_workerThread.Priority = ThreadPriority.AboveNormal;
|
|||
|
|
_workerThread.Name = "ControlCenterWorker";
|
|||
|
|
_workerThread.Start();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 安全添加任务到队列
|
|||
|
|
/// </summary>
|
|||
|
|
private bool EnqueueTask(Action task, string taskName = "")
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 检查队列是否已满
|
|||
|
|
if (_taskQueue.Count >= _maxQueueSize)
|
|||
|
|
{
|
|||
|
|
_totalRejectedCount++;
|
|||
|
|
|
|||
|
|
// 限制日志频率,避免日志风暴
|
|||
|
|
if ((DateTime.Now - _lastQueueWarningTime).TotalSeconds > 30)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"任务队列已满({_taskQueue.Count}/{_maxQueueSize}),拒绝任务:{taskName}");
|
|||
|
|
_lastQueueWarningTime = DateTime.Now;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_taskQueue.Enqueue(task);
|
|||
|
|
_taskEvent.Set();
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"入队失败:{ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 初始化工位方法
|
|||
|
|
/// </summary>
|
|||
|
|
private void InitFunc()
|
|||
|
|
{
|
|||
|
|
_stationFunctions = new Dictionary<string, Func<JYResult<byte[]>, PlcGroup, Task>>();
|
|||
|
|
_stationFunctions.Add("物流线料盘来料", TrayFeedingStation);
|
|||
|
|
_stationFunctions.Add("物流线料盘进站", CheckTrayStation);
|
|||
|
|
_stationFunctions.Add("电池进站", FeedingStation);
|
|||
|
|
_stationFunctions.Add("电池与拖盘绑定", PackStation);
|
|||
|
|
_stationFunctions.Add("强制放行", ForceOutStation);
|
|||
|
|
_stationFunctions.Add("料框码解绑", UnBindTrayStation);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 启动心跳及工位读取线程
|
|||
|
|
/// </summary>
|
|||
|
|
public async Task StartAsync()
|
|||
|
|
{
|
|||
|
|
// 先清理旧的线程
|
|||
|
|
StopAndCleanOldThreads();
|
|||
|
|
|
|||
|
|
// 创建新线程
|
|||
|
|
CreatePLCThreads();
|
|||
|
|
_heartbeatTask = StartHeartbeatCheck(_cts.Token);
|
|||
|
|
await Task.CompletedTask;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 停止并清理旧线程
|
|||
|
|
/// </summary>
|
|||
|
|
private void StopAndCleanOldThreads()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
foreach (var kvp in _groupThreads)
|
|||
|
|
{
|
|||
|
|
var thread = kvp.Value;
|
|||
|
|
if (thread != null && thread.IsAlive)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
thread.Join(1000);
|
|||
|
|
}
|
|||
|
|
catch (ThreadInterruptedException) { }
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_groupThreads.Clear();
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"清理旧线程异常:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 加载配置文件
|
|||
|
|
/// </summary>
|
|||
|
|
private void InitializeDevicesFromExcel()
|
|||
|
|
{
|
|||
|
|
string gongwei = CommonMethods.sysConfig.StationNo;
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, "开始加载配置参数……".Translated());
|
|||
|
|
string deviceConfigPath = CommonMethods.deviceConfigPath;//Path.Combine(configFolderPath, "Device.xlsx");
|
|||
|
|
string groupConfigPath = CommonMethods.groupConfigPath;//Path.Combine(configFolderPath, $"Group_{gongwei}.xlsx");
|
|||
|
|
string variableConfigPath = CommonMethods.variableConfigPath;//Path.Combine(configFolderPath, $"Variable_{gongwei}.xlsx");
|
|||
|
|
//string plcVariableConfigPath = Path.Combine(configFolderPath, "PLCVariable.xlsx");
|
|||
|
|
|
|||
|
|
if (!File.Exists(deviceConfigPath) || !File.Exists(groupConfigPath) || !File.Exists(variableConfigPath))
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError("配置文件不存在");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var deviceConfigs = MiniExcel.Query<PlcDevice>(deviceConfigPath).ToList();
|
|||
|
|
var groupConfigs = MiniExcel.Query<PlcGroup>(groupConfigPath).ToList();
|
|||
|
|
var variableConfigs = MiniExcel.Query<PlcVariable>(variableConfigPath).ToList();
|
|||
|
|
|
|||
|
|
foreach (var deviceConfig in deviceConfigs.Where(d => d.IsActive))
|
|||
|
|
{
|
|||
|
|
IPEndPoint iep = new IPEndPoint(IPAddress.Parse(deviceConfig.IPAddress), deviceConfig.Port);
|
|||
|
|
PlcReadWriteBase plc = PLCFactory.CreatePLC(GetPLCTypeEnum(deviceConfig.PLCType), deviceConfig.PlcNum, $"PLC_{deviceConfig.PlcNum}", iep);
|
|||
|
|
|
|||
|
|
string GongWei = CommonMethods.sysConfig.GongWei;
|
|||
|
|
var stationNo = CommonMethods.sysConfig.StationNo;
|
|||
|
|
deviceConfig.Cts = new CancellationTokenSource();
|
|||
|
|
deviceConfig.GroupList = groupConfigs
|
|||
|
|
.Where(g => g.IsActive && g.PlcNum == deviceConfig.PlcNum && ((g.PlcNum == 1 && g.StationName == stationNo) || (g.PlcNum == 2 && g.StationName == GongWei)) || g.GroupName == "MES心跳")
|
|||
|
|
.ToList();
|
|||
|
|
|
|||
|
|
foreach (var group in deviceConfig.GroupList)
|
|||
|
|
{
|
|||
|
|
group.VarList = variableConfigs
|
|||
|
|
.Where(v => v.GroupName == group.GroupName && v.PlcNum == deviceConfig.PlcNum && v.StationName == group.StationName)
|
|||
|
|
.ToList();
|
|||
|
|
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
_groupStatus[group.GroupName] = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_plcDeviceMapping[plc] = new List<PlcDevice> { deviceConfig };
|
|||
|
|
CommonMethods.plcDevices.Add(plc);
|
|||
|
|
|
|||
|
|
// 初始化 PLCAlarm Reader报警读取
|
|||
|
|
var alarmReader = new PLCAlarmReader(
|
|||
|
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AlarmConfig.csv"),
|
|||
|
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AlarmRecords"),
|
|||
|
|
RecordType.RecordWhenFinish,
|
|||
|
|
false
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
//alarmReader.PLCAlarm_New += AlrmReader_PLCAlarm_New;
|
|||
|
|
//alarmReader.PLCAlarm_Remove += AlrmReader_PLCAlarm_Remove;
|
|||
|
|
_plcAlarmReaders[plc] = alarmReader;
|
|||
|
|
|
|||
|
|
CommonMethods.plcStatuses[plc] = new Dictionary<string, object>
|
|||
|
|
{
|
|||
|
|
["OldStatus"] = (short)0,
|
|||
|
|
["StatusCount"] = 0,
|
|||
|
|
["GuidAlarm"] = string.Empty,
|
|||
|
|
["Status"] = new List<bool> { false, false, false, false }
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"加载配置失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, "配置文件加载完成".Translated());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 3、PLC读写
|
|||
|
|
|
|||
|
|
#region 创建PLC线程 几个工位就几个线程
|
|||
|
|
/// <summary>
|
|||
|
|
/// 按工位读取线程
|
|||
|
|
/// </summary>
|
|||
|
|
private void CreatePLCThreads()
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < CommonMethods.plcDevices.Count; i++)
|
|||
|
|
{
|
|||
|
|
PlcReadWriteBase plc = CommonMethods.plcDevices[i];
|
|||
|
|
int plcIndex = i; // 捕获索引
|
|||
|
|
|
|||
|
|
if (_plcDeviceMapping.TryGetValue(plc, out var devList))
|
|||
|
|
{
|
|||
|
|
foreach (PlcDevice dev in devList)
|
|||
|
|
{
|
|||
|
|
foreach (PlcGroup group in dev.GroupList)
|
|||
|
|
{
|
|||
|
|
if (group.IsActive)
|
|||
|
|
{
|
|||
|
|
string key = dev.PlcNum + "-" + group.GroupName;
|
|||
|
|
|
|||
|
|
// 如果已存在同名线程,先停止
|
|||
|
|
if (_groupThreads.ContainsKey(key))
|
|||
|
|
{
|
|||
|
|
var oldThread = _groupThreads[key];
|
|||
|
|
if (oldThread != null && oldThread.IsAlive)
|
|||
|
|
{
|
|||
|
|
try { oldThread.Join(500); } catch { }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Thread t = new Thread(ReadPLCWorkModel);
|
|||
|
|
t.IsBackground = true;
|
|||
|
|
t.Priority = ThreadPriority.Highest;
|
|||
|
|
t.Name = $"PLC_{key}";
|
|||
|
|
_groupThreads[key] = t;
|
|||
|
|
|
|||
|
|
var param = new Tuple<PlcGroup, int>(group, plcIndex);
|
|||
|
|
t.Start(param);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
LogHelper.Instance.WriteLog($"PLC线程创建完成,共{_groupThreads.Count}个线程");
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 循环读取PLC工位状态
|
|||
|
|
/// <summary>
|
|||
|
|
/// 读取PLC循环工位
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="plcModel"></param>
|
|||
|
|
private void ReadPLCWorkModel(object parameter)
|
|||
|
|
{
|
|||
|
|
Tuple<PlcGroup, int> param = parameter as Tuple<PlcGroup, int>;
|
|||
|
|
if (param == null) return;
|
|||
|
|
|
|||
|
|
PlcGroup group = param.Item1;
|
|||
|
|
int plcIndex = param.Item2;
|
|||
|
|
|
|||
|
|
// 添加重连计数器
|
|||
|
|
int consecutiveErrorCount = 0;
|
|||
|
|
const int maxConsecutiveErrors = 10;
|
|||
|
|
int lastLogSecond = 0; // 用于限制日志频率
|
|||
|
|
|
|||
|
|
while (_cts != null && !_cts.Token.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 检查系统运行状态
|
|||
|
|
if (!CommonMethods.IsRun)
|
|||
|
|
{
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
consecutiveErrorCount = 0;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PlcReadWriteBase plc = CommonMethods.plcDevices[plcIndex];
|
|||
|
|
if (plc == null || !plc.IsConnected)
|
|||
|
|
{
|
|||
|
|
consecutiveErrorCount++;
|
|||
|
|
if (consecutiveErrorCount > maxConsecutiveErrors && lastLogSecond != DateTime.Now.Second)
|
|||
|
|
{
|
|||
|
|
lastLogSecond = DateTime.Now.Second;
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-PLC状态】{group.GroupName} | PLC未连接 | 连续失败次数={consecutiveErrorCount}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
consecutiveErrorCount = 0;
|
|||
|
|
if (!CommonMethods.sysConfig.WuLiu && group.GroupName == "物流线料盘来料")
|
|||
|
|
{
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重点监视物流线料盘进站
|
|||
|
|
bool isCriticalStation = group.GroupName == "物流线料盘进站" || group.GroupName == "物流线料盘来料";
|
|||
|
|
|
|||
|
|
// 读取触发信号
|
|||
|
|
var trigResult = ReadWithTimeout(plc, group, 2000);
|
|||
|
|
if (trigResult == null || !trigResult.IsSuccess)
|
|||
|
|
{
|
|||
|
|
if (isCriticalStation && lastLogSecond != DateTime.Now.Second)
|
|||
|
|
{
|
|||
|
|
lastLogSecond = DateTime.Now.Second;
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-读取失败】{group.GroupName} | 读取触发信号失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Thread.Sleep(50);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
short trig = trigResult.Content;
|
|||
|
|
int status = 0;
|
|||
|
|
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
if (_groupStatus.ContainsKey(group.GroupName))
|
|||
|
|
status = _groupStatus[group.GroupName];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录触发信号(仅对关键工站)
|
|||
|
|
if (isCriticalStation && trig != 0)
|
|||
|
|
{
|
|||
|
|
StationDiagnostics.LogTrigger(group.GroupName, trig, status);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (trig == 1 && status == 0)
|
|||
|
|
{
|
|||
|
|
if (isCriticalStation)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-信号触发】{group.GroupName} | 检测到触发信号 | 开始处理...");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
JYResult<byte[]> data = plc.ReadValue<JYResult<byte[]>>(
|
|||
|
|
group.VarList[1].VarAddress,
|
|||
|
|
DataType.ArrByte,
|
|||
|
|
Convert.ToUInt16(group.VarList[1].OffsetOrLength)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (data != null && _stationFunctions.ContainsKey(group.GroupName))
|
|||
|
|
{
|
|||
|
|
Func<JYResult<byte[]>, PlcGroup, Task> func = _stationFunctions[group.GroupName];
|
|||
|
|
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
_groupStatus[group.GroupName] = 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用安全的入队方法
|
|||
|
|
string groupName = group.GroupName;
|
|||
|
|
DateTime triggerTime = DateTime.Now;
|
|||
|
|
|
|||
|
|
bool enqueued = EnqueueTask(() =>
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
StationDiagnostics.LogTaskStart(groupName);
|
|||
|
|
// 记录入队到开始执行的延迟
|
|||
|
|
var queueDelay = DateTime.Now - triggerTime;
|
|||
|
|
if (queueDelay.TotalMilliseconds > 500)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-队列延迟】{groupName} | 入队到执行延迟={queueDelay.TotalMilliseconds:F0}ms");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加超时控制
|
|||
|
|
var task = func(data, group);
|
|||
|
|
if (!task.Wait(30000))
|
|||
|
|
{
|
|||
|
|
StationDiagnostics.LogTimeout(groupName, 30);
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-业务超时】{groupName} | 执行超过30秒");
|
|||
|
|
|
|||
|
|
// 超时后强制完成,避免状态卡死
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
if (_groupStatus.ContainsKey(groupName) && _groupStatus[groupName] == 1)
|
|||
|
|
{
|
|||
|
|
WriteFinishToPLC(group, plcIndex);
|
|||
|
|
_groupStatus[groupName] = 0;
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-强制恢复】{groupName} | 超时后强制重置状态");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
StationDiagnostics.LogTaskComplete(groupName, true);
|
|||
|
|
LogHelper.Instance.WriteLog($"【业务完成】{groupName} | 总耗时={(DateTime.Now - triggerTime).TotalMilliseconds:F0}ms");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
StationDiagnostics.LogTaskComplete(groupName, false, ex.Message);
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-异常】{groupName} | {ex.Message}");
|
|||
|
|
|
|||
|
|
// 异常时也要重置状态
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
if (_groupStatus.ContainsKey(groupName) && _groupStatus[groupName] == 1)
|
|||
|
|
{
|
|||
|
|
_groupStatus[groupName] = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, groupName);
|
|||
|
|
|
|||
|
|
if (!enqueued)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-入队失败】{groupName} | 队列已满,任务被拒绝");
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
_groupStatus[group.GroupName] = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
StationDiagnostics.LogTaskEnqueued(groupName, _taskQueue.Count);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (trig == 0 && status == 1)
|
|||
|
|
{
|
|||
|
|
if (isCriticalStation)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-信号下降】{group.GroupName} | 触发信号已复位 | 清理状态");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
_groupStatus[group.GroupName] = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ClearFinishFlags(group, plcIndex);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Thread.Sleep(50);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"PLC读取异常:{ex.Message}");
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 带超时的PLC读取(新增)
|
|||
|
|
/// </summary>
|
|||
|
|
private JYResult<short> ReadWithTimeout(PlcReadWriteBase plc, PlcGroup group, int timeoutMs)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var task = Task.Run(() => plc.ReadValue<JYResult<short>>(group.VarList[0].VarAddress, DataType.Short));
|
|||
|
|
if (task.Wait(timeoutMs))
|
|||
|
|
{
|
|||
|
|
return task.Result;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"PLC读取超时:{group.GroupName}");
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void WriteFinishToPLC(PlcGroup group, int plcIndex)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
PlcReadWriteBase plc = CommonMethods.plcDevices[plcIndex];
|
|||
|
|
if (plc == null || !plc.IsConnected) return;
|
|||
|
|
|
|||
|
|
// 使用信号量限制并发写入
|
|||
|
|
if (_plcWriteSemaphore.Wait(1000))
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
switch (group.GroupName)
|
|||
|
|
{
|
|||
|
|
case "物流线料盘来料":
|
|||
|
|
case "物流线料盘进站":
|
|||
|
|
plc.WriteInt16(group.VarList[5].VarAddress, 1);
|
|||
|
|
plc.WriteInt16(group.VarList[4].VarAddress, 1);
|
|||
|
|
break;
|
|||
|
|
case "电池进站":
|
|||
|
|
plc.WriteInt16(group.VarList[5].VarAddress, 1);
|
|||
|
|
break;
|
|||
|
|
case "电池与拖盘绑定":
|
|||
|
|
plc.WriteInt16(group.VarList[8].VarAddress, 1);
|
|||
|
|
plc.WriteInt16(group.VarList[7].VarAddress, 1);
|
|||
|
|
break;
|
|||
|
|
case "料框码解绑":
|
|||
|
|
plc.WriteInt16(group.VarList[2].VarAddress, 1);
|
|||
|
|
break;
|
|||
|
|
case "强制放行":
|
|||
|
|
plc.WriteInt16(group.VarList[2].VarAddress, 1);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
_plcWriteSemaphore.Release();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"获取PLC写入信号量超时:{group.GroupName}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"写入PLC失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 清空PLC响应标志
|
|||
|
|
/// </summary>
|
|||
|
|
private void ClearFinishFlags(PlcGroup group, int plcIndex)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
PlcReadWriteBase plc = CommonMethods.plcDevices[plcIndex];
|
|||
|
|
if (plc == null || !plc.IsConnected) return;
|
|||
|
|
|
|||
|
|
if (_plcWriteSemaphore.Wait(500))
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
switch (group.GroupName)
|
|||
|
|
{
|
|||
|
|
case "电池与拖盘绑定":
|
|||
|
|
plc.WriteInt16(group.VarList[8].VarAddress, 0);
|
|||
|
|
plc.WriteInt16(group.VarList[7].VarAddress, 0);
|
|||
|
|
break;
|
|||
|
|
case "电池进站":
|
|||
|
|
plc.WriteInt16(group.VarList[5].VarAddress, 0);
|
|||
|
|
break;
|
|||
|
|
case "物流线料盘来料":
|
|||
|
|
case "物流线料盘进站":
|
|||
|
|
//plc.WriteInt16(group.VarList[4].VarAddress, 0);
|
|||
|
|
plc.WriteInt16(group.VarList[5].VarAddress, 0);
|
|||
|
|
break;
|
|||
|
|
case "料框码解绑":
|
|||
|
|
plc.WriteInt16(group.VarList[2].VarAddress, 0);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
_plcWriteSemaphore.Release();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 开始停止联机事件
|
|||
|
|
/// <summary>
|
|||
|
|
/// 开始联机
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
|
|||
|
|
public async Task<bool> ConnectAllPlcsAsync()
|
|||
|
|
{
|
|||
|
|
bool allConnected = true;
|
|||
|
|
if (CommonMethods.plcDevices.Count <= 0)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, "无PLC配置信息".Translated() + "," + "请检查配置文件".Translated());
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (_plcDeviceMapping.TryGetValue(plc, out var plcDevices))
|
|||
|
|
{
|
|||
|
|
foreach (var deviceConfig in plcDevices)
|
|||
|
|
{
|
|||
|
|
string msg = "";
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 使用 Task.Run 来在后台线程执行连接操作
|
|||
|
|
bool isConnected = await Task.Run(delegate ()
|
|||
|
|
{
|
|||
|
|
// 设置一个连接超时
|
|||
|
|
//using var cts = new CancellationTokenSource(10000);
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
return plc.Connect(ref msg);
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
msg = "连接超时";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
deviceConfig.IsConnected = isConnected;
|
|||
|
|
plc.IsConnected = isConnected;
|
|||
|
|
|
|||
|
|
if (!isConnected)
|
|||
|
|
{
|
|||
|
|
allConnected = false;
|
|||
|
|
LogHelper.Instance.WriteLog($"连接PLC失败: {deviceConfig.PlcNum}, 错误: {msg}");
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, "连接PLC失败".Translated() + $": {deviceConfig.PlcNum}, " + "错误".Translated() + $": {msg}");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
await SendConfigToPLCAsync(plc);
|
|||
|
|
await SendUserRightToPLCAsync(plc);
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, "成功连接PLC".Translated() + $": {deviceConfig.PlcNum}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
allConnected = false;
|
|||
|
|
LogHelper.Instance.WriteLog($"连接PLC时发生异常: {deviceConfig.PlcNum}, 错误: {ex.Message}");
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, "连接PLC时发生异常".Translated() + $": {deviceConfig.PlcNum}" + "错误".Translated() + $": {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return allConnected;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 断开链接
|
|||
|
|
/// </summary>
|
|||
|
|
public void DisconnectAllPlcs()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (_cts != null && !_cts.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
_cts.Cancel();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (plc.IsConnected)
|
|||
|
|
{
|
|||
|
|
plc.Disconnect();
|
|||
|
|
plc.IsConnected = false;
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, "已断开与PLC".Translated() + $"[{plc.PlcID}]" + "的链接".Translated());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.WriteLine($"断开PLC {plc.PlcID} 异常:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.WriteLine($"断开连接异常: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region MES
|
|||
|
|
/// <summary>
|
|||
|
|
/// Mes心跳
|
|||
|
|
/// </summary>
|
|||
|
|
private void UpdateMesHeartBeat()
|
|||
|
|
{
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
while (!_cts.Token.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.IsRun)//CommonMethods.mesConfig.isUpMes && CommonMethods.mesConfig.IsLoginMesOK &&
|
|||
|
|
{
|
|||
|
|
bool isOnline = FastPing();
|
|||
|
|
CommonMethods.mesConfig.isConnected = isOnline;
|
|||
|
|
|
|||
|
|
if (!isOnline)
|
|||
|
|
{
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (plc.IsConnected && plc.Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
plc.WriteInt16("D80", 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//if (Ping() == true)
|
|||
|
|
//{
|
|||
|
|
// CommonMethods.mesConfig.isConnected = true;
|
|||
|
|
// string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping成功,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort}";
|
|||
|
|
// TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
|
|||
|
|
//}
|
|||
|
|
//else//Ping失败,提示检查线路。
|
|||
|
|
//{
|
|||
|
|
// int count = 0;
|
|||
|
|
// bool mesOnLine = false;
|
|||
|
|
// do//重复3次
|
|||
|
|
// {
|
|||
|
|
// Thread.Sleep(1000);
|
|||
|
|
// if (Ping() == true) { mesOnLine = true; break; }
|
|||
|
|
// count++;
|
|||
|
|
// }
|
|||
|
|
// while (count < 3);
|
|||
|
|
|
|||
|
|
// if (!mesOnLine)
|
|||
|
|
// {
|
|||
|
|
// CommonMethods.mesConfig.isConnected = false;
|
|||
|
|
// if (CommonMethods.plcDevices.Count() > 0)
|
|||
|
|
// {
|
|||
|
|
// for (int i = 0; i < CommonMethods.plcDevices.Count(); i++)
|
|||
|
|
// {
|
|||
|
|
// if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1")
|
|||
|
|
// {
|
|||
|
|
// CommonMethods.plcDevices[i].WriteInt16("D80", (short)1);
|
|||
|
|
|
|||
|
|
// string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping失败,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort}";
|
|||
|
|
// TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping异常,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort},原因:{ex.ToString()}";
|
|||
|
|
TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
|
|||
|
|
}
|
|||
|
|
Thread.Sleep(1000);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Ping 检查网络线路问题
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public bool FastPing()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
string ip = CommonMethods.mesConfig.MESURLAddressIP;
|
|||
|
|
using var _ping = new Ping();
|
|||
|
|
int timeout = 500;
|
|||
|
|
var reply = _ping.Send(ip, timeout);
|
|||
|
|
return reply.Status == IPStatus.Success;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping异常,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort},原因:{ex.ToString()}";
|
|||
|
|
TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 4、心跳和状态检查
|
|||
|
|
/// <summary>
|
|||
|
|
/// 心跳检测
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="cancellationToken"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private async Task StartHeartbeatCheck(CancellationToken cancellationToken)
|
|||
|
|
{
|
|||
|
|
while (!cancellationToken.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.IsRun)
|
|||
|
|
{
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (_plcDeviceMapping.TryGetValue(plc, out var plcDevices))
|
|||
|
|
{
|
|||
|
|
foreach (var plcDevice in plcDevices)
|
|||
|
|
{
|
|||
|
|
if (plcDevice.IsActive && plcDevice.IsHeartBeat)
|
|||
|
|
{
|
|||
|
|
await CheckHeartbeatForDevice(plc, plcDevice);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
await Task.Delay(HeartbeatInterval, cancellationToken);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="plc"></param>
|
|||
|
|
/// <param name="plcDevice"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private async Task CheckHeartbeatForDevice(PlcReadWriteBase plc, PlcDevice plcDevice)
|
|||
|
|
{
|
|||
|
|
if (plcDevice.IsConnected)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
JYResult result = await Task.Run(() => plc.ReadValue<JYResult<short>>(plcDevice.HeartBeat, DataType.Short));
|
|||
|
|
if (result.IsSuccess)
|
|||
|
|
{
|
|||
|
|
plcDevice.ErrorTimes = 0;
|
|||
|
|
await Task.Run(() => plc.WriteValue(plcDevice.HeartBeat, (short)1, DataType.Short));
|
|||
|
|
plcDevice.IsConnected = true;
|
|||
|
|
plc.IsConnected = true;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
|
|||
|
|
plcDevice.ErrorTimes++;
|
|||
|
|
if (plcDevice.ErrorTimes >= plcDevice.AllowErrorTimes)
|
|||
|
|
{
|
|||
|
|
plcDevice.IsConnected = false;
|
|||
|
|
plc.IsConnected = false;
|
|||
|
|
LogHelper.Instance.WriteLog($"编号:[{plcDevice.PlcNum}]的PLC链接断开:{plcDevice.ErrorTimes}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
await ReconnectDevice(plc, plcDevice);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
private async Task ReconnectDevice(PlcReadWriteBase plc, PlcDevice plcDevice)
|
|||
|
|
{
|
|||
|
|
if (!plcDevice.IsFirstConnect)
|
|||
|
|
{
|
|||
|
|
await Task.Delay(plcDevice.ReConnectTime);
|
|||
|
|
plc.Disconnect();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string msg = "";
|
|||
|
|
var connectResult = await Task.Run(() => plc.Connect(ref msg));
|
|||
|
|
plcDevice.IsConnected = connectResult;
|
|||
|
|
LogHelper.Instance.WriteLog(connectResult
|
|||
|
|
? $"PLC编号:{plcDevice.PlcNum}{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接成功{msg}!"
|
|||
|
|
: $"[{plcDevice.PlcNum}]#PLC{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接失败{msg}!");
|
|||
|
|
|
|||
|
|
plcDevice.IsFirstConnect = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 定时刷新数据
|
|||
|
|
|
|||
|
|
private int TotalNum = 0;
|
|||
|
|
private int TotalNum1 = 0;
|
|||
|
|
private int Regular = 0;
|
|||
|
|
private bool _isUploadingEnergy = false;
|
|||
|
|
private bool _isGettingEnergy = false;
|
|||
|
|
|
|||
|
|
private void Timer_RefreshData_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 防止定时器重入
|
|||
|
|
if (Regular >= 300 && !_isUploadingEnergy)
|
|||
|
|
{
|
|||
|
|
_isUploadingEnergy = true;
|
|||
|
|
Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
UploadEquEnergy();
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
_isUploadingEnergy = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
Regular = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (TotalNum > 4)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.sysConfig.StationNo == "1" && !_isGettingEnergy)
|
|||
|
|
{
|
|||
|
|
_isGettingEnergy = true;
|
|||
|
|
Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
GetEnergyData();
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
_isGettingEnergy = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else if (!_isGettingEnergy)
|
|||
|
|
{
|
|||
|
|
_isGettingEnergy = true;
|
|||
|
|
Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
GetEnergyDataFormPlc();
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
_isGettingEnergy = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Task.Run(() => Refresh12HourProdQty());
|
|||
|
|
GetDefectTypeQty();
|
|||
|
|
ReadPLCComm();
|
|||
|
|
|
|||
|
|
Task.Run(() => GetASqlAlarmData(DateTime.Today, CommonMethods.strClass));
|
|||
|
|
TotalNum = 0;
|
|||
|
|
}
|
|||
|
|
else if (TotalNum == 2)//设备状态读取
|
|||
|
|
{
|
|||
|
|
SetClass(); //变更班次
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (TotalNum1 >= 3600 * 24)
|
|||
|
|
{
|
|||
|
|
Task.Run(() => DeleteFileOfLocal());
|
|||
|
|
TotalNum1 = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TotalNum++;
|
|||
|
|
TotalNum1++;
|
|||
|
|
Regular++;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"定时器异常:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 设备综合信息
|
|||
|
|
/// <summary>
|
|||
|
|
/// 读取PLC通用信息
|
|||
|
|
/// </summary>
|
|||
|
|
internal void ReadPLCComm()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (plc.IsConnected && plc.Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
_ = UpTotalCount(null);
|
|||
|
|
|
|||
|
|
CommonMethods.TotalQty = plc.ReadFloat("D15");//生产总数
|
|||
|
|
CommonMethods.ProOKQty = plc.ReadFloat("D17");//良品总数
|
|||
|
|
CommonMethods.Ppm = plc.ReadFloat("D13");
|
|||
|
|
CommonMethods.JiaDL = Convert.ToDouble(plc.ReadFloat("D11").ToString("F2"));
|
|||
|
|
|
|||
|
|
#region 开机时间/运行时间
|
|||
|
|
CommonMethods.PlcOpenAndRunTime = GetList<int>(6, 0);
|
|||
|
|
JYResult<byte[]> resByte = plc.ReadValue<JYResult<byte[]>>($"D50", DataType.ArrByte, 12);
|
|||
|
|
if (resByte.IsSuccess && resByte.Content.Length >= 24)
|
|||
|
|
{
|
|||
|
|
for (int j = 0; j < CommonMethods.PlcOpenAndRunTimeAdress.Count; j++)
|
|||
|
|
{
|
|||
|
|
CommonMethods.PlcOpenAndRunTime[j] = IntLib.GetIntFromByteArray(resByte.Content, j * 4, DataFormat.CDAB);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 保存12小时产能数据
|
|||
|
|
public int totalNum = 0; // 总数
|
|||
|
|
public int okNum = 0; // 良品数
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 储存数据到数据库
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="DataCount"></param>
|
|||
|
|
public async Task UpTotalCount(int[] DataCount)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
DateTime now = DateTime.Now;
|
|||
|
|
string date = now.ToString("yyyy-MM-dd");
|
|||
|
|
int hour = now.Minute < 30 ? (now.Hour + 23) % 24 : now.Hour;
|
|||
|
|
|
|||
|
|
if (now.Hour == 0 && now.Minute < 30)
|
|||
|
|
date = now.AddDays(-1).ToString("yyyy-MM-dd");
|
|||
|
|
|
|||
|
|
int inQty = GetToDayHourQty(hour, true);
|
|||
|
|
int outQty = GetToDayHourQty(hour, false);
|
|||
|
|
|
|||
|
|
Hourprod prod = new Hourprod
|
|||
|
|
{
|
|||
|
|
FDate = date,
|
|||
|
|
FHour = hour,
|
|||
|
|
ProdIn = inQty,
|
|||
|
|
ProdOut = outQty,
|
|||
|
|
TestTime = now,
|
|||
|
|
Ppm = (int)CommonMethods.Ppm,
|
|||
|
|
JiaDL = Convert.ToDecimal(CommonMethods.JiaDL)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
int update = CommonMethods.db.UpdateSingle(prod,
|
|||
|
|
it => new Hourprod { ProdIn = prod.ProdIn, ProdOut = prod.ProdOut, TestTime = prod.TestTime },
|
|||
|
|
it => it.FDate == date && it.FHour == hour);
|
|||
|
|
|
|||
|
|
if (update <= 0)
|
|||
|
|
await CommonMethods.db.AddReturnBoolAsync(prod);
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
|
|||
|
|
//try
|
|||
|
|
//{
|
|||
|
|
// DateTime dateTime = DateTime.Now;
|
|||
|
|
// string strDate = dateTime.ToString("yyyy-MM-dd");
|
|||
|
|
|
|||
|
|
// // 获取当前时间的小时和分钟
|
|||
|
|
// int currentHour = dateTime.Hour;
|
|||
|
|
// int currentMinute = dateTime.Minute;
|
|||
|
|
|
|||
|
|
// // 根据当前分钟确定统计小时
|
|||
|
|
// // 如果当前时间在 X:30 之前,则统计的是前一个小时的数据(从前一个小时的30分到这个小时的29分)
|
|||
|
|
// // 如果当前时间在 X:30 之后,则统计的是当前小时的数据(从当前小时的30分到下个小时的29分)
|
|||
|
|
// int statisticalHour;
|
|||
|
|
// if (currentMinute < 30)
|
|||
|
|
// {
|
|||
|
|
// statisticalHour = (currentHour + 23) % 24; // 前一个小时,处理跨天的情况
|
|||
|
|
// // 如果是凌晨0点前的数据,需要使用前一天的日期
|
|||
|
|
// if (currentHour == 0)
|
|||
|
|
// {
|
|||
|
|
// strDate = dateTime.AddDays(-1).ToString("yyyy-MM-dd");
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
// else
|
|||
|
|
// {
|
|||
|
|
// statisticalHour = currentHour;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// // 数据有效性验证
|
|||
|
|
// if (statisticalHour < 0 || statisticalHour > 23)
|
|||
|
|
// {
|
|||
|
|
// throw new ArgumentException($"无效的小时值: {statisticalHour}");
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// //读取统计小时的PLC投入总数和产出数
|
|||
|
|
// int ProdCurrAllQty = GetToDayHourQty(statisticalHour, true);
|
|||
|
|
// int ProdCurrOKQty = GetToDayHourQty(statisticalHour, false);
|
|||
|
|
|
|||
|
|
// // 确保数量为非负数
|
|||
|
|
// if (ProdCurrAllQty < 0 || ProdCurrOKQty < 0)
|
|||
|
|
// {
|
|||
|
|
// LogHelper.Instance.WriteError($"产量不能为负数: 投入={ProdCurrAllQty}, 产出={ProdCurrOKQty}");
|
|||
|
|
// //throw new ArgumentException($"产量不能为负数: 投入={ProdCurrAllQty}, 产出={ProdCurrOKQty}");
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// Hourprod mh = new Hourprod
|
|||
|
|
// {
|
|||
|
|
// FDate = strDate,
|
|||
|
|
// FHour = statisticalHour,
|
|||
|
|
// ProdIn = ProdCurrAllQty,
|
|||
|
|
// ProdOut = ProdCurrOKQty,
|
|||
|
|
// TestTime = dateTime,
|
|||
|
|
// Ppm = (int)CommonMethods.Ppm,
|
|||
|
|
// JiaDL = Convert.ToDecimal(CommonMethods.JiaDL)
|
|||
|
|
// };
|
|||
|
|
|
|||
|
|
// // 尝试更新记录
|
|||
|
|
// int res = CommonMethods.db.UpdateSingle(
|
|||
|
|
// mh,
|
|||
|
|
// it => new Hourprod
|
|||
|
|
// {
|
|||
|
|
// ProdIn = mh.ProdIn,
|
|||
|
|
// ProdOut = mh.ProdOut,
|
|||
|
|
// TestTime = mh.TestTime,
|
|||
|
|
// Ppm = mh.Ppm,
|
|||
|
|
// JiaDL = mh.JiaDL
|
|||
|
|
// },
|
|||
|
|
// it => it.FDate == mh.FDate && it.FHour == mh.FHour
|
|||
|
|
// );
|
|||
|
|
|
|||
|
|
// // 如果更新失败(记录不存在),则新增记录
|
|||
|
|
// if (res <= 0)
|
|||
|
|
// {
|
|||
|
|
// bool addResult = await CommonMethods.db.AddReturnBoolAsync<Hourprod>(mh).ConfigureAwait(false);
|
|||
|
|
// if (!addResult)
|
|||
|
|
// {
|
|||
|
|
// LogHelper.Instance.WriteError($"保存小时产能新增记录失败");
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// // 可以添加日志记录成功信息
|
|||
|
|
// // Logger.Log($"成功更新/新增产能数据: 日期={strDate}, 小时={statisticalHour}, 时段={statisticalHour}:30~{(statisticalHour + 1) % 24}:29");
|
|||
|
|
//}
|
|||
|
|
//catch (Exception ex)
|
|||
|
|
//{
|
|||
|
|
// // 这里应该添加错误日志记录
|
|||
|
|
// LogHelper.Instance.WriteEX($"保存小时产能数据失败: ", ex);
|
|||
|
|
//}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 每小时产能读取处理
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="hour"></param>
|
|||
|
|
/// <param name="flag"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private int GetToDayHourQty(int hour, bool flag)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
string strAddr = flag ? $"D{1040 + hour}" : $"D{1070 + hour}";
|
|||
|
|
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (plc.IsConnected && plc.Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
int ProdNGQty = plc.ReadInt16(strAddr);
|
|||
|
|
return ProdNGQty;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show(ex.Message, "系统异常", MessageBoxButtons.OK, MessageBoxIcon.Hand);
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 5、设备状态
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定时获取设备状态
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="mt"></param>
|
|||
|
|
///
|
|||
|
|
int OldStatues = int.TryParse(IniConfigHelper.ReadIniData("上一次设备状态", "DeviceStatus", "1", CommonMethods.strDeviceStatusPath), out var result) ? result : 0; //退出程序前设备状态
|
|||
|
|
string strGuidAlarm = IniConfigHelper.ReadIniData("上一次Guid", "DeviceGuid", Guid.NewGuid().ToString(), CommonMethods.strDeviceStatusPath);//退出程序前Guid
|
|||
|
|
DateTime StatuesBeginTime = DateTime.Now; // DateTime.Now.AddMinutes(-6);
|
|||
|
|
bool run_AlarmMark = false;//特殊情况1标志(运行状态)
|
|||
|
|
bool run_IdleMark = false;//特殊情况2标志(闲置状态)
|
|||
|
|
bool isUpIdleAlarm = false;//闲置状态报警信息上传标志
|
|||
|
|
List<AlarmData> idleAlarmData = new List<AlarmData>();//闲置状态的报警信息
|
|||
|
|
|
|||
|
|
private async void ShowStateEven_Run()
|
|||
|
|
{
|
|||
|
|
while (!_cts.Token.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
if (plc.IsConnected && plc.Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
DateTime Date = DateTime.Now;
|
|||
|
|
bool isUpload = false;
|
|||
|
|
string deviceStatus = string.Empty;
|
|||
|
|
string deviceStatusDesc = string.Empty;
|
|||
|
|
short NewStatus = plc.ReadInt16("D10");
|
|||
|
|
|
|||
|
|
if (NewStatus != 100)
|
|||
|
|
{
|
|||
|
|
//特殊情况1:运行->报警->……->运行
|
|||
|
|
if (NewStatus == 3 && OldStatues == 1) { run_AlarmMark = true; }//第一次进入报警状态
|
|||
|
|
if (NewStatus == 1) { run_AlarmMark = false; }//下一次运行状态,置位
|
|||
|
|
if (run_AlarmMark) { NewStatus = 3; }//报警状态后下一次运行状态期间一直是报警状态
|
|||
|
|
//特殊情况2:运行->闲置->……->运行
|
|||
|
|
if (NewStatus == 4 && OldStatues == 1) { run_IdleMark = true; }//第一次进入闲置状态
|
|||
|
|
if (NewStatus == 1) { run_IdleMark = false; }//下一次闲置状态,置位
|
|||
|
|
if (run_IdleMark) { NewStatus = 4; }//闲置状态后下一次运行状态期间一直是闲置状态
|
|||
|
|
|
|||
|
|
//闲置状态结束报警信息上传
|
|||
|
|
if (OldStatues == 4 && NewStatus != 4 && isUpIdleAlarm)
|
|||
|
|
{
|
|||
|
|
_ = UpIdleDataAsync(idleAlarmData, strGuidAlarm);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//设备状态信号灯变量
|
|||
|
|
CommonMethods.Statues = new List<bool> { NewStatus == 1, NewStatus == 2, NewStatus == 3, NewStatus == 4 };
|
|||
|
|
|
|||
|
|
// 状态切换时生成新的 GUID
|
|||
|
|
if (NewStatus != OldStatues)
|
|||
|
|
{
|
|||
|
|
// 从 Alert 切换到非 Alert 状态时,上传报警信息
|
|||
|
|
if (OldStatues == 3 && NewStatus != 3)
|
|||
|
|
{
|
|||
|
|
ShowAlarmEven_Run(strGuidAlarm, NewStatus);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 只要状态变更,就生成新的 GUID
|
|||
|
|
strGuidAlarm = Guid.NewGuid().ToString(); // 强制生成新的 GUID
|
|||
|
|
|
|||
|
|
isUpload = true;
|
|||
|
|
OldStatues = NewStatus;
|
|||
|
|
StatuesBeginTime = Convert.ToDateTime(Date);
|
|||
|
|
|
|||
|
|
IniConfigHelper.WriteIniData("上一次设备状态", "DeviceStatus", NewStatus.ToString(), CommonMethods.strDeviceStatusPath);
|
|||
|
|
IniConfigHelper.WriteIniData("上一次Guid", "DeviceGuid", strGuidAlarm, CommonMethods.strDeviceStatusPath);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (Date.Subtract(StatuesBeginTime).TotalSeconds < 5 * 60 && Date.Subtract(StatuesBeginTime).TotalSeconds >= 4 * 60)
|
|||
|
|
{
|
|||
|
|
isUpload = true;
|
|||
|
|
StatuesBeginTime = DateTime.Now;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据状态设置设备状态和描述
|
|||
|
|
switch (NewStatus)
|
|||
|
|
{
|
|||
|
|
case 1:
|
|||
|
|
deviceStatus = "Run";
|
|||
|
|
deviceStatusDesc = "正常运行";
|
|||
|
|
break;
|
|||
|
|
case 2://取消
|
|||
|
|
deviceStatus = "Maintain";
|
|||
|
|
deviceStatusDesc = "停止运行";
|
|||
|
|
break;
|
|||
|
|
case 3:
|
|||
|
|
deviceStatus = "Alert";
|
|||
|
|
deviceStatusDesc = "故障调试";
|
|||
|
|
// Alert 状态时上传报警信息
|
|||
|
|
ShowAlarmEven_Run(strGuidAlarm, NewStatus);
|
|||
|
|
break;
|
|||
|
|
case 4:
|
|||
|
|
deviceStatus = "Idle";
|
|||
|
|
deviceStatusDesc = "闲置待机";
|
|||
|
|
ShowStuesEven_Run(strGuidAlarm);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上传设备状态信息
|
|||
|
|
if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected && isUpload && (NewStatus == 1 || NewStatus == 3 || NewStatus == 4))
|
|||
|
|
{
|
|||
|
|
var (success, message) = await CommonMethods.hbgMes.UploadEquStatusAsync(
|
|||
|
|
CommonMethods.mesConfig.DeviceStatus,
|
|||
|
|
CommonMethods.mesConfig.siteCode,
|
|||
|
|
CommonMethods.mesConfig.lineCode,
|
|||
|
|
CommonMethods.mesConfig.equipNum,
|
|||
|
|
CommonMethods.mesConfig.MaterialCode,
|
|||
|
|
CommonMethods.mesConfig.mesUserName,
|
|||
|
|
strGuidAlarm, // RUN 状态时传空
|
|||
|
|
deviceStatus,
|
|||
|
|
null
|
|||
|
|
).ConfigureAwait(false);
|
|||
|
|
|
|||
|
|
if (success)
|
|||
|
|
{
|
|||
|
|
//StatuesCount = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteEX(ex);
|
|||
|
|
}
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
//Task.Delay(100).CountinueWith();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
#region 触摸屏弹窗
|
|||
|
|
private async void ShowStuesEven_Run(string strGuid)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.plcDevices.Count() > 0)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < CommonMethods.plcDevices.Count(); i++)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
string strCode = "";
|
|||
|
|
string alarmName = "";
|
|||
|
|
short HMIStatus = CommonMethods.plcDevices[i].ReadInt16("D40");
|
|||
|
|
if (HMIStatus != 0)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, "触发了手动HMI弹窗功能".Translated());
|
|||
|
|
switch (HMIStatus) //注意:B1-D12都为闲置代码,上传代码是只需上传闲置代码
|
|||
|
|
{
|
|||
|
|
case 5:
|
|||
|
|
strCode = "D1";
|
|||
|
|
alarmName = "设备故障".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 6:
|
|||
|
|
strCode = "D2";
|
|||
|
|
alarmName = "设备转型".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 7:
|
|||
|
|
strCode = "D3";
|
|||
|
|
alarmName = "工程变更".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 8:
|
|||
|
|
strCode = "D4";
|
|||
|
|
alarmName = "MES停机".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 9:
|
|||
|
|
strCode = "D5";
|
|||
|
|
alarmName = "品质异常".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 10:
|
|||
|
|
strCode = "D6";
|
|||
|
|
alarmName = "配件,物料更换".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 11:
|
|||
|
|
strCode = "D7";
|
|||
|
|
alarmName = "来料不良".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 12:
|
|||
|
|
strCode = "D8";
|
|||
|
|
alarmName = "待料".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 13:
|
|||
|
|
strCode = "D9";
|
|||
|
|
alarmName = "品质检查".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 14:
|
|||
|
|
strCode = "D10";
|
|||
|
|
alarmName = "清洁点检".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 15:
|
|||
|
|
strCode = "D11";
|
|||
|
|
alarmName = "环境不满足".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
case 16:
|
|||
|
|
strCode = "D12";
|
|||
|
|
alarmName = "其它非计划停机".Translated();
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected)
|
|||
|
|
{
|
|||
|
|
List<AlarmData> alarmObj = new List<AlarmData>();
|
|||
|
|
AlarmData alarmData = new AlarmData();
|
|||
|
|
alarmData.AlarmCode = strCode;
|
|||
|
|
alarmData.StartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
alarmData.EndTime = "";
|
|||
|
|
alarmData.AlarmType = "非计划损失";
|
|||
|
|
alarmData.AlarmContent = alarmName;
|
|||
|
|
alarmObj.Add(alarmData);
|
|||
|
|
|
|||
|
|
//闲置报警上传标志
|
|||
|
|
isUpIdleAlarm = true;
|
|||
|
|
idleAlarmData.Add(alarmData);
|
|||
|
|
|
|||
|
|
//上传报警
|
|||
|
|
var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode,
|
|||
|
|
CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, alarmObj, strGuid).ConfigureAwait(false);
|
|||
|
|
if (success)
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗状态已上传成功,代码".Translated() + $":{strCode}");
|
|||
|
|
}
|
|||
|
|
bool IsSuccess = CommonMethods.plcDevices[i].WriteInt16("D40", 0);
|
|||
|
|
if (IsSuccess)
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, $"D40已复位0".Translated());
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// HMI手动弹窗结束状态上传
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="idleAlarmDate"></param>
|
|||
|
|
/// <param name="strGuidAlarm"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private async Task UpIdleDataAsync(List<AlarmData> idleAlarmDate, string strGuidAlarm)
|
|||
|
|
{
|
|||
|
|
if (idleAlarmDate.Count == 0) { return; }
|
|||
|
|
string strCode = "";
|
|||
|
|
foreach (var item in idleAlarmDate)
|
|||
|
|
{
|
|||
|
|
item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
strCode = item.AlarmCode + ",";
|
|||
|
|
}
|
|||
|
|
if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected)
|
|||
|
|
{
|
|||
|
|
//上传报警
|
|||
|
|
var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode,
|
|||
|
|
CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, idleAlarmDate, strGuidAlarm).ConfigureAwait(false);
|
|||
|
|
if (success)
|
|||
|
|
CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗结束状态已上传成功,代码".Translated() + $":{strCode.TrimEnd(',')}");
|
|||
|
|
}
|
|||
|
|
idleAlarmData.Clear();
|
|||
|
|
isUpIdleAlarm = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 6、设备报警
|
|||
|
|
//报警表单数据
|
|||
|
|
List<AlarmForm> listAlarmForm = null;
|
|||
|
|
public void GetAlarmData()
|
|||
|
|
{
|
|||
|
|
//加载PLC报警文件
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
string strFileName = Path.Combine(Environment.CurrentDirectory, "欧姆龙报警寄存器对应表.csv");
|
|||
|
|
listAlarmForm = CSVHelper<AlarmForm>.ReadCSV(strFileName);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show("加载报警文件失败!");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 报警信息读取
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="mt"></param>
|
|||
|
|
/// <param name="resByte"></param>
|
|||
|
|
List<AlarmData> listOld = new List<AlarmData>();
|
|||
|
|
List<AlarmData> listNew = new List<AlarmData>();
|
|||
|
|
private void ShowAlarmEven_Run(string strGuidAlarm,int MachineState)
|
|||
|
|
{
|
|||
|
|
//if (!CommonMethods.IsRun || !CommonMethods.plcDevices[0].IsConnected)
|
|||
|
|
//{
|
|||
|
|
// return;
|
|||
|
|
//}
|
|||
|
|
bool boolIsConnected = false;
|
|||
|
|
if (CommonMethods.plcDevices.Count() > 0)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < CommonMethods.plcDevices.Count(); i++)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
boolIsConnected = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!CommonMethods.IsRun || !boolIsConnected)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 1. 读取 PLC 报警信息
|
|||
|
|
List<AlarmStatus> alarmStatusList = ReadAlarmStatusFromPLC();
|
|||
|
|
if (alarmStatusList == null || listAlarmForm == null)//true
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 解析报警信息并生成新的报警列表
|
|||
|
|
List<AlarmData> newAlarms = ParseAlarmStatus(alarmStatusList, strGuidAlarm);
|
|||
|
|
if (newAlarms.Count > 0)
|
|||
|
|
{
|
|||
|
|
// 3. 检测新报警并添加到旧报警列表,上传第一次报警信息
|
|||
|
|
DetectNewAlarmsAsync(newAlarms);
|
|||
|
|
|
|||
|
|
// 4. 文字显示新报警信息
|
|||
|
|
DisplayAlarmInfo(newAlarms);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 检测已取消的报警并处理
|
|||
|
|
List<AlarmData> canceledAlarms = DetectCanceledAlarms(newAlarms);
|
|||
|
|
if (canceledAlarms.Count > 0)
|
|||
|
|
{
|
|||
|
|
// 6. 记录已取消的报警信息(保存本地)
|
|||
|
|
LogCanceledAlarms(canceledAlarms);
|
|||
|
|
|
|||
|
|
// 7. 上传已取消的报警信息(报警结束上传一次)
|
|||
|
|
UploadCanceledAlarmsAsync(canceledAlarms, strGuidAlarm);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (MachineState != 3 && listOld.Count > 0) //设备状态变为非报警并旧报警仍有报警。旧报警上传并清空
|
|||
|
|
{
|
|||
|
|
List<AlarmData> tempOldAlarmList = new List<AlarmData>();
|
|||
|
|
foreach (var item in listOld)
|
|||
|
|
{
|
|||
|
|
item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //所有报警全部结束时间赋值
|
|||
|
|
tempOldAlarmList.Add(item);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 6. 记录已取消的报警信息(保存本地)
|
|||
|
|
LogCanceledAlarms(tempOldAlarmList);
|
|||
|
|
|
|||
|
|
// 7. 上传已取消的报警信息(报警结束上传一次)
|
|||
|
|
UploadCanceledAlarmsAsync(tempOldAlarmList, strGuidAlarm);
|
|||
|
|
|
|||
|
|
listOld.Clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteEX(ex);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从 PLC 读取报警状态
|
|||
|
|
/// </summary>
|
|||
|
|
private List<AlarmStatus> ReadAlarmStatusFromPLC()
|
|||
|
|
{
|
|||
|
|
string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
// 读取足够的字节覆盖所有可能的报警地址
|
|||
|
|
int startAddr = 10010;
|
|||
|
|
int endAddr = 10519; // 根据实际最大地址调整
|
|||
|
|
int wordCount = endAddr - startAddr + 1;
|
|||
|
|
int byteCount = wordCount * 2; // 每个字2字节
|
|||
|
|
|
|||
|
|
if (CommonMethods.plcDevices.Count() > 0)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < CommonMethods.plcDevices.Count(); i++)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
JYResult<byte[]> resByte = CommonMethods.plcDevices[i].ReadValue<JYResult<byte[]>>($"D{startAddr}", DataType.ArrByte, (ushort)byteCount);
|
|||
|
|
|
|||
|
|
if (resByte.IsSuccess && resByte.Content != null)
|
|||
|
|
{
|
|||
|
|
return PLCAlarmParseHelper.OmronFinsByte2Status(startAddr, resByte.Content);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 解析报警状态并生成新的报警列表
|
|||
|
|
/// </summary>
|
|||
|
|
private List<AlarmData> ParseAlarmStatus(List<AlarmStatus> alarmStatusList, string strGuidAlarm)
|
|||
|
|
{
|
|||
|
|
string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
|
|||
|
|
var alarmTypeMap = new Dictionary<char, string> // 定义报警类型映射字典
|
|||
|
|
{
|
|||
|
|
{'1', "停机报警"},
|
|||
|
|
{'2', "非停机报警"},
|
|||
|
|
};
|
|||
|
|
switch (LanguageManager.LanguageCode)
|
|||
|
|
{
|
|||
|
|
case "en"://英语报警信息
|
|||
|
|
return alarmStatusList
|
|||
|
|
.Where(status => status.Status)
|
|||
|
|
.Join(
|
|||
|
|
listAlarmForm,
|
|||
|
|
status => status.PLCAdress,
|
|||
|
|
form => form.PLCAdress,
|
|||
|
|
(status, form) =>
|
|||
|
|
{
|
|||
|
|
var alarmContent = string.IsNullOrEmpty(form.EnAlarmContent)
|
|||
|
|
? status.PLCAdress
|
|||
|
|
: form.EnAlarmContent;
|
|||
|
|
string alarmType = "停机报警"; // 默认值
|
|||
|
|
if (form.AlarmCode?.Length >= 3)
|
|||
|
|
{
|
|||
|
|
char thirdChar = form.AlarmCode[2];
|
|||
|
|
// 尝试从字典获取对应的值
|
|||
|
|
if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType))
|
|||
|
|
{
|
|||
|
|
alarmType = mappedType;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return new AlarmData
|
|||
|
|
{
|
|||
|
|
AlarmGuid = strGuidAlarm,
|
|||
|
|
PLCAdress = status.PLCAdress,
|
|||
|
|
AlarmCode = form.AlarmCode,
|
|||
|
|
AlarmContent = alarmContent,
|
|||
|
|
AlarmType = alarmType,
|
|||
|
|
AlarmDesc = alarmContent,
|
|||
|
|
AlarmState = "1",
|
|||
|
|
StartTime = Date,
|
|||
|
|
EndTime = "",
|
|||
|
|
Flag = 0
|
|||
|
|
};
|
|||
|
|
})
|
|||
|
|
.ToList();
|
|||
|
|
case "hu"://匈牙利语报警信息
|
|||
|
|
return alarmStatusList
|
|||
|
|
.Where(status => status.Status)
|
|||
|
|
.Join(
|
|||
|
|
listAlarmForm,
|
|||
|
|
status => status.PLCAdress,
|
|||
|
|
form => form.PLCAdress,
|
|||
|
|
(status, form) =>
|
|||
|
|
{
|
|||
|
|
var alarmContent = string.IsNullOrEmpty(form.HuAlarmContent)
|
|||
|
|
? status.PLCAdress
|
|||
|
|
: form.HuAlarmContent;
|
|||
|
|
string alarmType = "停机报警"; // 默认值
|
|||
|
|
if (form.AlarmCode?.Length >= 3)
|
|||
|
|
{
|
|||
|
|
char thirdChar = form.AlarmCode[2];
|
|||
|
|
// 尝试从字典获取对应的值
|
|||
|
|
if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType))
|
|||
|
|
{
|
|||
|
|
alarmType = mappedType;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return new AlarmData
|
|||
|
|
{
|
|||
|
|
AlarmGuid = strGuidAlarm,
|
|||
|
|
PLCAdress = status.PLCAdress,
|
|||
|
|
AlarmCode = form.AlarmCode,
|
|||
|
|
AlarmContent = alarmContent,
|
|||
|
|
AlarmType = alarmType,
|
|||
|
|
AlarmDesc = alarmContent,
|
|||
|
|
AlarmState = "1",
|
|||
|
|
StartTime = Date,
|
|||
|
|
EndTime = "",
|
|||
|
|
Flag = 0
|
|||
|
|
};
|
|||
|
|
})
|
|||
|
|
.ToList();
|
|||
|
|
case "ms"://马来语报警信息
|
|||
|
|
return alarmStatusList
|
|||
|
|
.Where(status => status.Status)
|
|||
|
|
.Join(
|
|||
|
|
listAlarmForm,
|
|||
|
|
status => status.PLCAdress,
|
|||
|
|
form => form.PLCAdress,
|
|||
|
|
(status, form) =>
|
|||
|
|
{
|
|||
|
|
var alarmContent = string.IsNullOrEmpty(form.MsAlarmContent)
|
|||
|
|
? status.PLCAdress
|
|||
|
|
: form.MsAlarmContent;
|
|||
|
|
string alarmType = "停机报警"; // 默认值
|
|||
|
|
if (form.AlarmCode?.Length >= 3)
|
|||
|
|
{
|
|||
|
|
char thirdChar = form.AlarmCode[2];
|
|||
|
|
// 尝试从字典获取对应的值
|
|||
|
|
if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType))
|
|||
|
|
{
|
|||
|
|
alarmType = mappedType;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return new AlarmData
|
|||
|
|
{
|
|||
|
|
AlarmGuid = strGuidAlarm,
|
|||
|
|
PLCAdress = status.PLCAdress,
|
|||
|
|
AlarmCode = form.AlarmCode,
|
|||
|
|
AlarmContent = alarmContent,
|
|||
|
|
AlarmType = alarmType,
|
|||
|
|
AlarmDesc = alarmContent,
|
|||
|
|
AlarmState = "1",
|
|||
|
|
StartTime = Date,
|
|||
|
|
EndTime = "",
|
|||
|
|
Flag = 0
|
|||
|
|
};
|
|||
|
|
})
|
|||
|
|
.ToList();
|
|||
|
|
case "zh":
|
|||
|
|
default:
|
|||
|
|
return alarmStatusList
|
|||
|
|
.Where(status => status.Status)
|
|||
|
|
.Join(
|
|||
|
|
listAlarmForm,
|
|||
|
|
status => status.PLCAdress,
|
|||
|
|
form => form.PLCAdress,
|
|||
|
|
(status, form) =>
|
|||
|
|
{
|
|||
|
|
var alarmContent = string.IsNullOrEmpty(form.AlarmContent)
|
|||
|
|
? status.PLCAdress
|
|||
|
|
: form.AlarmContent;
|
|||
|
|
string alarmType = "停机报警"; // 默认值
|
|||
|
|
if (form.AlarmCode?.Length >= 3)
|
|||
|
|
{
|
|||
|
|
char thirdChar = form.AlarmCode[2];
|
|||
|
|
// 尝试从字典获取对应的值
|
|||
|
|
if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType))
|
|||
|
|
{
|
|||
|
|
alarmType = mappedType;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return new AlarmData
|
|||
|
|
{
|
|||
|
|
AlarmGuid = strGuidAlarm,
|
|||
|
|
PLCAdress = status.PLCAdress,
|
|||
|
|
AlarmCode = form.AlarmCode,
|
|||
|
|
AlarmContent = alarmContent,
|
|||
|
|
AlarmType = alarmType,
|
|||
|
|
AlarmDesc = alarmContent,
|
|||
|
|
AlarmState = "1",
|
|||
|
|
StartTime = Date,
|
|||
|
|
EndTime = "",
|
|||
|
|
Flag = 0
|
|||
|
|
};
|
|||
|
|
})
|
|||
|
|
.ToList();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private HashSet<string> _allUploadedAlarmContents = new HashSet<string>();
|
|||
|
|
private void DetectNewAlarmsAsync(List<AlarmData> newAlarms)
|
|||
|
|
{
|
|||
|
|
List<AlarmData> tempAlarmList = new List<AlarmData>();
|
|||
|
|
foreach (var alarm in newAlarms)
|
|||
|
|
{
|
|||
|
|
if (!listOld.Any(o => o.PLCAdress == alarm.PLCAdress))
|
|||
|
|
{
|
|||
|
|
listOld.Add(alarm);
|
|||
|
|
tempAlarmList.Add(alarm); //把新报警保存到临时表中
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected)
|
|||
|
|
{
|
|||
|
|
CommonMethods.hbgMes.UploadEquAlarmAsync(
|
|||
|
|
CommonMethods.mesConfig.DeviceAlarm,
|
|||
|
|
CommonMethods.mesConfig.siteCode,
|
|||
|
|
CommonMethods.mesConfig.lineCode,
|
|||
|
|
CommonMethods.mesConfig.equipNum,
|
|||
|
|
CommonMethods.mesConfig.MaterialCode,
|
|||
|
|
CommonMethods.mesConfig.mesUserName,
|
|||
|
|
tempAlarmList,
|
|||
|
|
strGuidAlarm
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 显示报警信息
|
|||
|
|
/// </summary>
|
|||
|
|
private void DisplayAlarmInfo(List<AlarmData> newAlarms)
|
|||
|
|
{
|
|||
|
|
StringBuilder sb = new StringBuilder();
|
|||
|
|
newAlarms.ForEach(r => sb.Append($"{r.AlarmContent} "));
|
|||
|
|
|
|||
|
|
if (sb.Length > 0)
|
|||
|
|
{
|
|||
|
|
CommonMethods.SlideAlarmText = sb.ToString().Substring(0, sb.Length - 10);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检测已取消的报警
|
|||
|
|
/// </summary>
|
|||
|
|
private List<AlarmData> DetectCanceledAlarms(List<AlarmData> newAlarms)
|
|||
|
|
{
|
|||
|
|
var canceledAlarms = new List<AlarmData>();
|
|||
|
|
var listOldAlarm = new List<AlarmData>(listOld);
|
|||
|
|
|
|||
|
|
foreach (var item in listOldAlarm)
|
|||
|
|
{
|
|||
|
|
if (!newAlarms.Any(n => n.PLCAdress == item.PLCAdress))
|
|||
|
|
{
|
|||
|
|
item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
canceledAlarms.Add(item);
|
|||
|
|
listOld.Remove(item);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return canceledAlarms;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 记录已取消的报警信息
|
|||
|
|
/// </summary>
|
|||
|
|
private void LogCanceledAlarms(List<AlarmData> canceledAlarms)
|
|||
|
|
{
|
|||
|
|
var nowDate = DateTime.Now;
|
|||
|
|
string basePath = $@"{CommonMethods.strAlarmLogspath}\{nowDate.Year}\{nowDate.Month}";
|
|||
|
|
string fileName = $@"{nowDate.Day}.csv";
|
|||
|
|
string title = "GUID,报警地址,故障代码,报警内容,开始时间,结束时间,是否停机";
|
|||
|
|
string alarmContent = string.Join("\n", canceledAlarms.Select(m1 =>
|
|||
|
|
$"{m1.AlarmGuid},{m1.PLCAdress},{m1.AlarmCode},{m1.AlarmContent},{m1.StartTime},{m1.EndTime},{m1.AlarmType}"));
|
|||
|
|
|
|||
|
|
CSVHelper<AlarmCSVData>.WriterCSV(basePath, fileName, title, alarmContent);
|
|||
|
|
|
|||
|
|
foreach (var m1 in canceledAlarms)
|
|||
|
|
{
|
|||
|
|
var alarm = new SysLog
|
|||
|
|
{
|
|||
|
|
InsertTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
|||
|
|
LogType = "报警日志",
|
|||
|
|
Note = m1.AlarmContent == "" ? m1.PLCAdress : m1.AlarmContent,
|
|||
|
|
AlarmType = "错误",
|
|||
|
|
Operater = CommonMethods.currentAdmin.LoginName,
|
|||
|
|
VarName = m1.PLCAdress,
|
|||
|
|
AlarmSet = "",
|
|||
|
|
AlarmValue = "1",
|
|||
|
|
};
|
|||
|
|
// 显示报警信息
|
|||
|
|
CommonMethods.AddAlarmLog(alarm);//存报警信息
|
|||
|
|
// 报警top10保存
|
|||
|
|
SaveAlarmCount(canceledAlarms);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 报警结束上传
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="canceledAlarms"></param>
|
|||
|
|
/// <param name="strGuidAlarm"></param>
|
|||
|
|
private void UploadCanceledAlarmsAsync(List<AlarmData> canceledAlarms, string strGuidAlarm)
|
|||
|
|
{
|
|||
|
|
if (CommonMethods.sysConfig.MesModeSwitching)
|
|||
|
|
{
|
|||
|
|
CommonMethods.hbgMes.UploadEquAlarmAsync(
|
|||
|
|
CommonMethods.mesConfig.DeviceAlarm,
|
|||
|
|
CommonMethods.mesConfig.siteCode,
|
|||
|
|
CommonMethods.mesConfig.lineCode,
|
|||
|
|
CommonMethods.mesConfig.equipNum,
|
|||
|
|
CommonMethods.mesConfig.MaterialCode,
|
|||
|
|
CommonMethods.mesConfig.mesUserName,
|
|||
|
|
canceledAlarms,
|
|||
|
|
strGuidAlarm
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 报警Top10保存
|
|||
|
|
/// </summary>
|
|||
|
|
private async Task SaveAlarmCount(List<AlarmData> alarmDatas)
|
|||
|
|
{
|
|||
|
|
bool ISAdd = false;
|
|||
|
|
var recordToUpdate = new AlarmCountEntry();
|
|||
|
|
|
|||
|
|
DateTime today = DateTime.Today;
|
|||
|
|
DateTime upTime = DateTime.Now;
|
|||
|
|
var listAlarmStatistics = CommonMethods.db.QueryWhereList<AlarmCountEntry>(it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today);
|
|||
|
|
List<AlarmCountEntry> fd = new List<AlarmCountEntry>();
|
|||
|
|
if (listAlarmStatistics != null)
|
|||
|
|
{
|
|||
|
|
alarmDatas.ForEach(async r =>
|
|||
|
|
{
|
|||
|
|
recordToUpdate = listAlarmStatistics.FirstOrDefault(x => x.PLCAdress.Trim() == r.PLCAdress);
|
|||
|
|
if (recordToUpdate != null)
|
|||
|
|
{
|
|||
|
|
await CommonMethods.db.UpdateAsync<AlarmCountEntry>(it => it.AlarmCount == it.AlarmCount + 1, it => it.PLCAdress == r.PLCAdress && Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
ISAdd = true;
|
|||
|
|
AlarmCountEntry alarm = new AlarmCountEntry();
|
|||
|
|
alarm.PLCAdress = r.PLCAdress;
|
|||
|
|
alarm.AlarmContent = r.AlarmContent;
|
|||
|
|
alarm.AlarmCount = 1;
|
|||
|
|
alarm.Class = CommonMethods.strClass;
|
|||
|
|
alarm.AlarmTime = upTime;
|
|||
|
|
fd.Add(alarm);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (ISAdd)
|
|||
|
|
{
|
|||
|
|
bool res = await CommonMethods.db.AddReturnBoolAsync(fd).ConfigureAwait(false);
|
|||
|
|
ISAdd = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 显示报警信息方法已存数据库,报警TOP10就插入新增了
|
|||
|
|
//else
|
|||
|
|
//{
|
|||
|
|
// foreach (var m in listNew)
|
|||
|
|
// {
|
|||
|
|
// AlarmCountEntry alarm = new AlarmCountEntry();
|
|||
|
|
// alarm.PLCAdress = m.PLCAdress;
|
|||
|
|
// alarm.AlarmContent = m.AlarmContent;
|
|||
|
|
// alarm.AlarmCount = 1;
|
|||
|
|
// alarm.Class = CommonMethods.strClass;
|
|||
|
|
// alarm.AlarmTime = today;
|
|||
|
|
// fd.Add(alarm);
|
|||
|
|
// }
|
|||
|
|
// await CommonMethods.db.AddReturnBoolAsync(fd);
|
|||
|
|
//}
|
|||
|
|
await GetASqlAlarmData(DateTime.Today, CommonMethods.strClass).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 读取数据库报警信息
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="today"></param>
|
|||
|
|
/// <param name="strClass"></param>
|
|||
|
|
public async Task GetASqlAlarmData(DateTime today, string strClass)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
List<ChartDataType> chartAlarms = new List<ChartDataType>();
|
|||
|
|
List<AlarmCountEntry> AlarmDatas = null;
|
|||
|
|
List<string> List_X = new List<string>();
|
|||
|
|
List<int> List_Y = new List<int>();
|
|||
|
|
|
|||
|
|
DateTime baiClassStart = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 8:30:00"));
|
|||
|
|
DateTime baiClassEnd = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 20:29:59"));
|
|||
|
|
|
|||
|
|
DateTime yeClassStart = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 20:30:00"));
|
|||
|
|
DateTime yeClassEnd = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd 8:29:59"));
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (strClass == "全天".Translated())
|
|||
|
|
{
|
|||
|
|
AlarmDatas = await CommonMethods.db.QueryListAsync<AlarmCountEntry>("AlarmCount", 10, it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
else if (strClass == "白班".Translated())
|
|||
|
|
{
|
|||
|
|
AlarmDatas = await CommonMethods.db.QueryListAsync<AlarmCountEntry>("AlarmCount", 10, it => it.AlarmTime >= baiClassStart && it.AlarmTime <= baiClassEnd && it.Class == strClass).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
AlarmDatas = await CommonMethods.db.QueryListAsync<AlarmCountEntry>("AlarmCount", 10, it => it.AlarmTime >= yeClassStart && it.AlarmTime <= yeClassEnd && it.Class == strClass).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (AlarmDatas != null && AlarmDatas.Count > 0)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
for (int i = 0; i < AlarmDatas.Count; i++)
|
|||
|
|
{
|
|||
|
|
chartAlarms.Add(new ChartDataType()
|
|||
|
|
{
|
|||
|
|
DataType = AlarmDatas[i].AlarmContent,
|
|||
|
|
DataCount = (int)AlarmDatas[i].AlarmCount
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
CommonMethods.Lst_AlarmTop10_Qty = chartAlarms;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
CommonMethods.Lst_AlarmTop10_Qty = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 配置下发给PLC
|
|||
|
|
public async Task SendConfigToPLCAsync(PlcReadWriteBase plc)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 给物流线PLC下发 生产型号 和 本机架各垛位的档位
|
|||
|
|
if (plc.IsConnected && plc.Name == "PLC_2")
|
|||
|
|
{
|
|||
|
|
await SendConfigToWuliuPLCAsync(plc);
|
|||
|
|
}
|
|||
|
|
else if (plc.IsConnected && plc.Name == "PLC_1")
|
|||
|
|
{
|
|||
|
|
await SendConfigToZuPanPLCAsync(plc);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddLog(true, "PC写入PLC配置异常".Translated() + ":" + ex.Message);
|
|||
|
|
LogHelper.Instance.WriteEX(ex);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task SendConfigToWuliuPLCAsync(PlcReadWriteBase plc)
|
|||
|
|
{
|
|||
|
|
string plc_addr = CommonMethods.mesConfig.GetWuliuModelAddr();
|
|||
|
|
if (plc_addr == null || String.IsNullOrEmpty(plc_addr))
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"机架号不识别,无对应机架号的PLC生产型号地址({plc_addr})");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int model = CommonMethods.mesConfig.GetModel(CommonMethods.mesConfig.ModelName);
|
|||
|
|
JYResult result = await Task.Run(() => plc.WriteValue(plc_addr, (short)model, DataType.Short)).ConfigureAwait(false);
|
|||
|
|
if (result.IsSuccess)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, $"Write_WuLiu_PLC_model({plc_addr})_{model}_Successful");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"Write_Wuliu_PLC_model({plc_addr})_{model}_Failed");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Dictionary<int, string> plc_addrDict = CommonMethods.mesConfig.GetWuliuGradeAddr();
|
|||
|
|
if (plc_addrDict == null || plc_addrDict.Count == 0)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"机架号不识别或者垛位号错误,无对应的PLC分档地址({plc_addr})");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var kvp in plc_addrDict)
|
|||
|
|
{
|
|||
|
|
if (kvp.Value == null || String.IsNullOrEmpty(kvp.Value))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int stationNo = kvp.Key;
|
|||
|
|
string plcAddress = kvp.Value;
|
|||
|
|
string strGrade = MesConfig.GetLocGrade(stationNo, CommonMethods.curFdConfig);
|
|||
|
|
int grade = CommonMethods.GetGradeNo(strGrade);
|
|||
|
|
|
|||
|
|
result = await Task.Run(() => plc.WriteValue(plcAddress, (short)grade, DataType.Short)).ConfigureAwait(false);
|
|||
|
|
if (result.IsSuccess)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, $"Write_PLC_grade({plcAddress})_{strGrade}({grade})_Successful");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"Write_PLC_grade({plcAddress})_{strGrade}({grade})_Failed");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task SendConfigToZuPanPLCAsync(PlcReadWriteBase plc)
|
|||
|
|
{
|
|||
|
|
var writeTasks = new List<Task>();
|
|||
|
|
Dictionary<int, string> loc_plcAddr = CommonMethods.mesConfig.gw_loc_plcAddr[Convert.ToInt16(CommonMethods.sysConfig.StationNo)];
|
|||
|
|
|
|||
|
|
FDConfig fdconfig = CommonMethods.curFdConfig;
|
|||
|
|
foreach (KeyValuePair<int, string> locKvp in loc_plcAddr)
|
|||
|
|
{
|
|||
|
|
int stationNo = locKvp.Key;
|
|||
|
|
string plcAddr = locKvp.Value;
|
|||
|
|
|
|||
|
|
string gradeStr = MesConfig.GetLocGrade(stationNo, fdconfig);
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 转换等级
|
|||
|
|
int gradeValue = CommonMethods.GetGradeNo(gradeStr);
|
|||
|
|
string logBase = $"LOC{stationNo} Write_Zupan_PLC({plcAddr}:等级{gradeStr},转换PLC值{gradeValue})";
|
|||
|
|
JYResult result = await Task.Run(() => plc.WriteValue(plcAddr, (short)gradeValue, DataType.Short)).ConfigureAwait(false);
|
|||
|
|
if (result.IsSuccess)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, $"{logBase} 成功");
|
|||
|
|
CommonMethods.AddOPLog(true, $"{logBase} 成功");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
string failMsg = $"{logBase} 失败,错误:{result.Message ?? "无详细信息"}";
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, failMsg);
|
|||
|
|
CommonMethods.AddOPLog(false, failMsg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
string exMsg = $"LOC{stationNo} Write_Zupan_PLC({plcAddr}) 异常:{ex.Message},堆栈:{ex.StackTrace}";
|
|||
|
|
CommonMethods.AddDataMonitorLog(3, exMsg);
|
|||
|
|
CommonMethods.AddOPLog(false, exMsg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 下发权限给PLC
|
|||
|
|
/// <summary>
|
|||
|
|
/// 下发权限到PLC
|
|||
|
|
/// </summary>
|
|||
|
|
public async Task SendUserRightToPLCAsync(PlcReadWriteBase plc)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
if (!plc.IsConnected || plc.Name != "PLC_1")
|
|||
|
|
{
|
|||
|
|
if(plc.Name == "PLC_1")
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"组盘PLC连接中断,无法给PLC下发用户名和权限");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ushort quanxian = 1;
|
|||
|
|
switch (CommonMethods.currentAdmin.LoginLevel)
|
|||
|
|
{
|
|||
|
|
case "操作员":
|
|||
|
|
quanxian = 1;
|
|||
|
|
break;
|
|||
|
|
case "工程师":
|
|||
|
|
quanxian = 2;
|
|||
|
|
break;
|
|||
|
|
case "管理员":
|
|||
|
|
quanxian = 3;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Dictionary<string, string> plc_addrs = CommonMethods.mesConfig.GetHMIUserRightAddr();
|
|||
|
|
if (!string.IsNullOrEmpty(plc_addrs["right"]))
|
|||
|
|
{
|
|||
|
|
JYResult result = await Task.Run(() => plc.WriteValue(plc_addrs["right"], (short)quanxian, DataType.Short));
|
|||
|
|
if (result.IsSuccess)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, $"Write_PLC_permission_{quanxian}({plc_addrs["right"]})_Successful");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"Write_PLC_permission_{quanxian}({plc_addrs["right"]})_Failed");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(plc_addrs["user"]))
|
|||
|
|
{
|
|||
|
|
const int BARCODE_SIZE = 40;
|
|||
|
|
string loginName = string.Concat(CommonMethods.currentAdmin.LoginName + GetString_0(BARCODE_SIZE - CommonMethods.currentAdmin.LoginName.Length));
|
|||
|
|
byte[] codeBytes = Encoding.Default.GetBytes(loginName).ByteReverse();
|
|||
|
|
JYResult result2 = await Task.Run(() => plc.WriteValue(plc_addrs["user"], codeBytes, DataType.ArrByte));
|
|||
|
|
if (result2.IsSuccess)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(1, $"Write_PLC_username_{loginName}({plc_addrs["user"]})_Successful\n");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddDataMonitorLog(2, $"Write_PLC_username_{loginName}({plc_addrs["user"]})_Failed");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 获取白夜班次
|
|||
|
|
bool qiehuan = false;
|
|||
|
|
private void SetClass()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var nowDate = DateTime.Now;
|
|||
|
|
string strClass = "";
|
|||
|
|
DateTime time1 = Convert.ToDateTime($"{nowDate.ToShortDateString()} {CommonMethods.ChangeClassTime}");
|
|||
|
|
DateTime time2 = Convert.ToDateTime($"{nowDate.ToShortDateString()} {Convert.ToDateTime(CommonMethods.ChangeClassTime).AddHours(12).ToString("HH:mm:ss")}");
|
|||
|
|
if (nowDate <= time1 && nowDate >= time2)
|
|||
|
|
{
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (_plcDeviceMapping.TryGetValue(plc, out var plcDevices))
|
|||
|
|
{
|
|||
|
|
foreach (var plcDevice in plcDevices)
|
|||
|
|
{
|
|||
|
|
if (plcDevice.IsConnected)
|
|||
|
|
{
|
|||
|
|
if (!qiehuan)
|
|||
|
|
{
|
|||
|
|
if (!SetPlcTime(plc))
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddLog(true, $"PC写入PLC时间失败".Translated());
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
qiehuan = true;
|
|||
|
|
CommonMethods.AddLog(false, "PC写入PLC时间成功".Translated());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
strClass = "白班".Translated();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
qiehuan = false;
|
|||
|
|
strClass = "夜班".Translated();
|
|||
|
|
}
|
|||
|
|
CommonMethods.strClass = strClass;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
CommonMethods.AddLog(true, "PC写入PLC时间异常".Translated() + ":" + ex.Message);
|
|||
|
|
LogHelper.Instance.WriteEX(ex);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 写入PLC时间
|
|||
|
|
/// <summary>
|
|||
|
|
/// 写当前时间给PLC
|
|||
|
|
/// </summary>
|
|||
|
|
private bool SetPlcTime(PlcReadWriteBase plcDevice)
|
|||
|
|
{
|
|||
|
|
var nowDate = DateTime.Now;
|
|||
|
|
string yearStr = nowDate.Year.ToString();
|
|||
|
|
|
|||
|
|
|
|||
|
|
bool IsSuccess = false;
|
|||
|
|
|
|||
|
|
// 为防止PLC时间出现偏差,每次自动运行的时候写PLC系统时间
|
|||
|
|
short[] writeArr = new short[]
|
|||
|
|
{
|
|||
|
|
Convert.ToInt16(nowDate.Year),//D70
|
|||
|
|
Convert.ToInt16(nowDate.Month),//D71
|
|||
|
|
Convert.ToInt16(nowDate.Day),//D72
|
|||
|
|
Convert.ToInt16(nowDate.Hour),//D73
|
|||
|
|
Convert.ToInt16(nowDate.Minute),//D74
|
|||
|
|
Convert.ToInt16(nowDate.Second),//D75
|
|||
|
|
(short)1 //D76
|
|||
|
|
};
|
|||
|
|
if (plcDevice.IsConnected)
|
|||
|
|
{
|
|||
|
|
JYResult result = plcDevice.WriteValue("D70", writeArr, DataType.ArrShort);
|
|||
|
|
if (result.IsSuccess)
|
|||
|
|
{
|
|||
|
|
IsSuccess = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return IsSuccess;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 9、IDisposable 实现
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 如果需要在StopAsync中也显示进度,可以添加一个进度回调
|
|||
|
|
|
|||
|
|
//private Task processTask;
|
|||
|
|
|
|||
|
|
public async Task StopAsync(IProgress<string> progress = null)
|
|||
|
|
{
|
|||
|
|
progress?.Report("正在停止控制中心...");
|
|||
|
|
|
|||
|
|
// 停止健康监控
|
|||
|
|
if (_healthMonitorTimer != null)
|
|||
|
|
{
|
|||
|
|
_healthMonitorTimer.Stop();
|
|||
|
|
_healthMonitorTimer.Dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取消所有操作
|
|||
|
|
if (_cts != null)
|
|||
|
|
{
|
|||
|
|
_cts.Cancel();
|
|||
|
|
}
|
|||
|
|
_isWorkerRunning = false;
|
|||
|
|
_taskEvent.Set();
|
|||
|
|
|
|||
|
|
// 等待心跳任务完成
|
|||
|
|
if (_heartbeatTask != null)
|
|||
|
|
{
|
|||
|
|
progress?.Report("停止心跳任务...");
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 使用Wait带超时
|
|||
|
|
_heartbeatTask.Wait(5000);
|
|||
|
|
}
|
|||
|
|
catch (AggregateException) { }
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 停止所有PLC读取线程
|
|||
|
|
progress?.Report("停止PLC读取线程...");
|
|||
|
|
StopAndCleanOldThreads();
|
|||
|
|
|
|||
|
|
// 等待工作线程完成
|
|||
|
|
if (_workerThread != null && _workerThread.IsAlive)
|
|||
|
|
{
|
|||
|
|
progress?.Report("停止工作线程...");
|
|||
|
|
_workerThread.Join(5000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理队列
|
|||
|
|
int remainingTasks = _taskQueue.Count;
|
|||
|
|
if (remainingTasks > 0)
|
|||
|
|
{
|
|||
|
|
progress?.Report($"清理剩余{remainingTasks}个任务...");
|
|||
|
|
while (_taskQueue.TryDequeue(out _)) { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
progress?.Report("控制中心已停止");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
if (_isDisposed) return;
|
|||
|
|
_isDisposed = true;
|
|||
|
|
|
|||
|
|
Dispose(true);
|
|||
|
|
GC.SuppressFinalize(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected virtual void Dispose(bool disposing)
|
|||
|
|
{
|
|||
|
|
if (disposing)
|
|||
|
|
{
|
|||
|
|
_isWorkerRunning = false;
|
|||
|
|
_taskEvent.Set();
|
|||
|
|
_taskEvent.Dispose();
|
|||
|
|
|
|||
|
|
if (_cts != null)
|
|||
|
|
{
|
|||
|
|
_cts.Cancel();
|
|||
|
|
_cts.Dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_healthMonitorTimer != null)
|
|||
|
|
{
|
|||
|
|
_healthMonitorTimer.Stop();
|
|||
|
|
_healthMonitorTimer.Dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_dbSemaphore?.Dispose();
|
|||
|
|
_plcWriteSemaphore?.Dispose();
|
|||
|
|
|
|||
|
|
if (_timerRefreshData != null)
|
|||
|
|
{
|
|||
|
|
_timerRefreshData.Stop();
|
|||
|
|
_timerRefreshData.Dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
plc.Disconnect();
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CommonMethods.plcDevices?.Clear();
|
|||
|
|
_plcDeviceMapping?.Clear();
|
|||
|
|
_plcAlarmReaders?.Clear();
|
|||
|
|
CommonMethods.plcStatuses?.Clear();
|
|||
|
|
|
|||
|
|
_groupStatus?.Clear();
|
|||
|
|
_stationFunctions?.Clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 7、辅助方法
|
|||
|
|
|
|||
|
|
private static PLCType GetPLCTypeEnum(string plcTypeString)
|
|||
|
|
{
|
|||
|
|
if (Enum.TryParse(plcTypeString, out PLCType plcType))
|
|||
|
|
{
|
|||
|
|
return plcType;
|
|||
|
|
}
|
|||
|
|
throw new ArgumentException($"PLC 类型无效: {plcTypeString}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 诊断日志
|
|||
|
|
|
|||
|
|
private void StartDiagnosticMonitor()
|
|||
|
|
{
|
|||
|
|
Thread monitorThread = new Thread(() =>
|
|||
|
|
{
|
|||
|
|
while (!_cts.Token.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
Thread.Sleep(60000); // 每分钟输出一次报告
|
|||
|
|
|
|||
|
|
// 检查关键工站是否卡死
|
|||
|
|
CheckStuckStation("物流线料盘进站");
|
|||
|
|
CheckStuckStation("物流线料盘来料");
|
|||
|
|
CheckStuckStation("电池进站");
|
|||
|
|
|
|||
|
|
// 输出诊断报告
|
|||
|
|
var report = StationDiagnostics.GetDiagnosticReport();
|
|||
|
|
LogHelper.Instance.WriteLog(report);
|
|||
|
|
|
|||
|
|
// 输出系统状态
|
|||
|
|
LogHelper.Instance.WriteLog($"【系统状态】队列大小={_taskQueue.Count} | 工作线程运行={_isWorkerRunning} | 总处理={_totalProcessedCount} | 总拒绝={_totalRejectedCount}");
|
|||
|
|
|
|||
|
|
// 检查PLC连接状态
|
|||
|
|
foreach (var plc in CommonMethods.plcDevices)
|
|||
|
|
{
|
|||
|
|
if (plc != null)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteLog($"【PLC状态】{plc.Name} | IsConnected={plc.IsConnected}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"监控线程异常:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
monitorThread.IsBackground = true;
|
|||
|
|
monitorThread.Name = "DiagnosticMonitor";
|
|||
|
|
monitorThread.Start();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetStationStatus(string stationName)
|
|||
|
|
{
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
var status = _groupStatus.ContainsKey(stationName) ? _groupStatus[stationName] : -1;
|
|||
|
|
var queueSize = _taskQueue.Count;
|
|||
|
|
var workerAlive = _workerThread?.IsAlive ?? false;
|
|||
|
|
|
|||
|
|
return $"工站:{stationName} 状态:{status} 队列:{queueSize} 工作线程存活:{workerAlive}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 手动重置卡死的工站
|
|||
|
|
public void ResetStation(string stationName)
|
|||
|
|
{
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
if (_groupStatus.ContainsKey(stationName))
|
|||
|
|
{
|
|||
|
|
_groupStatus[stationName] = 0;
|
|||
|
|
LogHelper.Instance.WriteLog($"手动重置工站: {stationName}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void CheckStuckStation(string stationName)
|
|||
|
|
{
|
|||
|
|
// 检查是否有工站卡在处理中状态超过30秒
|
|||
|
|
lock (_statusLock)
|
|||
|
|
{
|
|||
|
|
if (_groupStatus.ContainsKey(stationName) && _groupStatus[stationName] == 1)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-卡死检测】{stationName} | 状态为1(处理中)已超过30秒,可能已卡死");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
public class StationDiagnostics
|
|||
|
|
{
|
|||
|
|
private static readonly object _lock = new object();
|
|||
|
|
private static Dictionary<string, Queue<DateTime>> _triggerHistory = new Dictionary<string, Queue<DateTime>>();
|
|||
|
|
private static Dictionary<string, int> _timeoutCount = new Dictionary<string, int>();
|
|||
|
|
private static Dictionary<string, DateTime> _lastTriggerTime = new Dictionary<string, DateTime>();
|
|||
|
|
private static Dictionary<string, DateTime> _lastCompleteTime = new Dictionary<string, DateTime>();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 记录触发信号
|
|||
|
|
/// </summary>
|
|||
|
|
public static void LogTrigger(string stationName, short trigValue, int status)
|
|||
|
|
{
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
if (!_triggerHistory.ContainsKey(stationName))
|
|||
|
|
_triggerHistory[stationName] = new Queue<DateTime>();
|
|||
|
|
|
|||
|
|
_triggerHistory[stationName].Enqueue(DateTime.Now);
|
|||
|
|
|
|||
|
|
// 只保留最近100条记录
|
|||
|
|
while (_triggerHistory[stationName].Count > 100)
|
|||
|
|
_triggerHistory[stationName].Dequeue();
|
|||
|
|
|
|||
|
|
_lastTriggerTime[stationName] = DateTime.Now;
|
|||
|
|
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-触发】{stationName} | Trig={trigValue} | Status={status} | 队列长度={_triggerHistory[stationName].Count}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 记录任务入队
|
|||
|
|
/// </summary>
|
|||
|
|
public static void LogTaskEnqueued(string stationName, int queueSize)
|
|||
|
|
{
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-入队】{stationName} | 当前队列大小={queueSize}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 记录任务开始执行
|
|||
|
|
/// </summary>
|
|||
|
|
public static void LogTaskStart(string stationName)
|
|||
|
|
{
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
var now = DateTime.Now;
|
|||
|
|
if (_lastTriggerTime.ContainsKey(stationName))
|
|||
|
|
{
|
|||
|
|
var waitTime = (now - _lastTriggerTime[stationName]).TotalMilliseconds;
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-执行开始】{stationName} | 等待时间={waitTime:F0}ms");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-执行开始】{stationName}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 记录任务完成
|
|||
|
|
/// </summary>
|
|||
|
|
public static void LogTaskComplete(string stationName, bool success, string errorMsg = "")
|
|||
|
|
{
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
_lastCompleteTime[stationName] = DateTime.Now;
|
|||
|
|
LogHelper.Instance.WriteLog($"【诊断-执行完成】{stationName} | 成功={success} | 错误={errorMsg}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 记录超时
|
|||
|
|
/// </summary>
|
|||
|
|
public static void LogTimeout(string stationName, int timeoutSeconds)
|
|||
|
|
{
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
if (!_timeoutCount.ContainsKey(stationName))
|
|||
|
|
_timeoutCount[stationName] = 0;
|
|||
|
|
|
|||
|
|
_timeoutCount[stationName]++;
|
|||
|
|
|
|||
|
|
LogHelper.Instance.WriteError($"【诊断-超时】{stationName} | 超时时间={timeoutSeconds}秒 | 累计超时次数={_timeoutCount[stationName]}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取诊断报告
|
|||
|
|
/// </summary>
|
|||
|
|
public static string GetDiagnosticReport()
|
|||
|
|
{
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== 工站诊断报告 ===");
|
|||
|
|
|
|||
|
|
foreach (var station in _triggerHistory.Keys)
|
|||
|
|
{
|
|||
|
|
sb.AppendLine($"工站: {station}");
|
|||
|
|
sb.AppendLine($" 最近触发时间: {(_lastTriggerTime.ContainsKey(station) ? _lastTriggerTime[station].ToString() : "无")}");
|
|||
|
|
sb.AppendLine($" 最近完成时间: {(_lastCompleteTime.ContainsKey(station) ? _lastCompleteTime[station].ToString() : "无")}");
|
|||
|
|
sb.AppendLine($" 触发次数(最近100): {_triggerHistory[station].Count}");
|
|||
|
|
sb.AppendLine($" 超时次数: {(_timeoutCount.ContainsKey(station) ? _timeoutCount[station] : 0)}");
|
|||
|
|
|
|||
|
|
if (_lastTriggerTime.ContainsKey(station) && _lastCompleteTime.ContainsKey(station))
|
|||
|
|
{
|
|||
|
|
var processingTime = _lastCompleteTime[station] - _lastTriggerTime[station];
|
|||
|
|
sb.AppendLine($" 最近处理耗时: {processingTime.TotalMilliseconds:F0}ms");
|
|||
|
|
}
|
|||
|
|
sb.AppendLine();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
}
|