Files
ThingsGateway/src/Gateway/ThingsGateway.Gateway.Application/Services/Alarm/AlarmTask.cs
2025-08-07 19:19:36 +08:00

550 lines
26 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.

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using ThingsGateway.Common.Extension;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife.Extension;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 设备采集报警后台服务
/// </summary>
internal sealed class AlarmTask : IDisposable
{
private readonly ILogger _logger;
private ScheduledSyncTask scheduledTask;
public AlarmTask(ILogger logger)
{
_logger = logger;
}
public void StartTask(CancellationToken cancellationToken)
{
_logger.LogInformation(AppResource.RealAlarmTaskStart);
scheduledTask = new ScheduledSyncTask(10, DoWork, null, null, cancellationToken);
scheduledTask.Start();
}
public void StopTask()
{
_logger.LogInformation(AppResource.RealAlarmTaskStop);
scheduledTask?.Stop();
}
public void Dispose()
{
StopTask();
scheduledTask?.SafeDispose();
}
#region
/// <summary>
/// 获取bool报警类型
/// </summary>
/// <param name="tag">要检查的变量</param>
/// <param name="limit">报警限制值</param>
/// <param name="expressions">报警约束表达式</param>
/// <param name="text">报警文本</param>
/// <returns>报警类型枚举</returns>
private static AlarmTypeEnum? GetBoolAlarmCode(VariableRuntime tag, out string limit, out string expressions, out string text)
{
limit = string.Empty; // 初始化报警限制值为空字符串
expressions = string.Empty; // 初始化报警约束表达式为空字符串
text = string.Empty; // 初始化报警文本为空字符串
if (tag?.Value == null) // 检查变量是否为null或其值为null
{
return null; // 如果是则返回null
}
if (tag.AlarmPropertys.BoolCloseAlarmEnable && !tag.Value.ToBoolean(true)) // 检查是否启用了关闭报警功能并且变量的布尔值为false
{
limit = false.ToString(); // 将报警限制值设置为"false"
expressions = tag.AlarmPropertys.BoolCloseRestrainExpressions!; // 获取关闭报警的约束表达式
text = tag.AlarmPropertys.BoolCloseAlarmText!; // 获取关闭报警时的报警文本
return AlarmTypeEnum.Close; // 返回关闭报警类型枚举
}
if (tag.AlarmPropertys.BoolOpenAlarmEnable && tag.Value.ToBoolean(false)) // 检查是否启用了开启报警功能并且变量的布尔值为true
{
limit = true.ToString(); // 将报警限制值设置为"true"
expressions = tag.AlarmPropertys.BoolOpenRestrainExpressions!; // 获取开启报警的约束表达式
text = tag.AlarmPropertys.BoolOpenAlarmText!; // 获取开启报警时的报警文本
return AlarmTypeEnum.Open; // 返回开启报警类型枚举
}
return null; // 如果不符合任何报警条件则返回null
}
/// <summary>
/// 获取自定义报警类型
/// </summary>
/// <param name="tag">要检查的变量</param>
/// <param name="limit">报警限制值</param>
/// <param name="expressions">报警约束表达式</param>
/// <param name="text">报警文本</param>
/// <returns>报警类型枚举</returns>
private static AlarmTypeEnum? GetCustomAlarmDegree(VariableRuntime tag, out string limit, out string expressions, out string text)
{
limit = string.Empty; // 初始化报警限制值为空字符串
expressions = string.Empty; // 初始化报警约束表达式为空字符串
text = string.Empty; // 初始化报警文本为空字符串
if (tag?.Value == null) // 检查变量是否为null或其值为null
{
return null; // 如果是则返回null
}
if (tag.AlarmPropertys.CustomAlarmEnable) // 检查是否启用了自定义报警功能
{
// 调用变量的CustomAlarmCode属性的GetExpressionsResult方法传入变量的值获取报警表达式的计算结果
var result = tag.AlarmPropertys.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
if (result is bool boolResult) // 检查计算结果是否为布尔类型
{
if (boolResult) // 如果计算结果为true
{
limit = tag.AlarmPropertys.CustomAlarmCode; // 将报警限制值设置为自定义报警代码
expressions = tag.AlarmPropertys.CustomRestrainExpressions!; // 获取自定义报警时的报警约束表达式
text = tag.AlarmPropertys.CustomAlarmText!; // 获取自定义报警时的报警文本
return AlarmTypeEnum.Custom; // 返回自定义报警类型枚举
}
}
}
return null; // 如果不符合自定义报警条件则返回null
}
/// <summary>
/// 获取decimal类型的报警类型
/// </summary>
/// <param name="tag">要检查的变量</param>
/// <param name="limit">报警限制值</param>
/// <param name="expressions">报警约束表达式</param>
/// <param name="text">报警文本</param>
/// <returns>报警类型枚举</returns>
private static AlarmTypeEnum? GetDecimalAlarmDegree(VariableRuntime tag, out string limit, out string expressions, out string text)
{
limit = string.Empty; // 初始化报警限制值为空字符串
expressions = string.Empty; // 初始化报警约束表达式为空字符串
text = string.Empty; // 初始化报警文本为空字符串
if (tag?.Value == null) // 检查变量是否为null或其值为null
{
return null; // 如果是则返回null
}
// 检查是否启用了高高报警功能,并且变量的值大于高高报警的限制值
if (tag.AlarmPropertys.HHAlarmEnable && tag.Value.ToDecimal() > tag.AlarmPropertys.HHAlarmCode)
{
limit = tag.AlarmPropertys.HHAlarmCode.ToString()!; // 将报警限制值设置为高高报警的限制值
expressions = tag.AlarmPropertys.HHRestrainExpressions!; // 获取高高报警的约束表达式
text = tag.AlarmPropertys.HHAlarmText!; // 获取高高报警时的报警文本
return AlarmTypeEnum.HH; // 返回高高报警类型枚举
}
// 检查是否启用了高报警功能,并且变量的值大于高报警的限制值
if (tag.AlarmPropertys.HAlarmEnable && tag.Value.ToDecimal() > tag.AlarmPropertys.HAlarmCode)
{
limit = tag.AlarmPropertys.HAlarmCode.ToString()!; // 将报警限制值设置为高报警的限制值
expressions = tag.AlarmPropertys.HRestrainExpressions!; // 获取高报警的约束表达式
text = tag.AlarmPropertys.HAlarmText!; // 获取高报警时的报警文本
return AlarmTypeEnum.H; // 返回高报警类型枚举
}
// 检查是否启用了低低报警功能,并且变量的值小于低低报警的限制值
if (tag.AlarmPropertys.LLAlarmEnable && tag.Value.ToDecimal() < tag.AlarmPropertys.LLAlarmCode)
{
limit = tag.AlarmPropertys.LLAlarmCode.ToString()!; // 将报警限制值设置为低低报警的限制值
expressions = tag.AlarmPropertys.LLRestrainExpressions!; // 获取低低报警的约束表达式
text = tag.AlarmPropertys.LLAlarmText!; // 获取低低报警时的报警文本
return AlarmTypeEnum.LL; // 返回低低报警类型枚举
}
// 检查是否启用了低报警功能,并且变量的值小于低报警的限制值
if (tag.AlarmPropertys.LAlarmEnable && tag.Value.ToDecimal() < tag.AlarmPropertys.LAlarmCode)
{
limit = tag.AlarmPropertys.LAlarmCode.ToString()!; // 将报警限制值设置为低报警的限制值
expressions = tag.AlarmPropertys.LRestrainExpressions!; // 获取低报警的约束表达式
text = tag.AlarmPropertys.LAlarmText!; // 获取低报警时的报警文本
return AlarmTypeEnum.L; // 返回低报警类型枚举
}
return null; // 如果不符合任何报警条件则返回null
}
/// <summary>
/// 对变量进行报警分析,并根据需要触发相应的报警事件或恢复事件。
/// </summary>
/// <param name="item">要进行报警分析的变量</param>
private static void AlarmAnalysis(VariableRuntime item)
{
string limit; // 报警限制值
string ex; // 报警约束表达式
string text; // 报警文本
AlarmTypeEnum? alarmEnum; // 报警类型枚举
int delay = item.AlarmPropertys.AlarmDelay; // 获取报警延迟时间
// 检查变量的数据类型
if (item.Value?.GetType() == typeof(bool))
{
// 如果数据类型为布尔型则调用GetBoolAlarmCode方法获取布尔型报警类型及相关信息
alarmEnum = GetBoolAlarmCode(item, out limit, out ex, out text);
}
else
{
// 如果数据类型为非布尔型则调用GetDecimalAlarmDegree方法获取数值型报警类型及相关信息
alarmEnum = GetDecimalAlarmDegree(item, out limit, out ex, out text);
}
// 如果未获取到报警类型,则尝试获取自定义报警类型
if (alarmEnum == null)
{
alarmEnum = GetCustomAlarmDegree(item, out limit, out ex, out text);
}
if (alarmEnum == null)
{
// 如果仍未获取到报警类型,则触发需恢复报警事件(如果存在)
AlarmChange(item, null, text, true, alarmEnum, delay);
}
else
{
// 如果获取到了报警类型,则需触发报警事件或更新报警状态
if (!string.IsNullOrEmpty(ex))
{
// 如果存在报警约束表达式,则计算表达式结果,以确定是否触发报警事件
var data = ex.GetExpressionsResult(item.Value, item.DeviceRuntime?.Driver?.LogMessage);
if (data is bool result)
{
if (result)
{
// 如果表达式结果为true则触发报警事件
AlarmChange(item, limit, text, false, alarmEnum, delay);
}
else
{
AlarmChange(item, limit, text, true, alarmEnum, delay);
}
}
}
else
{
// 如果不存在报警约束表达式,则直接触发报警事件
AlarmChange(item, limit, text, false, alarmEnum, delay);
}
}
}
/// <summary>
/// 根据报警事件类型进行相应的处理操作,包括触发报警事件或更新报警状态。
/// </summary>
/// <param name="item">要处理的变量</param>
/// <param name="limit">报警限制值</param>
/// <param name="text">报警文本</param>
/// <param name="finish">是否恢复</param>
/// <param name="alarmEnum">报警类型枚举</param>
/// <param name="delay">报警延时</param>
private static void AlarmChange(VariableRuntime item, object limit, string text, bool finish, AlarmTypeEnum? alarmEnum, int delay)
{
lock (item.AlarmRuntimePropertys.AlarmLockObject)
{
bool changed = false;
if (finish)
{
// 如果是需恢复报警事件
// 如果实时报警列表中不存在该变量,则直接返回
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
{
return;
}
}
else
{
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Confirm)
item.AlarmRuntimePropertys.AlarmConfirm = false;
// 如果是触发报警事件
// 在实时报警列表中查找该变量
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable) && (variable.EventType == EventTypeEnum.Alarm || variable.EventType == EventTypeEnum.Confirm))
{
// 如果变量已经处于相同的报警类型,则直接返回
if (item.AlarmRuntimePropertys.AlarmType == alarmEnum)
return;
}
}
// 更新变量的报警信息和事件时间
if (!finish)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Alarm && item.AlarmRuntimePropertys.EventType != EventTypeEnum.PrepareAlarm)
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.PrepareAlarm;//准备报警
item.AlarmRuntimePropertys.PrepareAlarmEventTime = now;
}
else
{
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.PrepareAlarm)
{
if ((now - item.AlarmRuntimePropertys.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Alarm;
item.AlarmRuntimePropertys.AlarmTime = now;
item.AlarmRuntimePropertys.EventTime = now;
item.AlarmRuntimePropertys.AlarmType = alarmEnum;
item.AlarmRuntimePropertys.AlarmLimit = limit.ToString();
item.AlarmRuntimePropertys.AlarmCode = item.Value.ToString();
item.AlarmRuntimePropertys.RecoveryCode = string.Empty;
item.AlarmRuntimePropertys.AlarmText = text;
item.AlarmRuntimePropertys.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Alarm && item.AlarmRuntimePropertys.AlarmType != alarmEnum)
{
//报警类型改变,重新计时
if (item.AlarmRuntimePropertys.PrepareAlarmEventTime == null)
item.AlarmRuntimePropertys.PrepareAlarmEventTime = now;
if ((now - item.AlarmRuntimePropertys.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Alarm;
item.AlarmRuntimePropertys.AlarmTime = now;
item.AlarmRuntimePropertys.EventTime = now;
item.AlarmRuntimePropertys.AlarmType = alarmEnum;
item.AlarmRuntimePropertys.AlarmLimit = limit.ToString();
item.AlarmRuntimePropertys.AlarmCode = item.Value.ToString();
item.AlarmRuntimePropertys.RecoveryCode = string.Empty;
item.AlarmRuntimePropertys.AlarmText = text;
item.AlarmRuntimePropertys.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
return;
}
}
}
else
{
// 如果是触发报警事件
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Alarm;
item.AlarmRuntimePropertys.AlarmTime = now;
item.AlarmRuntimePropertys.EventTime = now;
item.AlarmRuntimePropertys.AlarmType = alarmEnum;
item.AlarmRuntimePropertys.AlarmLimit = limit.ToString();
item.AlarmRuntimePropertys.AlarmCode = item.Value.ToString();
item.AlarmRuntimePropertys.RecoveryCode = string.Empty;
item.AlarmRuntimePropertys.AlarmText = text;
item.AlarmRuntimePropertys.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish && item.AlarmRuntimePropertys.EventType != EventTypeEnum.PrepareFinish)
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.PrepareFinish;
item.AlarmRuntimePropertys.PrepareFinishEventTime = now;
}
else
{
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.PrepareFinish)
{
if ((now - item.AlarmRuntimePropertys.PrepareFinishEventTime!.Value).TotalMilliseconds > delay)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmRuntimePropertys.AlarmType = oldAlarm.AlarmType;
item.AlarmRuntimePropertys.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmRuntimePropertys.AlarmCode = oldAlarm.AlarmCode;
item.AlarmRuntimePropertys.RecoveryCode = item.Value.ToString();
item.AlarmRuntimePropertys.AlarmText = oldAlarm.AlarmText;
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish)
{
item.AlarmRuntimePropertys.FinishTime = now;
item.AlarmRuntimePropertys.EventTime = now;
}
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Finish;
item.AlarmRuntimePropertys.PrepareFinishEventTime = null;
changed = true;
}
}
}
else
{
return;
}
}
}
else
{
// 如果是需恢复报警事件
// 获取旧的报警信息
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish && item.AlarmRuntimePropertys.EventType != EventTypeEnum.PrepareFinish)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmRuntimePropertys.AlarmType = oldAlarm.AlarmType;
item.AlarmRuntimePropertys.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmRuntimePropertys.AlarmCode = oldAlarm.AlarmCode;
item.AlarmRuntimePropertys.RecoveryCode = item.Value.ToString();
item.AlarmRuntimePropertys.AlarmText = oldAlarm.AlarmText;
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish)
{
item.AlarmRuntimePropertys.FinishTime = now;
item.AlarmRuntimePropertys.EventTime = now;
}
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Finish;
item.AlarmRuntimePropertys.PrepareFinishEventTime = null;
changed = true;
}
}
}
}
// 触发报警变化事件
if (changed)
{
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Alarm)
{
// 如果是触发报警事件
//lock (GlobalData. RealAlarmVariables)
{
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
}
else if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Finish)
{
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
if (item.AlarmRuntimePropertys.AlarmConfirm)
{
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
item.AlarmRuntimePropertys.EventType = EventTypeEnum.ConfirmAndFinish;
}
else
{
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
}
}
public void ConfirmAlarm(long variableId)
{
// 如果是确认报警事件
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var item))
{
lock (item.AlarmRuntimePropertys.AlarmLockObject)
{
item.AlarmRuntimePropertys.AlarmConfirm = true;
item.AlarmRuntimePropertys.ConfirmTime = DateTime.Now;
item.AlarmRuntimePropertys.EventTime = item.AlarmRuntimePropertys.ConfirmTime;
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Finish)
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.ConfirmAndFinish;
GlobalData.RealAlarmIdVariables.TryRemove(variableId, out _);
}
else
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Confirm;
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
}
}
#endregion
/// <summary>
/// 执行工作任务,对设备变量进行报警分析。
/// </summary>
/// <param name="state"></param>
/// <param name="cancellation">取消任务的 CancellationToken</param>
private void DoWork(object? state, CancellationToken cancellation)
{
try
{
if (!GlobalData.StartBusinessChannelEnable)
return;
//Stopwatch stopwatch = Stopwatch.StartNew();
// 遍历设备变量列表
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
{
var list = GlobalData.AlarmEnableIdVariables.Select(a => a.Value).ToArray();
list.ParallelForEach((item, state, index) =>
{
{
// 如果取消请求已经被触发,则结束任务
if (cancellation.IsCancellationRequested)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.AlarmEnable)
return;
// 如果该变量离线,则跳过该变量
if (!item.IsOnline)
return;
// 对该变量进行报警分析
AlarmAnalysis(item);
}
});
}
else
{
//if (scheduledTask.Period != 5000)
// scheduledTask.Change(0, 5000); // 如果没有启用报警的变量则设置下次执行时间为5秒后
scheduledTask.SetNext(5000); // 如果没有启用报警的变量则设置下次执行时间为5秒后
}
//stopwatch.Stop();
//_logger.LogInformation("报警分析耗时:" + stopwatch.ElapsedMilliseconds + "ms");
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Alarm analysis fail");
}
}
}