SOMS/src/YunDa.Server/YunDa.Server.ISMSTcp/Services/TelesignalisationHandle.cs
qsp89 326d50630a 邱蜀鹏合并程序:
1、每10分钟,主动判断一下遥信全体
2、程序启动时,提取遥测遥信全体命令,每个命令闹间隔1秒
3、提取遥测遥信全体命令完成后(10分钟超时),再执行巡检和定位发送提取遥测信息
4、每30秒请求一次虚点
5、其他一系列修改
2026-01-08 15:51:20 +08:00

903 lines
38 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 DynamicExpresso;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ToolLibrary;
using YunDa.Server.ISMSTcp.Domain;
using YunDa.Server.ISMSTcp.Interfaces;
using YunDa.Server.ISMSTcp.Models;
using YunDa.SOMS.DataTransferObject.ExternalEntities.BeijingYounuo;
using YunDa.SOMS.DataTransferObject.GeneralInformation.EquipmentLiveDataDto;
using YunDa.SOMS.DataTransferObject.GeneralInformation.ProtectionDeviceInfoDto;
using YunDa.SOMS.Entities.GeneralInformation;
using YunDa.SOMS.Redis.Repositories;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace YunDa.Server.ISMSTcp.Services
{
/// <summary>
/// 遥信数据处理服务
/// </summary>
public class TelesignalisationHandle
{
private readonly ILogger<TelesignalisationHandle> _logger;
private readonly IRedisRepository<TelesignalisationModel, string> _telesignalisationModelListRedis;
private readonly string _telesignalisationModelListRediskey = "telesignalisationModelList";
public string _telesignalisationInflectionInflectionZZChannelRediskey = "telesignalisationInflection_ZZ_Channel";
private readonly IWebSocketPushService _webSocketPushService;
private readonly IApiEndpoints _apiEndpoints;
// 🔧 新增保护装置通信信息Redis仓储用于检查设备检修状态
private readonly IRedisRepository<ProtectionDeviceCommInfoOutput, string> _protectionDeviceCommInfoRedis;
private const string PROTECTION_DEVICE_COMM_INFO_REDIS_KEY = "protectionDeviceCommInfo";
// 映射字典YX_ID -> haskey
private readonly ConcurrentDictionary<string, string> _yxIdToHashKeyMapping = new ConcurrentDictionary<string, string>();
// 🔧 新增:网线和光缆逻辑表达式处理相关字段
private readonly WebApiRequest _webApiRequest;
private readonly ConcurrentDictionary<string, List<NetworkCableDto>> _telesignalToNetworkCableMapping = new ConcurrentDictionary<string, List<NetworkCableDto>>();
private readonly ConcurrentDictionary<string, List<OpticalCableDto>> _telesignalToOpticalCableMapping = new ConcurrentDictionary<string, List<OpticalCableDto>>();
// 初始化状态
public volatile bool _isInitialized = false;
public volatile bool _isExpressionMappingInitialized = false;
private readonly object _initLock = new object();
private readonly object _expressionInitLock = new object();
//遥信数据缓冲队列
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(ZzDataCacheContainerDataType.eYX, 30); //只保留5分钟的数据
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logger">日志服务</param>
/// <param name="telesignalisationModelListRedis">遥信数据Redis仓储</param>
public TelesignalisationHandle(
IApiEndpoints apiEndpoints,
ILogger<TelesignalisationHandle> logger,
IWebSocketPushService webSocketPushService,
IRedisRepository<TelesignalisationModel, string> telesignalisationModelListRedis,
IRedisRepository<ProtectionDeviceCommInfoOutput, string> protectionDeviceCommInfoRedis,
WebApiRequest webApiRequest,
ThingService thingService)
{
_apiEndpoints = apiEndpoints ?? throw new ArgumentNullException(nameof(apiEndpoints));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_telesignalisationModelListRedis = telesignalisationModelListRedis ?? throw new ArgumentNullException(nameof(telesignalisationModelListRedis));
_webSocketPushService = webSocketPushService ?? throw new ArgumentNullException(nameof(webSocketPushService));
_protectionDeviceCommInfoRedis = protectionDeviceCommInfoRedis ?? throw new ArgumentNullException(nameof(protectionDeviceCommInfoRedis));
_webApiRequest = webApiRequest ?? throw new ArgumentNullException(nameof(webApiRequest));
ZzDataCache.SetDataCache(ZzDataCacheContainerDataType.eYX, _zzDataCacheContainer);
}
/// <summary>
/// 初始化遥信数据映射
/// </summary>
/// <param name="maxRetries">最大重试次数</param>
/// <param name="retryDelayMs">重试间隔(毫秒)</param>
/// <returns>初始化任务</returns>
public async Task<bool> InitAsync(int maxRetries = 3, int retryDelayMs = 2000)
{
if (_isInitialized)
{
_logger.LogInformation("遥信数据映射已经初始化完成");
return true;
}
lock (_initLock)
{
if (_isInitialized)
{
return true;
}
_logger.LogInformation("开始初始化遥信数据映射...");
}
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
_logger.LogInformation("第 {Attempt}/{MaxRetries} 次尝试查询遥信数据", attempt, maxRetries);
// 查询所有遥信数据,使用固定的数据源类别 "Zongzi" (0)
string redisKey = $"{_telesignalisationModelListRediskey}_Zongzi";
var telesignalisationModels = await _telesignalisationModelListRedis.HashSetGetAllAsync(redisKey);
if (telesignalisationModels == null || telesignalisationModels.Count == 0)
{
_logger.LogWarning("第 {Attempt} 次查询遥信数据为空Redis键: {RedisKey}", attempt, redisKey);
if (attempt < maxRetries)
{
_logger.LogInformation("等待 {DelayMs} 毫秒后重试...", retryDelayMs);
await Task.Delay(retryDelayMs);
continue;
}
else
{
_logger.LogError("所有重试都失败,无法获取遥信数据");
return false;
}
}
// 创建映射字典
int mappingCount = 0;
foreach (var model in telesignalisationModels)
{
if (!string.IsNullOrEmpty(model.ismsbaseYXId))
{
// 构造haskey格式"{dev_addr}_{dev_sector}_{dev_inf}_0"
// 这里使用模型中的地址信息
string haskey = $"{model.DeviceAddress}_0_{model.DispatcherAddress}_0";
if (_yxIdToHashKeyMapping.TryAdd(model.ismsbaseYXId, haskey))
{
mappingCount++;
//初始化
_zzDataCacheContainer.Write(model.ismsbaseYXId, model.ResultValue, model.ResultTime, model.Name, model.ResultValueStr, model.DispatcherAddress);
}
}
else
{
_logger.LogDebug("遥信模型 {ModelName} 的 ismsbaseYXId 为空,跳过", model.Name);
}
}
lock (_initLock)
{
_isInitialized = true;
}
_logger.LogInformation("遥信数据映射初始化成功!总计 {TotalModels} 个模型,创建 {MappingCount} 个映射",
telesignalisationModels.Count, mappingCount);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "第 {Attempt} 次初始化遥信数据映射时发生错误", attempt);
if (attempt < maxRetries)
{
_logger.LogInformation("等待 {DelayMs} 毫秒后重试...", retryDelayMs);
await Task.Delay(retryDelayMs);
}
else
{
_logger.LogError("所有重试都失败,初始化遥信数据映射失败");
return false;
}
}
}
return false;
}
/// <summary>
/// 初始化网线和光缆逻辑表达式映射
/// </summary>
/// <param name="maxRetries">最大重试次数</param>
/// <param name="retryDelayMs">重试间隔(毫秒)</param>
/// <returns>初始化任务</returns>
public async Task<bool> InitExpressionMappingAsync(int maxRetries = 3, int retryDelayMs = 2000)
{
if (_isExpressionMappingInitialized)
{
_logger.LogInformation("网线和光缆逻辑表达式映射已经初始化完成");
return true;
}
lock (_expressionInitLock)
{
if (_isExpressionMappingInitialized)
{
return true;
}
_logger.LogInformation("开始初始化网线和光缆逻辑表达式映射...");
}
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
_logger.LogInformation("第 {Attempt}/{MaxRetries} 次尝试获取网线和光缆配置数据", attempt, maxRetries);
// 1. 获取网线配置数据
var networkCables = await _webApiRequest.GetNetworkCableListAsync();
var opticalCables = await _webApiRequest.GetOpticalCableListAsync();
if ((networkCables == null || networkCables.Count == 0) &&
(opticalCables == null || opticalCables.Count == 0))
{
_logger.LogWarning("第 {Attempt} 次查询网线和光缆配置数据为空", attempt);
if (attempt < maxRetries)
{
_logger.LogInformation("等待 {DelayMs} 毫秒后重试...", retryDelayMs);
await Task.Delay(retryDelayMs);
continue;
}
else
{
_logger.LogError("所有重试都失败,无法获取网线和光缆配置数据");
return false;
}
}
// 2. 处理网线配置,创建遥信地址到网线配置的映射
int networkCableMappingCount = 0;
if (networkCables != null)
{
foreach (var cable in networkCables.Where(c => c.IsActive && !string.IsNullOrWhiteSpace(c.LogicalExpression)))
{
var addresses = ExtractTelesignalAddresses(cable.LogicalExpression);
foreach (var address in addresses)
{
_telesignalToNetworkCableMapping.AddOrUpdate(address,
new List<NetworkCableDto> { cable },
(key, existingList) =>
{
existingList.Add(cable);
return existingList;
});
networkCableMappingCount++;
}
}
}
// 3. 处理光缆配置,创建遥信地址到光缆配置的映射
int opticalCableMappingCount = 0;
if (opticalCables != null)
{
foreach (var cable in opticalCables.Where(c => c.IsActive && !string.IsNullOrWhiteSpace(c.LogicalExpression)))
{
var addresses = ExtractTelesignalAddresses(cable.LogicalExpression);
foreach (var address in addresses)
{
_telesignalToOpticalCableMapping.AddOrUpdate(address,
new List<OpticalCableDto> { cable },
(key, existingList) =>
{
existingList.Add(cable);
return existingList;
});
opticalCableMappingCount++;
}
}
}
lock (_expressionInitLock)
{
_isExpressionMappingInitialized = true;
}
_logger.LogInformation("网线和光缆逻辑表达式映射初始化成功!网线配置: {NetworkCableCount} 个,光缆配置: {OpticalCableCount} 个,网线映射: {NetworkCableMappingCount} 个,光缆映射: {OpticalCableMappingCount} 个",
networkCables?.Count ?? 0, opticalCables?.Count ?? 0, networkCableMappingCount, opticalCableMappingCount);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "第 {Attempt} 次初始化网线和光缆逻辑表达式映射时发生错误", attempt);
if (attempt < maxRetries)
{
_logger.LogInformation("等待 {DelayMs} 毫秒后重试...", retryDelayMs);
await Task.Delay(retryDelayMs);
}
else
{
_logger.LogError("所有重试都失败,初始化网线和光缆逻辑表达式映射失败");
return false;
}
}
}
return false;
}
/// <summary>
/// 处理遥信数据
/// </summary>
/// <param name="yxData">遥信数据JSON令牌</param>
/// <param name="skipForwarding">是否跳过转发逻辑仅更新Redis</param>
/// <returns>处理任务</returns>
public async Task<TelesignalisationModel?> ProcessYXDataAsync(JToken yxData, bool skipForwarding = false)
{
try
{
if (yxData == null)
{
_logger.LogWarning("遥信数据为空,跳过处理");
return null;
}
if (!_isInitialized)
{
_logger.LogWarning("遥信数据映射尚未初始化,跳过处理");
return null;
}
return await ProcessSingleYXDataAsync(yxData, skipForwarding);
//_logger.LogInformation("遥信数据处理完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "处理遥信数据时发生错误: {YXData}", yxData?.ToString());
throw;
}
}
/// <summary>
/// 处理单个遥信数据项
/// </summary>
/// <param name="yxItem">单个遥信数据项</param>
/// <param name="skipForwarding">是否跳过转发逻辑仅更新Redis</param>
/// <returns>处理任务</returns>
private async Task<TelesignalisationModel?> ProcessSingleYXDataAsync(JToken yxItem, bool skipForwarding = false)
{
try
{
// 解析YXData结构
var yxDataModel = yxItem.ToObject<YXData>();
if (yxDataModel == null)
{
_logger.LogWarning("无法解析遥信数据项: {Item}", yxItem?.ToString());
return null;
}
//开关状态(合、分、不定),与具体的规约(101、104)无关 TSwitchstate = (swon = 0 swOff = 1,swUncertainty = 2);
switch (yxDataModel.V)
{
case 0:
yxDataModel.V = 2;
break;
case 2:
yxDataModel.V = 0;
break;
}
//if (yxDataModel.YX_ID == "YXB001121065")
//{
// _logger.LogWarning($"YXB001121065: {yxDataModel.V}, {yxDataModel.T}");
// Debug.WriteLine($"YXB001121065: {yxDataModel.V}, {yxDataModel.T}");
//}
// 使用映射字典查找对应的haskey
if (!_yxIdToHashKeyMapping.TryGetValue(yxDataModel.YX_ID, out string haskey))
{
// _logger.LogWarning("未找到YX_ID {YxId} 对应的映射", yxDataModel.YX_ID);
return null;
}
// 构建Redis键
string redisKey = $"{_telesignalisationModelListRediskey}_Zongzi";
// 从Redis获取现有数据
var telesignalisationModel = await _telesignalisationModelListRedis.HashSetGetOneAsync(redisKey, haskey);
if (telesignalisationModel == null)
{
_logger.LogWarning("Redis中未找到遥信数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
return null;
}
// 解析时间
DateTime resultTime = DateTime.Now;
if (!string.IsNullOrEmpty(yxDataModel.T))
{
if (DateTime.TryParse(yxDataModel.T, out DateTime parsedTime))
{
if (parsedTime.Year>2000)
{
resultTime = parsedTime;
}
}
else
{
_logger.LogWarning("无法解析时间格式: {TimeStr},使用当前时间", yxDataModel.T);
}
}
// 更新数据
telesignalisationModel.ResultTime = resultTime;
telesignalisationModel.ResultValue = yxDataModel.V;
// 保存到Redis
await _telesignalisationModelListRedis.HashSetUpdateOneAsync(redisKey, haskey, telesignalisationModel);
string channel = $"{_telesignalisationInflectionInflectionZZChannelRediskey}_Zongzi";
_telesignalisationModelListRedis.PublishAsync(channel, telesignalisationModel);
//缓存遥信数据到内存
_zzDataCacheContainer.Write(yxDataModel.YX_ID, yxDataModel.V, resultTime, telesignalisationModel.Name, telesignalisationModel.ResultValueStr, telesignalisationModel.DispatcherAddress);
// 🔧 优化如果跳过转发仅更新Redis后直接返回
if (skipForwarding)
{
_logger.LogDebug("跳过转发逻辑 - YX_ID: {YxId}", yxDataModel.YX_ID);
return telesignalisationModel;
}
// 提取设备ID并调用数据变位信号API
var equipmentId = telesignalisationModel.EquipmentInfoId.Value;
await CallDataChangedAsync(equipmentId);
// 🔧 新增:检查设备是否处于检修状态,如果是则跳过数据推送
var isInMaintenance = await IsEquipmentInMaintenanceAsync(equipmentId);
if (isInMaintenance)
{
_logger.LogDebug("设备处于检修状态,跳过遥信数据推送 - EquipmentInfoId: {EquipmentId}", equipmentId);
// 设备处于检修状态,跳过数据推送
return telesignalisationModel;
}
// WebSocket实时推送
try
{
JToken pointToken = JToken.FromObject(telesignalisationModel);
// 推送到YX分组
await _webSocketPushService.PushYXDataAsync(pointToken, telesignalisationModel.EquipmentInfoId.ToString());
}
catch (Exception pushEx)
{
_logger.LogWarning(pushEx, "YX数据WebSocket推送失败但不影响主要处理流程");
}
// 🔧 新增:处理网线和光缆逻辑表达式
if (_isExpressionMappingInitialized)
{
await ProcessLogicExpressionsAsync(yxDataModel.YX_ID, yxDataModel.V);
}
return telesignalisationModel;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理单个遥信数据项时发生错误: {Item}", yxItem?.ToString());
return null;
}
}
public async Task<List<ZzDataResultModel>> GetYXDataByDataIds(List<string> ids, int times, int timeWindowType, CancellationToken cancellationToken)
{
//检查数据是否为空如果为空从Redis里取最新值
if(!_isInitialized)
{
await InitAsync();
}
var finalResult = await _zzDataCacheContainer.Read(ids, times, timeWindowType, cancellationToken);
string redisKey = $"{_telesignalisationModelListRediskey}_Zongzi";
foreach (var item in finalResult)
{
if(item.TimeStamp == DateTime.MinValue)
{
if (!_yxIdToHashKeyMapping.TryGetValue(item.Id, out string haskey))
{
continue;
}
var telesignalisationModel = await _telesignalisationModelListRedis.HashSetGetOneAsync(redisKey, haskey);
if (telesignalisationModel == null)
{
_logger.LogWarning("Redis中未找到遥信数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
continue; // 继续处理下一个haskey
}
item.Value = telesignalisationModel.ResultValue;
item.ValueStr = telesignalisationModel.ResultValueStr;
item.TimeStamp = telesignalisationModel.ResultTime;
}
}
return finalResult;
}
private readonly ConcurrentQueue<DateTime> _callTimestamps = new ConcurrentQueue<DateTime>();
private readonly SemaphoreSlim _rateLimitLock = new SemaphoreSlim(1, 1);
private const int MaxCallsPerWindow = 300;
private const int TimeWindowSeconds = 3;
private async Task CallDataChangedAsync(Guid equipmentId)
{
try
{
// 检查调用频率
if (!await ShouldAllowCallAsync())
{
_logger.LogWarning("数据变位信号调用频率过高放弃本次调用设备ID: {EquipmentId}", equipmentId);
return;
}
var url = $"{_apiEndpoints.DataChangedUri}?equipmentId={equipmentId}";
_ = Task.Run(async () =>
{
try
{
var response = await Task.Run(() => HttpHelper.HttpGetRequest<object>(url));
}
catch (Exception ex)
{
_logger.LogWarning(ex, "数据变位信号API调用失败设备ID: {EquipmentId}", equipmentId);
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "调用数据变位信号API异常设备ID: {EquipmentId}", equipmentId);
}
}
private async Task<bool> ShouldAllowCallAsync()
{
await _rateLimitLock.WaitAsync();
try
{
var now = DateTime.UtcNow;
var windowStart = now.AddSeconds(-TimeWindowSeconds);
// 移除时间窗口外的旧记录
while (_callTimestamps.TryPeek(out var oldestTime) && oldestTime < windowStart)
{
_callTimestamps.TryDequeue(out _);
}
// 检查当前窗口内的调用次数
if (_callTimestamps.Count >= MaxCallsPerWindow)
{
return false; // 超过限制,拒绝调用
}
// 记录本次调用时间
_callTimestamps.Enqueue(now);
return true;
}
finally
{
_rateLimitLock.Release();
}
}
/// <summary>
/// 🔧 新增:检查设备是否处于检修状态
/// </summary>
/// <param name="equipmentInfoId">设备信息ID</param>
/// <returns>是否处于检修状态</returns>
private async Task<bool> IsEquipmentInMaintenanceAsync(Guid equipmentInfoId)
{
try
{
// 从Redis获取所有保护装置通信信息
var allDevices = await _protectionDeviceCommInfoRedis.HashSetGetAllAsync(PROTECTION_DEVICE_COMM_INFO_REDIS_KEY);
// 查找匹配的设备
var targetDevice = allDevices.FirstOrDefault(d => d.EquipmentInfoId == equipmentInfoId);
if (targetDevice != null)
{
return targetDevice.IsMaintenance;
}
// 如果未找到设备信息,默认不处于检修状态
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "检查设备检修状态时发生错误 - EquipmentInfoId: {EquipmentInfoId}", equipmentInfoId);
// 异常情况下默认不处于检修状态,避免影响正常数据传输
return false;
}
}
/// <summary>
/// 从逻辑表达式中提取遥信地址
/// </summary>
/// <param name="expression">逻辑表达式,如 "{1793}==1" 或 "{1800}==1&&{1900}==1"</param>
/// <returns>遥信地址列表</returns>
private List<string> ExtractTelesignalAddresses(string expression)
{
var addresses = new List<string>();
if (string.IsNullOrWhiteSpace(expression))
{
return addresses;
}
try
{
// 使用正则表达式提取花括号中的数字
var matches = Regex.Matches(expression, @"\{(\d+)\}");
foreach (Match match in matches)
{
if (match.Groups.Count > 1)
{
var address = match.Groups[1].Value;
if (!addresses.Contains(address))
{
addresses.Add(address);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "提取遥信地址时发生错误,表达式: {Expression}", expression);
}
return addresses;
}
/// <summary>
/// 处理网线和光缆逻辑表达式
/// </summary>
/// <param name="yxId">遥信ID</param>
/// <param name="value">遥信值</param>
/// <returns></returns>
private async Task ProcessLogicExpressionsAsync(string yxId, int value)
{
try
{
// 处理网线逻辑表达式
if (_telesignalToNetworkCableMapping.TryGetValue(yxId, out var networkCables))
{
foreach (var cable in networkCables)
{
await EvaluateAndProcessExpressionAsync(cable.LogicalExpression, cable.TwinId, "网线", cable.CiCode);
}
}
// 处理光缆逻辑表达式
if (_telesignalToOpticalCableMapping.TryGetValue(yxId, out var opticalCables))
{
foreach (var cable in opticalCables)
{
await EvaluateAndProcessExpressionAsync(cable.LogicalExpression, cable.TwinId, "光缆", cable.CiCode);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理网线和光缆逻辑表达式时发生错误遥信ID: {YxId}", yxId);
}
}
/// <summary>
/// 评估逻辑表达式并处理结果
/// </summary>
/// <param name="expression">逻辑表达式</param>
/// <param name="twinId">孪生体ID</param>
/// <param name="cableType">线缆类型</param>
/// <param name="cableName">线缆名称</param>
/// <returns></returns>
private async Task EvaluateAndProcessExpressionAsync(string expression, string twinId, string cableType, string cableName)
{
try
{
if (string.IsNullOrWhiteSpace(expression) || string.IsNullOrWhiteSpace(twinId))
{
return;
}
// 1. 获取表达式中的所有遥信地址
var addresses = ExtractTelesignalAddresses(expression);
if (addresses.Count == 0)
{
_logger.LogWarning("表达式中未找到有效的遥信地址: {Expression}", expression);
return;
}
// 2. 获取当前遥信值并替换表达式中的地址
var evaluableExpression = await ReplaceAddressesWithCurrentValuesAsync(expression, addresses);
if (string.IsNullOrWhiteSpace(evaluableExpression))
{
_logger.LogWarning("无法构建可评估的表达式: {Expression}", expression);
return;
}
// 3. 使用NCalc评估表达式
var result = EvaluateExpressionWithNCalc(evaluableExpression);
if (!result.HasValue)
{
_logger.LogWarning("表达式评估失败: {Expression} -> {EvaluableExpression}", expression, evaluableExpression);
return;
}
_logger.LogDebug("{CableType} {TwinId} 表达式评估结果: {Result}, 原表达式: {Expression}, 评估表达式: {EvaluableExpression}",
cableType, twinId, result.Value, expression, evaluableExpression);
// 4. 根据结果调用相应的API
if (result.Value) // True表示异常需要推送报警
{
await PushCableAlarmAsync(twinId, cableType, cableName, expression);
}
else // False表示正常需要删除报警
{
await DeleteCableAlarmAsync(twinId, cableType);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "评估和处理{CableType}逻辑表达式时发生错误: {Expression}, TwinId: {TwinId}", cableType, expression, twinId);
}
}
/// <summary>
/// 将表达式中的地址替换为当前遥信值
/// </summary>
/// <param name="expression">原始表达式</param>
/// <param name="addresses">地址列表</param>
/// <returns>可评估的表达式</returns>
private async Task<string> ReplaceAddressesWithCurrentValuesAsync(string expression, List<string> addresses)
{
try
{
var result = expression;
string redisKey = $"{_telesignalisationModelListRediskey}_Zongzi";
foreach (var address in addresses)
{
// 构造haskey格式来查找对应的遥信数据
// 这里需要根据实际的haskey格式来构造可能需要调整
var possibleHashKeys = new[]
{
$"{address}_0_{address}_0", // 尝试不同的haskey格式
$"0_0_{address}_0",
address
};
int? telesignalValue = null;
foreach (var hashKey in possibleHashKeys)
{
try
{
var telesignalModel = await _telesignalisationModelListRedis.HashSetGetOneAsync(redisKey, hashKey);
if (telesignalModel != null)
{
telesignalValue = telesignalModel.ResultValue;
break;
}
}
catch
{
// 继续尝试下一个hashKey格式
}
}
// 如果找不到对应的遥信值使用默认值0
var valueToUse = telesignalValue ?? 0;
result = result.Replace($"{{{address}}}", valueToUse.ToString());
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "替换表达式地址时发生错误: {Expression}", expression);
return null;
}
}
/// <summary>
/// 使用NCalc评估表达式
/// </summary>
/// <param name="expression">表达式</param>
/// <returns>评估结果</returns>
private bool? EvaluateExpressionWithNCalc(string expression)
{
try
{
if (string.IsNullOrWhiteSpace(expression))
{
return null;
}
// ===== DynamicExpresso - 数学/逻辑表达式 =====
var mathInterpreter = new Interpreter();
// 简单计算
var result = mathInterpreter.Eval(expression); // 17
if (result is bool boolResult)
{
return boolResult;
}
else if (result is double doubleResult)
{
// 非零为true零为false
return Math.Abs(doubleResult) > 0.0001;
}
else if (result is int intResult)
{
return intResult != 0;
}
else
{
// 尝试转换为布尔值
if (bool.TryParse(result?.ToString(), out bool parsedBool))
{
return parsedBool;
}
}
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "使用NCalc评估表达式时发生错误: {Expression}", expression);
return null;
}
}
/// <summary>
/// 推送线缆报警到3D系统
/// </summary>
/// <param name="twinId">孪生体ID</param>
/// <param name="cableType">线缆类型</param>
/// <param name="cableName">线缆名称</param>
/// <param name="expression">触发的表达式</param>
/// <returns></returns>
private async Task PushCableAlarmAsync(string twinId, string cableType, string cableName, string expression)
{
try
{
// 这里需要调用BeijingYounuoApiAppService的PushEquipmentAlarmsAsync方法
// 由于在ISMSTcp项目中可能需要通过HTTP API调用
var alarmData = new YounuoAlert
{
TwinID = twinId,
Severity = 1, // 默认严重级别
Status = 1, // 活跃报警
SourceAlertKey = $"{cableType}异常",
Summary = $"{cableType} {cableName} 异常: {expression}",
SourceIdentifier = $"{cableType}_{twinId}",
LastOccurrence = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
};
// 这里应该调用BeijingYounuoApiAppService但由于架构限制
// 可能需要通过HTTP API或消息队列来实现
_logger.LogInformation("推送{CableType}报警: TwinId={TwinId}, 表达式={Expression}", cableType, twinId, expression);
// TODO: 实现实际的API调用
await _webApiRequest.CallPushAlarmsApiAsync(alarmData);
}
catch (Exception ex)
{
_logger.LogError(ex, "推送{CableType}报警时发生错误: TwinId={TwinId}", cableType, twinId);
}
}
/// <summary>
/// 删除线缆报警
/// </summary>
/// <param name="twinId">孪生体ID</param>
/// <param name="cableType">线缆类型</param>
/// <returns></returns>
private async Task DeleteCableAlarmAsync(string twinId, string cableType)
{
try
{
_logger.LogInformation("删除{CableType}报警: TwinId={TwinId}", cableType, twinId);
await _webApiRequest.CallDeleteAlarmsByTwinIdApiAsync(twinId);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除{CableType}报警时发生错误: TwinId={TwinId}", cableType, twinId);
}
}
}
}