#region << 版 本 注 释 >> /*-------------------------------------------------------------------- * 版权所有 (c) 2025 WenJY 保留所有权利。 * CLR版本:4.0.30319.42000 * 机器名称:Mr.Wen's MacBook Pro * 命名空间:Sln.Imm.Daemon.Business * 唯一标识:A7D0F481-367C-4C8F-9E27-416639212E62 * * 创建者:WenJY * 电子邮箱: * 创建时间:2025-12-19 09:57:36 * 版本:V1.0.0 * 描述: * *-------------------------------------------------------------------- * 修改人: * 时间: * 修改说明: * * 版本:V1.0.0 *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> using Dm.util; using Models; using Newtonsoft.Json; using Sln.Imm.Daemon.Cache; using Sln.Imm.Daemon.Model.dao; using Sln.Imm.Daemon.Model.dto; using Sln.Imm.Daemon.Opc; using Sln.Imm.Daemon.Opc.Impl; using Sln.Imm.Daemon.Repository.service; using Sln.Imm.Daemon.Repository.service.@base; using Sln.Imm.Daemon.Serilog; using SqlSugar; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Threading; namespace Sln.Imm.Daemon.Business; public class DeviceDataCollector : IDisposable { private readonly SerilogHelper _serilog; private readonly SemaphoreSlim _semaphore; // 并发控制:限制同时采集的设备数(避免资源耗尽) private CancellationTokenSource _cts; // 取消令牌:用于终止所有采集任务 private readonly int _collectIntervalMs; // 循环采集间隔(毫秒) private readonly int _collectAlarmMs; // 循环采集三色灯间隔(毫秒) private bool _isCollecting; // 是否正在采集(防止重复启动) private readonly BaseDeviceInfoCacheService _cacheService; private readonly IBaseDeviceParamService _paramAlarmService; private readonly IBaseService _paramValService; private readonly IBaseDeviceAlarmValService _alarmValService; private readonly IBaseRecordShutDownService _shutDownValService; // 在类级别添加这个字典来存储每个设备的上一次状态 private readonly ConcurrentDictionary _deviceLastAlarmStates = new ConcurrentDictionary(); // 新增:存储每个设备Alarm触发的状态 private readonly ConcurrentDictionary _deviceAlarmTriggerStates = new ConcurrentDictionary(); // 新增:存储每个设备停机触发的状态 private readonly ConcurrentDictionary _deviceShutDownTriggerStates = new ConcurrentDictionary(); private Dictionary _deviceAlarmStatus = new Dictionary(); private static readonly TimeSpan DisconnectTimeout = TimeSpan.FromSeconds(5); private const int AlarmDeviceIntervalMs = 5000; // 每台设备采集完成后,等待 5 秒再采集下一台 private const int AlarmRoundIntervalMs = 10000; // 每轮采集完成后,固定等待 10 秒再开始下一轮 public DeviceDataCollector(SerilogHelper serilogHelper, BaseDeviceInfoCacheService cacheService, IBaseService paramValService, IBaseDeviceParamService paramAlarmService, IBaseDeviceAlarmValService alarmValService, IBaseRecordShutDownService shutDownValService, int maxConcurrentDevices = 15) { _serilog = serilogHelper; _cacheService = cacheService; _paramValService = paramValService; _paramAlarmService = paramAlarmService; _alarmValService = alarmValService; _shutDownValService = shutDownValService; _semaphore = new SemaphoreSlim(maxConcurrentDevices, maxConcurrentDevices); _cts = new CancellationTokenSource(); _collectIntervalMs = 1000 * 60 *10 ; //设备数据采集时间间隔 _isCollecting = false; } #region 私有类 /// /// 设备报警状态结构 /// private class DeviceAlarmState { public bool LastRunning { get; set; } public bool LastAlarm { get; set; } public bool LastStopped { get; set; } public DateTime LastUpdateTime { get; set; } public bool IsFirstTime { get; set; } = true; // 标记是否为第一次采集 } /// /// Alarm触发状态结构 /// private class AlarmTriggerState { public bool LastAlarmValue { get; set; } public long? AlarmRecordId { get; set; } // 存储插入的Alarm记录ID,用于后续更新 public DateTime LastTriggerTime { get; set; } public bool IsAlarmActive { get; set; } // 标记当前是否有活跃的Alarm记录 } /// /// 停机触发状态结构 /// private class ShutDownTriggerState { public bool LastStoppedValue { get; set; } public DateTime LastTriggerTime { get; set; } public bool IsShutDownActive { get; set; } // 标记当前是否有活跃的停机记录 } #endregion #region 循环采集 /// /// 启动15设备并行循环采集 /// /// 15台设备列表 /// 循环次数(-1=无限) public async Task StartParallelLoopCollectAsync(int loopCount = -1) { if (_isCollecting) { Console.WriteLine($"[{DateTime.Now}] 并行采集已在运行,无需重复启动"); return; } _isCollecting = true; int currentLoop = 0; try { while (!_cts.Token.IsCancellationRequested) { // 达到指定循环次数退出 if (loopCount > 0 && currentLoop >= loopCount) { Console.WriteLine($"[{DateTime.Now}] 完成指定循环次数({loopCount}次),停止采集"); break; } currentLoop++; Console.WriteLine($"\n========== 第 {currentLoop} 轮并行采集开始 [{DateTime.Now}] =========="); // 记录本轮开始时间(保证10秒间隔精准) var roundStartTime = DateTime.Now; // 核心:并行采集15台设备(真正同时启动) var collectResult = await CollectDevicesInParallelAsync(); // 输出本轮结果 OutputCollectResult(collectResult); // 计算耗时,补足10秒间隔 var roundCost = (DateTime.Now - roundStartTime).TotalMilliseconds; var waitMs = _collectIntervalMs - roundCost; if (waitMs > 0) { Console.WriteLine($"\n第 {currentLoop} 轮采集完成(耗时{roundCost:F0}ms),等待{waitMs:F0}ms后下一轮"); await Task.Delay((int)waitMs, _cts.Token); } else { Console.WriteLine($"\n第 {currentLoop} 轮采集超时(耗时{roundCost:F0}ms),立即开始下一轮"); } } } catch (OperationCanceledException) { Console.WriteLine($"[{DateTime.Now}] 并行采集已手动终止"); } catch (Exception ex) { Console.WriteLine($"[{DateTime.Now}] 并行采集异常:{ex.Message}", ex); } finally { _isCollecting = false; Console.WriteLine($"[{DateTime.Now}] 并行采集循环结束"); } } /// /// 启动报警信息监控循环 /// public async Task StartAlarmMonitorLoopSimpleAsync() { try { Console.WriteLine($"[{DateTime.Now}] 报警监控开始(每轮完成后固定等待 {AlarmRoundIntervalMs / 1000} 秒)"); while (!_cts.Token.IsCancellationRequested) { // 记录本轮开始时间 var startTime = DateTime.Now; try { // 执行报警采集 await CollectDevicesAlarmAsync(); } catch (Exception ex) { Console.WriteLine($"[报警监控] 采集异常: {ex.Message}"); _serilog.Error($"报警监控采集异常", ex); } // 计算耗时 var elapsedMs = (DateTime.Now - startTime).TotalMilliseconds; // 固定间隔:每轮采集结束后等待 30 秒再开始下一轮(不再做“精准补齐间隔”) Console.WriteLine($"[报警监控] 本轮耗时 {elapsedMs:F0}ms,等待 {AlarmRoundIntervalMs}ms 后下一轮"); await Task.Delay(AlarmRoundIntervalMs, _cts.Token); } } catch (OperationCanceledException) { Console.WriteLine($"[{DateTime.Now}] 报警监控已停止"); } catch (Exception ex) { Console.WriteLine($"[{DateTime.Now}] 报警监控异常:{ex.Message}"); _serilog.Error($"报警监控异常", ex); } finally { Console.WriteLine($"[{DateTime.Now}] 报警监控结束"); } } /// /// 输出采集结果 /// private void OutputCollectResult(Dictionary collectResult) { Console.WriteLine($"\n[{DateTime.Now}] 本轮采集结果汇总:"); foreach (var kvp in collectResult) { var deviceAddress = kvp.Key; var result = kvp.Value; if (result is Exception ex) { Console.WriteLine($" {deviceAddress}:失败 - {ex.Message}"); } else { Console.WriteLine($" {deviceAddress}:成功 - {JsonConvert.SerializeObject(result)}"); } } } public async Task> CollectDevicesInParallelAsync() { var deviceInfos = await _cacheService.GetValueAsync("BaseDeviceInfoCache"); if (deviceInfos == null || deviceInfos.Count == 0) throw new ArgumentException("设备列表不能为空", nameof(deviceInfos)); var resultDict = new Dictionary(); var taskList = new List(); foreach (var item in deviceInfos) { // 每个设备创建独立的采集任务 taskList.Add(CollectSingleDeviceParallelAsync(item, resultDict, _cts.Token)); } // 等待所有任务完成(无论成功/失败) await Task.WhenAll(taskList); return resultDict; } /// /// 采集三色灯报警:顺序循环采集每台设备,每台采集完成后等待 10 秒再采集下一台;一轮完成后间隔时间不变。 /// public async Task> CollectDevicesAlarmAsync() { var deviceInfos = await _cacheService.GetValueAsync("BaseDeviceInfoCache"); if (deviceInfos == null || deviceInfos.Count == 0) { Console.WriteLine($"[报警监控] 无设备信息"); throw new ArgumentException("设备列表不能为空", nameof(deviceInfos)); } Console.WriteLine($"[报警监控] 开始采集 {deviceInfos.Count} 台设备(顺序采集,每台间隔 {AlarmDeviceIntervalMs / 1000} 秒)"); var resultDict = new Dictionary(); for (var i = 0; i < deviceInfos.Count; i++) { if (_cts.Token.IsCancellationRequested) break; var device = deviceInfos[i]; await CollectOneDeviceAlarmAsync(device, resultDict, _cts.Token); // 每台设备采集完成后等待 10 秒再采集下一台(最后一台后不等待) if (i < deviceInfos.Count - 1) { await Task.Delay(AlarmDeviceIntervalMs, _cts.Token); } } Console.WriteLine($"[报警监控] 本轮采集完成(共 {deviceInfos.Count} 台设备,结果数: {resultDict.Count})"); return resultDict; } /// /// 顺序模式下单台设备报警采集(无信号量,由上层循环调用)。 /// private async Task CollectOneDeviceAlarmAsync( BaseDeviceInfo device, Dictionary resultDict, CancellationToken cancellationToken) { IOpcService opcUa = null; List opcItemValues = null; try { if (device.deviceFacture.Contains("伊之密")) { opcUa = new OpcUaService(); } else if (device.deviceFacture.Contains("老设备")) { opcUa = new OpcDaService(); } else { return; } bool connectResult = await opcUa.ConnectAsync(device.networkAddress); if (!connectResult) { Console.WriteLine($"报警信息 - [{DateTime.Now}] 设备 {device.deviceName} 连接失败"); resultDict[device.networkAddress] = new Exception($"设备 {device.deviceName} 连接失败"); return; } _serilog.Info($"报警信息开始采集{device.deviceName};"); opcItemValues = await ReadAlarm(device, opcUa); if (opcItemValues == null || opcItemValues.Count == 0) { Console.WriteLine($"报警信息 - [{DateTime.Now}] {device.deviceName} - 未读取到报警点位"); resultDict[device.networkAddress] = opcItemValues ?? new List(); return; } if (HasAlarmStateChanged(device, opcItemValues)) { _serilog.Info($"{device.deviceName}三色灯状态改变"); try { SaveParam(device, opcItemValues, out List paramValues); } catch (Exception ex) { _serilog.Error($"设备 {device.deviceName} 保存参数失败", ex); } } //报警处理 try { int should = ShouldRecordAlarmTrue(device, opcItemValues); if (should == 1) { _serilog.Info($"{device.deviceName}报警触发"); BaseDeviceAlarmVal paramVal = new BaseDeviceAlarmVal { DEVICE_ID = device.objid, ALARM_BEGIN_TIME = DateTime.Now, CONTINUE_TIME = 0 }; _alarmValService.Insert(paramVal); } else if (should == 2) { _serilog.Info($"{device.deviceName}报警恢复"); _alarmValService.UpdateAlarmVal(device.objid); } } catch (Exception ex) { _serilog.Error($"设备 {device.deviceName} 报警记录操作失败", ex); } // 处理停机记录 try { int shutDownShould = ShouldRecordShutDownTrue(device, opcItemValues); if (shutDownShould == 1) { _serilog.Info($"{device.deviceName}停机触发"); BaseRecordShutDown shutDownVal = new BaseRecordShutDown { MACHINE_ID = device.objid, SHUT_TYPE_ID = 1, SHUT_REASON_ID = 1, SHUT_REASON = "R001", SHUT_BEGIN_TIME = DateTime.Now, SHUT_TIME = 0, DOWNTIME_FLAG = "0", ACTIVE_FLAG = "1" }; _shutDownValService.Insert(shutDownVal); } else if (shutDownShould == 2) { _serilog.Info($"{device.deviceName}停机恢复"); _shutDownValService.UpdateShutDownVal(device.objid); } } catch (Exception ex) { _serilog.Error($"设备 {device.deviceName} 停机记录操作失败", ex); } resultDict[device.networkAddress] = opcItemValues; } catch (Exception ex) { _serilog.Error($"设备 {device.deviceName} 三色灯采集异常", ex); resultDict[device.networkAddress] = new Exception($"设备 {device.deviceName} 三色灯采集异常:{ex.Message}", ex); } finally { if (opcUa != null) { try { await SafeDisconnectAsync(opcUa, device.deviceName).ConfigureAwait(false); } catch (Exception ex) { _serilog.Error($"设备 {device.deviceName} 断开连接失败", ex); } } } } private async Task CollectSingleDeviceParallelAsync(BaseDeviceInfo device, Dictionary resultDict, CancellationToken cancellationToken) { await _semaphore.WaitAsync(cancellationToken); List opcItemValues = null; IOpcService opcUa = null; try { if (device.deviceFacture.Contains("伊之密")) { opcUa =new OpcUaService(); } else if(device.deviceFacture.Contains("老设备")) { opcUa =new OpcDaService(); } else { return; } Console.WriteLine($"[{DateTime.Now}] {device.deviceName} - 开始连接并采集"); bool connectResult = await opcUa.ConnectAsync(device.networkAddress); if (!connectResult) { resultDict[device.networkAddress] = new Exception($"设备 {device.deviceName} 连接失败"); Console.WriteLine($"[{DateTime.Now}] 设备 {device.deviceName} 连接失败"); return; } Console.WriteLine($"[{DateTime.Now}] 设备 {device.deviceName} 连接成功"); // 2. 读取设备数据(你封装的ReadParam方法) //Console.WriteLine($"[{DateTime.Now}] 开始读取设备 {device.deviceName} 数据"); _serilog.Info($"开始采集{device.deviceName};"); opcItemValues = await ReadParam(device, opcUa); this.SaveParam(device, opcItemValues, out List paramValues); _serilog.Info($"{device.deviceName}数据采集完成:{JsonConvert.SerializeObject(opcItemValues)}"); //Console.WriteLine($"[{DateTime.Now}] 设备 {device.deviceName} 数据读取完成"); // 3. 存储读取结果 resultDict[device.networkAddress] = opcItemValues; } catch (Exception ex) { // 捕获采集过程中的异常 resultDict[device.networkAddress] = new Exception($"设备 {device.deviceName} 采集异常:{ex.Message}", ex); Console.WriteLine($"[{DateTime.Now}] 设备 {device.deviceName} 采集异常:{ex.Message}"); } finally { // 无论成功/失败,都断开设备连接(释放资源) try { await SafeDisconnectAsync(opcUa, device.deviceName).ConfigureAwait(false); } catch (Exception ex) { Console.WriteLine($"[{DateTime.Now}] 设备 {device.deviceName} 断开连接失败:{ex.Message}"); } // 释放信号量(允许下一个设备采集) _semaphore.Release(); } } private static async Task SafeDisconnectAsync(IOpcService opcUa, string deviceName) { if (opcUa == null) return; Console.WriteLine($"[{DateTime.Now}] 设备 {deviceName} 开始断开连接..."); var disconnectTask = opcUa.DisconnectAsync(); var finished = await Task.WhenAny(disconnectTask, Task.Delay(DisconnectTimeout)).ConfigureAwait(false); if (finished != disconnectTask) { Console.WriteLine($"[{DateTime.Now}] 设备 {deviceName} 断开连接超时(>{DisconnectTimeout.TotalSeconds:F0}s),跳过等待以避免阻塞"); return; } await disconnectTask.ConfigureAwait(false); Console.WriteLine($"[{DateTime.Now}] 设备 {deviceName} 已断开连接"); } #endregion /// /// 读取设备参数 /// /// public async Task> ReadParam(BaseDeviceInfo device,IOpcService opcUa) { try { if (device == null) { throw new ArgumentNullException($"设备信息不允许为空"); } List deviceParams = device.deviceParams.Select(x => x.paramAddr).ToList(); Stopwatch sw = Stopwatch.StartNew(); List infos = await opcUa.ReadNodeAsync(deviceParams); sw.Stop(); _serilog.Info($"opc读取耗时{sw.ElapsedMilliseconds.ToString()}"); return infos; } catch (Exception e) { throw new InvalidOperationException($"设备参数读取异常:{e.Message}"); } } /// /// 读取设备报警 /// /// /// /// public async Task> ReadAlarm(BaseDeviceInfo device, IOpcService opcUa) { try { if (device == null) { throw new ArgumentNullException($"设备信息不允许为空"); } if (opcUa == null) { throw new ArgumentNullException($"OPC服务不允许为空"); } List deviceParams = new List(); List paramList = _paramAlarmService.GetDeviceAlarmParams(device); if (paramList == null || paramList.Count == 0) return null; foreach (BaseDeviceParam param in paramList) { if (string.IsNullOrEmpty(param?.paramAddr)) continue; deviceParams.Add(param.paramAddr); } if (deviceParams.Count == 0) return null; List infos = await opcUa.ReadNodeAsync(deviceParams); return infos; } catch (Exception e) { throw new InvalidOperationException($"设备参数读取异常:{e.Message}", e); } } /// /// 保存设备参数值到数据库 /// /// 设备信息 /// OPC节点值列表 /// 输出参数值DTO列表 public void SaveParam(BaseDeviceInfo device, List opcItemValues, out List paramValues) { paramValues = new List(); try { foreach (OpcNode opcItem in opcItemValues) { BaseDeviceParamVal deviceParamVal = new BaseDeviceParamVal(); var paramInfo = device.deviceParams.Where(x => x.paramAddr == opcItem.NodeId).FirstOrDefault(); if (paramInfo != null) { deviceParamVal.paramCode = paramInfo.paramCode; deviceParamVal.paramName = paramInfo.paramName; } deviceParamVal.deviceCode = device.deviceCode; deviceParamVal.deviceId = device.objid; if(opcItem.Value != null) { deviceParamVal.paramValue = opcItem.Value.ToString(); } else { deviceParamVal.paramValue = "无"; } deviceParamVal.paramType = opcItem.DataType; deviceParamVal.collectTime = DateTime.Now; deviceParamVal.recordTime = DateTime.Now; paramValues.Add(deviceParamVal); } var isRes = _paramValService.Insert(paramValues); if (isRes) { _serilog.Info(($"{device.deviceName} 设备参数保存成功")); } else { _serilog.Info(($"{device.deviceName} 设备参数保存失败")); } }catch (Exception e) { _serilog.Error($"DevicedataCollection报错", e); throw new InvalidOperationException($"设备参数保存异常:{e.Message}"); } } /// /// 检查报警状态是否有变化 /// /// /// /// private bool HasAlarmStateChanged(BaseDeviceInfo device, List currentOpcItemValues) { try { // 1. 从当前数据中提取三个关键报警点位 OpcNode runningNode = null; OpcNode alarmNode = null; OpcNode stoppedNode = null; foreach (var node in currentOpcItemValues) { if (node.NodeId.Contains("Running", StringComparison.OrdinalIgnoreCase)) runningNode = node; else if (node.NodeId.Contains("Alarm", StringComparison.OrdinalIgnoreCase)) alarmNode = node; else if (node.NodeId.Contains("Stopped", StringComparison.OrdinalIgnoreCase)) stoppedNode = node; } // 如果三个点位没有全部找到,认为数据不完整,不进行处理 if (runningNode == null || alarmNode == null || stoppedNode == null) { Console.WriteLine($"报警信息 - [{DateTime.Now}] 设备 {device.deviceName} - 未找到完整的报警点位(Running/Alarm/Stopped)"); return false; } // 2. 解析当前状态值 bool currentRunning = GetBoolValue(runningNode.Value); bool currentAlarm = GetBoolValue(alarmNode.Value); bool currentStopped = GetBoolValue(stoppedNode.Value); // 3. 获取或创建该设备的上一次状态记录 string deviceKey = device.objid.ToString(); // 使用设备ID作为字典键 bool hasChanged = false; // 使用线程安全的方式获取或创建设备状态 _deviceLastAlarmStates.AddOrUpdate(deviceKey, // 如果设备第一次采集,创建新状态记录 (key) => { // 第一次采集,创建初始状态记录 var newState = new DeviceAlarmState { LastRunning = currentRunning, LastAlarm = currentAlarm, LastStopped = currentStopped, LastUpdateTime = DateTime.Now, IsFirstTime = false // 标记为已初始化 }; Console.WriteLine($"报警信息 - [{DateTime.Now}] 设备 {device.deviceName} - 首次采集,创建初始状态记录"); hasChanged = true; // 第一次采集总是保存 return newState; }, // 如果设备已有状态记录,进行比较和更新 (key, existingState) => { // 检查状态是否有变化 bool runningChanged = existingState.LastRunning != currentRunning; bool alarmChanged = existingState.LastAlarm != currentAlarm; bool stoppedChanged = existingState.LastStopped != currentStopped; // 只要有一个状态发生变化,就认为有变化 hasChanged = runningChanged || alarmChanged || stoppedChanged; if (hasChanged) { // 记录变化详情 Console.WriteLine($"报警信息 - [{DateTime.Now}] 设备 {device.deviceName} - 状态变化: " + $"R[{existingState.LastRunning}->{currentRunning}], " + $"A[{existingState.LastAlarm}->{currentAlarm}], " + $"S[{existingState.LastStopped}->{currentStopped}]"); // 更新为最新状态 existingState.LastRunning = currentRunning; existingState.LastAlarm = currentAlarm; existingState.LastStopped = currentStopped; existingState.LastUpdateTime = DateTime.Now; } else { Console.WriteLine($"报警信息 - [{DateTime.Now}] 设备 {device.deviceName} - 状态无变化: " + $"R[{currentRunning}], A[{currentAlarm}], S[{currentStopped}]"); } return existingState; }); return hasChanged; } catch (Exception ex) { Console.WriteLine($"报警信息 - [{DateTime.Now}] 设备 {device.deviceName} - 检查状态变化异常: {ex.Message}"); _serilog.Error($"设备 {device.deviceName} 检查状态变化异常", ex); return false; // 出现异常时,不保存 } } /// /// 检查是否需要记录Alarm为true的情况(新增逻辑) /// /// 设备信息 /// 当前采集的OPC点位值 /// true表示需要记录Alarm为true,false表示不需要记录 private int ShouldRecordAlarmTrue(BaseDeviceInfo device, List currentOpcItemValues) { try { // 1. 从当前数据中提取Alarm点位 OpcNode alarmNode = null; foreach (var node in currentOpcItemValues) { if (node.NodeId.Contains("Alarm", StringComparison.OrdinalIgnoreCase)) { alarmNode = node; break; } } // 如果Alarm点位没有找到,不进行处理 if (alarmNode == null) { Console.WriteLine($"Alarm记录 - [{DateTime.Now}] 设备 {device.deviceName} - 未找到Alarm点位"); return 0; } // 2. 解析当前Alarm状态值 bool currentAlarm = GetBoolValue(alarmNode.Value); // 3. 获取设备当前的Alarm触发状态 string deviceKey = device.objid.ToString(); // 使用线程安全的方式获取或创建设备Alarm触发状态 int operationType = 0; // 0-无操作,1-插入,2-更新 _deviceAlarmTriggerStates.AddOrUpdate(deviceKey, // 如果设备第一次检测到Alarm,创建新状态记录 (key) => { // 第一次检测到Alarm,创建初始状态记录 var newState = new AlarmTriggerState { LastAlarmValue = currentAlarm, AlarmRecordId = null, LastTriggerTime = DateTime.Now, IsAlarmActive = false }; // 如果是第一次采集且Alarm为true,需要插入记录 if (currentAlarm) { Console.WriteLine($"Alarm记录 - [{DateTime.Now}] 设备 {device.deviceName} - 首次检测到Alarm为true,需要插入记录"); operationType = 1; // 需要插入记录 newState.IsAlarmActive = true; // 标记有活跃的Alarm记录 } else { Console.WriteLine($"Alarm记录 - [{DateTime.Now}] 设备 {device.deviceName} - 首次检测到Alarm为false,无需操作"); } return newState; }, // 如果设备已有Alarm触发状态记录,进行比较 (key, existingState) => { // 检查状态变化 if (existingState.LastAlarmValue == false && currentAlarm == true) { // 1: Alarm从false变为true,需要插入记录 operationType = 1; // 需要插入记录 existingState.IsAlarmActive = true; // 标记有活跃的Alarm记录 existingState.AlarmRecordId = null; // 重置记录ID,将在插入后设置 } else if (existingState.LastAlarmValue == true && currentAlarm == false) { // 2: Alarm从true变为false,需要更新记录 if (existingState.IsAlarmActive) { operationType = 2; // 需要更新记录 existingState.IsAlarmActive = false; // 标记Alarm记录已结束 } } else if (existingState.LastAlarmValue == true && currentAlarm == true) { // 3.Alarm持续为true,忽略 operationType = 0; } else // existingState.LastAlarmValue == false && currentAlarm == false { // 4.Alarm持续为false,忽略 operationType = 0; } // 更新状态 existingState.LastAlarmValue = currentAlarm; existingState.LastTriggerTime = DateTime.Now; return existingState; }); return operationType; } catch (Exception ex) { Console.WriteLine($"Alarm记录 - [{DateTime.Now}] 设备 {device.deviceName} - 检查Alarm记录异常: {ex.Message}"); _serilog.Error($"设备 {device.deviceName} 检查Alarm记录异常", ex); return 0; // 出现异常时,不操作 } } /// /// 检查是否需要记录停机为true的情况 /// /// 设备信息 /// 当前采集的OPC点位值 /// 0-无操作,1-插入,2-更新 private int ShouldRecordShutDownTrue(BaseDeviceInfo device, List currentOpcItemValues) { try { // 1. 从当前数据中提取Stopped点位 OpcNode stoppedNode = null; foreach (var node in currentOpcItemValues) { if (node.NodeId.Contains("Stopped", StringComparison.OrdinalIgnoreCase)) { stoppedNode = node; break; } } // 如果Stopped点位没有找到,不进行处理 if (stoppedNode == null) { Console.WriteLine($"停机记录 - [{DateTime.Now}] 设备 {device.deviceName} - 未找到Stopped点位"); return 0; } // 2. 解析当前停机状态值 bool currentStopped = GetBoolValue(stoppedNode.Value); // 3. 获取设备当前的停机触发状态 string deviceKey = device.objid.ToString(); // 使用线程安全的方式获取或创建设备停机触发状态 int operationType = 0; // 0-无操作,1-插入,2-更新 _deviceShutDownTriggerStates.AddOrUpdate(deviceKey, // 如果设备第一次检测到停机,创建新状态记录 (key) => { // 第一次检测到停机,创建初始状态记录 var newState = new ShutDownTriggerState { LastStoppedValue = currentStopped, LastTriggerTime = DateTime.Now, IsShutDownActive = false }; // 如果是第一次采集且停机为true,需要插入记录 if (currentStopped) { Console.WriteLine($"停机记录 - [{DateTime.Now}] 设备 {device.deviceName} - 首次检测到停机为true,需要插入记录"); operationType = 1; // 需要插入记录 newState.IsShutDownActive = true; // 标记有活跃的停机记录 } else { Console.WriteLine($"停机记录 - [{DateTime.Now}] 设备 {device.deviceName} - 首次检测到停机为false,无需操作"); } return newState; }, // 如果设备已有停机触发状态记录,进行比较 (key, existingState) => { // 检查状态变化 if (existingState.LastStoppedValue == false && currentStopped == true) { // 1: 停机从false变为true,需要插入记录 operationType = 1; // 需要插入记录 existingState.IsShutDownActive = true; // 标记有活跃的停机记录 } else if (existingState.LastStoppedValue == true && currentStopped == false) { // 2: 停机从true变为false,需要更新记录 if (existingState.IsShutDownActive) { operationType = 2; // 需要更新记录 existingState.IsShutDownActive = false; // 标记停机记录已结束 } } else if (existingState.LastStoppedValue == true && currentStopped == true) { // 3. 停机持续为true,忽略 operationType = 0; } else // existingState.LastStoppedValue == false && currentStopped == false { // 4. 停机持续为false,忽略 operationType = 0; } // 更新状态 existingState.LastStoppedValue = currentStopped; existingState.LastTriggerTime = DateTime.Now; return existingState; }); return operationType; } catch (Exception ex) { Console.WriteLine($"停机记录 - [{DateTime.Now}] 设备 {device.deviceName} - 检查停机记录异常: {ex.Message}"); _serilog.Error($"设备 {device.deviceName} 检查停机记录异常", ex); return 0; // 出现异常时,不操作 } } /// /// 安全获取bool值 /// /// /// private bool GetBoolValue(object value) { if (value == null) return false; try { if (value is bool boolValue) return boolValue; if (value is string stringValue) { if (bool.TryParse(stringValue, out bool result)) return result; // 尝试其他常见表示方式 if (stringValue == "1" || stringValue.Equals("true", StringComparison.OrdinalIgnoreCase)) return true; if (stringValue == "0" || stringValue.Equals("false", StringComparison.OrdinalIgnoreCase)) return false; } // 尝试转换 return Convert.ToBoolean(value); } catch { return false; } } /// /// 终止所有采集任务 /// public void CancelAllCollectTasks() { if (!_cts.IsCancellationRequested) { _cts.Cancel(); Console.WriteLine($"[{DateTime.Now}] 已触发采集任务终止"); } } /// /// 计算两个时间之间的持续时间(分钟),四舍五入取整 /// /// 开始时间 /// 结束时间 /// 持续时间的总分钟数(int类型,四舍五入) public int CalculateDurationMinutesRounded(DateTime startTime, DateTime endTime) { // 验证参数有效性 if (endTime < startTime) { // 这里可以根据需求处理,比如返回0或抛出异常 return 0; // 或者抛出异常:throw new ArgumentException("结束时间不能早于开始时间"); } // 计算时间差 TimeSpan duration = endTime - startTime; // 获取总分钟数(double类型),然后四舍五入转换为int return (int)Math.Round(duration.TotalMinutes); } public void Dispose() { throw new NotImplementedException(); } }