Files
1258/JinYuan.ControlCenter/ControlCenter.cs
T
2026-08-04 18:36:40 +08:00

2838 lines
118 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}