Compare commits

...

5 Commits

Author SHA1 Message Date
qsp89
8a4fceebc0 Merge branch 'master' of http://175.154.160.23:3237/guorui/SOMS 2025-11-20 19:06:15 +08:00
qsp89
0adda965c2 二次回路巡检bug修复与功能增加 2025-11-20 19:06:02 +08:00
qsp89
c30d957a0e Merge branch 'master' of http://175.154.160.23:3237/guorui/SOMS 2025-11-18 16:30:51 +08:00
qsp89
1fad43581a 合并 2025-11-13 10:59:01 +08:00
8864302771 事件巡检类型修改 2025-11-13 10:43:36 +08:00
18 changed files with 1774 additions and 743 deletions

View File

@ -95,9 +95,9 @@ namespace YunDa.Server.ISMSTcp.Configuration
public string SecondaryCircuitInspectionPlanUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionPlan/GetList";
/// <summary>
/// 二次回路获取事件的列表
/// 二次回路获取事件巡检的配置数据
/// </summary>
public string GetCircuitEventDrivenConfigUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitEventDrivenConfig/FindDatasNoPageList";
public string GetCircuitEventDrivenConfigUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitEventDrivenConfig/GetList";
/// <summary>
/// 获取孪生体与遥测数据绑定关系 URI
@ -129,6 +129,12 @@ namespace YunDa.Server.ISMSTcp.Configuration
/// </summary>
public string GetInspectionResultDataUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/OriginalInspectionStoreResult/GetInspectionResultData";
/// 获取网关数据URI
public string GetGatewayInfoDataUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetGatewayInfoData";
/// 获取虚点信息URI
public string GetVirtualPointInfoDataUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetVirtualPointInfoData";
/// <summary>
/// 查询网线配置
/// </summary>
@ -147,7 +153,19 @@ namespace YunDa.Server.ISMSTcp.Configuration
/// <summary>
/// 保存巡检计划执行结果
/// </summary>
public string SaveSecondaryCircuitInspectionPlanResultUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/OpticalCable/GetList";
public string SaveSecondaryCircuitInspectionPlanResultUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionResult/Create";
//调用Ai
public string GetAIAnalysisUri => "http://192.168.81.25:8002/chat/ai";
//巡检计划执行结果AI诊断更新
public string UpdateReportWithAIAnalysisUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionResult/UpdateReportWithAIAnalysis";
//获取虚点信息
public string GetVariantBaseinfoUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetVariantBaseinfo";
//获取网关信息
public string GetGateWayBaseInfoUri => $"{BaseUrl.TrimEnd('/')}/api/services/SOMS/SecondaryCircuitInspectionItem/GetGateWayBaseInfo";
}

View File

@ -35,6 +35,8 @@ namespace YunDa.Server.ISMSTcp.Controllers
private readonly ITcpClient _tcpClient;
private readonly TelemeteringHandle _telemeteringHandle;
private readonly TelesignalisationHandle _telesignalisationHandle;
private readonly GwErrorRatioService _gwErrorRatioService;
private readonly VirtualTerminalHandler _virtualTerminalHandler;
/// <summary>
/// 构造函数
@ -54,7 +56,9 @@ namespace YunDa.Server.ISMSTcp.Controllers
WebApiRequest webApiRequest,
ITcpClient tcpClient,
TelemeteringHandle telemeteringHandle,
TelesignalisationHandle telesignalisationHandle)
TelesignalisationHandle telesignalisationHandle,
GwErrorRatioService gwErrorRatioService,
VirtualTerminalHandler virtualTerminalHandler)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_queryService = queryService ?? throw new ArgumentNullException(nameof(queryService));
@ -64,6 +68,8 @@ namespace YunDa.Server.ISMSTcp.Controllers
_tcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient));
_telemeteringHandle = telemeteringHandle ?? throw new ArgumentNullException(nameof(telemeteringHandle));
_telesignalisationHandle = telesignalisationHandle ?? throw new ArgumentNullException(nameof(telesignalisationHandle));
_gwErrorRatioService = gwErrorRatioService ?? throw new ArgumentNullException(nameof(gwErrorRatioService));
_virtualTerminalHandler = virtualTerminalHandler ?? throw new ArgumentNullException(nameof(virtualTerminalHandler));
}
/// <summary>
@ -268,7 +274,7 @@ namespace YunDa.Server.ISMSTcp.Controllers
/// </remarks>
[HttpPost("CallYCByDataId")]
public async Task<IActionResult> CallYCByDataId(
[FromBody] CallYCByDataIdRequest request,
[FromBody] ZzDataRequestModel request,
CancellationToken cancellationToken = default)
{
var id = request.Id;
@ -278,6 +284,8 @@ namespace YunDa.Server.ISMSTcp.Controllers
string cmd = string.Format("CallYCByDataID|{0}", string.Join("#", id));
System.Console.WriteLine($"发送遥测命令:{cmd}");
try
{
// 参数验证
@ -297,26 +305,35 @@ namespace YunDa.Server.ISMSTcp.Controllers
DateTime cmdTime = DateTime.Now;
int sucessCount = 0;
for (int i = 0; i < times; i++)
if(request.TimeWindowType == 1 || request.TimeWindowType == 2)
{
var sendResult = await SendTcpMessageAsync(cmd, cancellationToken);
if (sendResult.Success)
for (int i = 0; i < times; i++)
{
sucessCount++;
var sendResult = await SendTcpMessageAsync(cmd, cancellationToken);
if (sendResult.Success)
{
sucessCount++;
}
await Task.Delay(1000);
}
await Task.Delay(1000);
}
else
{
sucessCount = 10;
}
if (times-sucessCount<3)
if (times - sucessCount < 3)
{
List<YCResultData> ycDataList = null;
List<ZzDataResultModel>? ycDataList = null;
var sw = Stopwatch.StartNew();
while(sw.ElapsedMilliseconds < 10*1000)
while (sw.ElapsedMilliseconds < 10 * 1000)
{
ycDataList = await _telemeteringHandle.GetYCDataByDataIds(id, cmdTime, sucessCount);
ycDataList = await _telemeteringHandle.GetYCDataByDataIds(id, times, request.TimeWindowType, cancellationToken, cmdTime);
if (ycDataList.Count == id.Count)
break;
@ -398,7 +415,7 @@ namespace YunDa.Server.ISMSTcp.Controllers
/// </remarks>
[HttpPost("CallYXByDataId")]
public async Task<IActionResult> CallYXByDataId(
[FromBody] CallYCByDataIdRequest request,
[FromBody] ZzDataRequestModel request,
CancellationToken cancellationToken = default)
{
var ids = request.Id;
@ -419,7 +436,7 @@ namespace YunDa.Server.ISMSTcp.Controllers
});
}
var dataList = await _telesignalisationHandle.GetYXDataByDataIds(ids, times, cancellationToken);
var dataList = await _telesignalisationHandle.GetYXDataByDataIds(ids, times, request.TimeWindowType, cancellationToken);
return Ok(new
{
@ -439,10 +456,98 @@ namespace YunDa.Server.ISMSTcp.Controllers
timestamp = DateTime.Now
});
}
}
/// 根据网关Id获取网关通信状态
[HttpPost("CallGatewayByDataId")]
public async Task<IActionResult> CallGatewayByDataId(
[FromBody] ZzDataRequestModel request,
CancellationToken cancellationToken = default)
{
var ids = request.Id;
var times = request.Times;
ids.RemoveAll(string.IsNullOrWhiteSpace);
try
{
// 参数验证
if (ids.Count == 0)
{
return BadRequest(new
{
success = false,
message = "未提供网关ID",
timestamp = DateTime.Now
});
}
var dataList = await _gwErrorRatioService.GetDataByIds(ids, times, cancellationToken);
return Ok(new
{
success = true,
message = "获取网关通信状态成功",
data = dataList,
count = dataList.Count,
timestamp = DateTime.Now
});
}
catch (Exception ex)
{
return StatusCode(500, new
{
success = false,
message = $"获取网关通信状态失败: {ex.Message}",
timestamp = DateTime.Now
});
}
}
/// 根据Id获取虚点数据
[HttpPost("CallVAByDataId")]
public async Task<IActionResult> CallVAByDataId(
[FromBody] ZzDataRequestModel request,
CancellationToken cancellationToken = default)
{
var ids = request.Id;
var times = request.Times;
ids.RemoveAll(string.IsNullOrWhiteSpace);
try
{
// 参数验证
if (ids.Count == 0)
{
return BadRequest(new
{
success = false,
message = "未提供ID",
timestamp = DateTime.Now
});
}
var dataList = await _virtualTerminalHandler.GetDataByIds(ids, times, request.TimeWindowType, cancellationToken);
return Ok(new
{
success = true,
message = "获取虚点数据成功",
data = dataList,
count = dataList.Count,
timestamp = DateTime.Now
});
}
catch (Exception ex)
{
return StatusCode(500, new
{
success = false,
message = $"获取虚点数据失败: {ex.Message}",
timestamp = DateTime.Now
});
}
}
/// <summary>
@ -917,87 +1022,87 @@ namespace YunDa.Server.ISMSTcp.Controllers
return WebSocketGroups.ALL;
}
/// <summary>
/// 获取召唤的遥测数据
/// </summary>
/// <param name="dataIds">数据ID字符串格式DataID1#DataID2#DataID3#......</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>遥测数据列表</returns>
/// <remarks>
/// 示例请求GET /api/GetCollectedYCData?dataIds=001#002#003
/// </remarks>
[HttpGet("GetCollectedYCData")]
public async Task<IActionResult> GetCollectedYCData(
[FromQuery] string dataIds,
CancellationToken cancellationToken = default)
{
try
{
// 参数验证
if (string.IsNullOrWhiteSpace(dataIds))
{
return BadRequest(new
{
success = false,
message = "参数 dataIds 不能为空",
timestamp = DateTime.Now
});
}
///// <summary>
///// 获取召唤的遥测数据
///// </summary>
///// <param name="dataIds">数据ID字符串格式DataID1#DataID2#DataID3#......</param>
///// <param name="cancellationToken">取消令牌</param>
///// <returns>遥测数据列表</returns>
///// <remarks>
///// 示例请求GET /api/GetCollectedYCData?dataIds=001#002#003
///// </remarks>
//[HttpGet("GetCollectedYCData")]
//public async Task<IActionResult> GetCollectedYCData(
// [FromQuery] string dataIds,
// CancellationToken cancellationToken = default)
//{
// try
// {
// // 参数验证
// if (string.IsNullOrWhiteSpace(dataIds))
// {
// return BadRequest(new
// {
// success = false,
// message = "参数 dataIds 不能为空",
// timestamp = DateTime.Now
// });
// }
_logger.LogInformation("收到获取遥测数据请求 - dataIds: {DataIds}", dataIds);
// _logger.LogInformation("收到获取遥测数据请求 - dataIds: {DataIds}", dataIds);
// 解析DataId列表
var dataIdList = dataIds.Split('#', StringSplitOptions.RemoveEmptyEntries)
.Select(id => id.Trim())
.Where(id => !string.IsNullOrWhiteSpace(id))
.ToList();
// // 解析DataId列表
// var dataIdList = dataIds.Split('#', StringSplitOptions.RemoveEmptyEntries)
// .Select(id => id.Trim())
// .Where(id => !string.IsNullOrWhiteSpace(id))
// .ToList();
if (dataIdList.Count == 0)
{
return BadRequest(new
{
success = false,
message = "未提供有效的数据ID",
timestamp = DateTime.Now
});
}
// if (dataIdList.Count == 0)
// {
// return BadRequest(new
// {
// success = false,
// message = "未提供有效的数据ID",
// timestamp = DateTime.Now
// });
// }
// 从TelemeteringHandle获取数据
var ycDataList = _telemeteringHandle.GetYCDataByDataIds(dataIdList);
// // 从TelemeteringHandle获取数据
// var ycDataList = _telemeteringHandle.GetYCDataByDataIds(dataIdList);
_logger.LogInformation("获取遥测数据成功 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
dataIdList.Count, ycDataList.Count);
// _logger.LogInformation("获取遥测数据成功 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
// dataIdList.Count, ycDataList.Count);
return Ok(new
{
success = true,
message = "获取遥测数据成功",
data = ycDataList,
count = ycDataList.Count,
timestamp = DateTime.Now
});
}
catch (OperationCanceledException)
{
_logger.LogWarning("获取遥测数据请求被取消 - dataIds: {DataIds}", dataIds);
return StatusCode(504, new
{
success = false,
message = "请求超时",
timestamp = DateTime.Now
});
}
catch (Exception ex)
{
_logger.LogError(ex, "获取遥测数据时发生异常 - dataIds: {DataIds}", dataIds);
return StatusCode(500, new
{
success = false,
message = $"服务器内部错误: {ex.Message}",
timestamp = DateTime.Now
});
}
}
// return Ok(new
// {
// success = true,
// message = "获取遥测数据成功",
// data = ycDataList,
// count = ycDataList.Count,
// timestamp = DateTime.Now
// });
// }
// catch (OperationCanceledException)
// {
// _logger.LogWarning("获取遥测数据请求被取消 - dataIds: {DataIds}", dataIds);
// return StatusCode(504, new
// {
// success = false,
// message = "请求超时",
// timestamp = DateTime.Now
// });
// }
// catch (Exception ex)
// {
// _logger.LogError(ex, "获取遥测数据时发生异常 - dataIds: {DataIds}", dataIds);
// return StatusCode(500, new
// {
// success = false,
// message = $"服务器内部错误: {ex.Message}",
// timestamp = DateTime.Now
// });
// }
//}
}

View File

@ -86,6 +86,12 @@ namespace YunDa.Server.ISMSTcp.Domain
/// </summary>
string GetInspectionResultDataUri { get; }
//查询网关数据
string GetGatewayInfoDataUri { get; }
//查询虚点信息
string GetVirtualPointInfoDataUri { get; }
/// <summary>
/// 查询网线配置
/// </summary>
@ -106,6 +112,19 @@ namespace YunDa.Server.ISMSTcp.Domain
/// </summary>
string SaveSecondaryCircuitInspectionPlanResultUri { get; }
//调用Ai
string GetAIAnalysisUri { get; }
//巡检计划执行结果AI诊断更新
string UpdateReportWithAIAnalysisUri { get; }
//获取虚点信息
string GetVariantBaseinfoUri { get; }
//获取网关信息
string GetGateWayBaseInfoUri { get; }
}
/// <summary>
@ -207,6 +226,13 @@ namespace YunDa.Server.ISMSTcp.Domain
public string GetInspectionResultDataUri => _config.GetInspectionResultDataUri;
//查询网关数据
public string GetGatewayInfoDataUri => _config.GetGatewayInfoDataUri;
//查询虚点信息
public string GetVirtualPointInfoDataUri => _config.GetVirtualPointInfoDataUri;
/// <summary>
/// 查询网线配置
/// </summary>
@ -227,5 +253,17 @@ namespace YunDa.Server.ISMSTcp.Domain
/// </summary>
public string SaveSecondaryCircuitInspectionPlanResultUri => _config.SaveSecondaryCircuitInspectionPlanResultUri;
//调用Ai
public string GetAIAnalysisUri => _config.GetAIAnalysisUri;
//巡检计划执行结果AI诊断更新
public string UpdateReportWithAIAnalysisUri => _config.UpdateReportWithAIAnalysisUri;
//获取虚点信息
public string GetVariantBaseinfoUri => _config.GetVariantBaseinfoUri;
//获取网关信息
public string GetGateWayBaseInfoUri => _config.GetGateWayBaseInfoUri;
}
}

View File

@ -648,12 +648,12 @@ namespace YunDa.Server.ISMSTcp.Domain
/// 获取二次回路巡检计划
/// </summary>
/// <returns>二次回路巡检计划列表</returns>
public async Task<List<SecondaryCircuitInspectionPlanOutput>> GetSecondaryCircuitInspectionPlanListAsync()
public async Task<List<SecondaryCircuitInspectionPlanOutput>> GetSecondaryCircuitInspectionPlanListAsync(int start)
{
try
{
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SecondaryCircuitInspectionPlanUri, new object());
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SecondaryCircuitInspectionPlanUri, new { maxResultCount = 10, skipCount = start, includeInspectionItems = true });
if (response != null)
{
@ -672,14 +672,14 @@ namespace YunDa.Server.ISMSTcp.Domain
}
/// <summary>
/// 获取事件的列表
/// 获取时间巡检的配置数据
/// </summary>
public async Task<List<SecondaryCircuitEventDrivenConfigOutput>> GetCircuitEventDrivenConfigAsync()
public async Task<List<SecondaryCircuitEventDrivenConfigOutput>> GetCircuitEventDrivenConfigAsync(int start)
{
try
{
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetCircuitEventDrivenConfigUri, new object());
var response = await HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetCircuitEventDrivenConfigUri, new { maxResultCount = 10, skipCount = start, includeInspectionItems = true });
if (response != null)
{
@ -751,7 +751,7 @@ namespace YunDa.Server.ISMSTcp.Domain
/// <summary>
/// 查询遥测
/// </summary>
public async Task<List<YCResultData>?> GetTelemetryInfoData(string id)
public async Task<List<ZzDataResultModel>?> GetTelemetryInfoData(string id)
{
try
{
@ -761,7 +761,7 @@ namespace YunDa.Server.ISMSTcp.Domain
if (response != null)
{
var result = ExtractDataFromAbpResponse<List<YCResultData>>(response);
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
return result;
}
@ -777,7 +777,7 @@ namespace YunDa.Server.ISMSTcp.Domain
/// <summary>
/// 查询遥信
/// </summary>
public async Task<List<YXResultData>?> GetTeleSignalData(string id)
public async Task<List<ZzDataResultModel>?> GetTeleSignalData(string id)
{
try
{
@ -787,7 +787,7 @@ namespace YunDa.Server.ISMSTcp.Domain
if (response != null)
{
var result = ExtractDataFromAbpResponse<List<YXResultData>>(response);
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
return result;
}
@ -827,7 +827,7 @@ namespace YunDa.Server.ISMSTcp.Domain
}
/// <summary>
/// 查询定值
/// 查询预置位的巡检结果
/// </summary>
public async Task<List<InspectionResultData>?> GetInspectionResultData(string id)
{
@ -852,6 +852,58 @@ namespace YunDa.Server.ISMSTcp.Domain
return null;
}
/// <summary>
/// 查询网关数据
/// </summary>
public async Task<List<ZzDataResultModel>?> GetGatewayInfoData(string id)
{
try
{
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>($"{_apiEndpoints.GetGatewayInfoDataUri}?id={id}"));
if (response != null)
{
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
return result;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Call GetTeleSignalData Api");
}
return null;
}
/// <summary>
/// 查询虚点信息
/// </summary>
public async Task<List<ZzDataResultModel>?> GetVirtualPointInfoData(string id)
{
try
{
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>($"{_apiEndpoints.GetVirtualPointInfoDataUri}?id={id}"));
if (response != null)
{
var result = ExtractDataFromAbpResponse<List<ZzDataResultModel>>(response);
return result;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Call GetTeleSignalData Api");
}
return null;
}
/// <summary>
/// 获取网线配置
/// </summary>
@ -911,8 +963,7 @@ namespace YunDa.Server.ISMSTcp.Domain
{
try
{
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetOpticalCableConfigUri, new object()));
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.GetOpticalCableConfigUri, new { includeInspectionItems = true }));
if (response != null)
{
@ -933,12 +984,53 @@ namespace YunDa.Server.ISMSTcp.Domain
/// <summary>
/// 保存巡检计划执行结果
/// </summary>
public async Task<bool> SaveSecondaryCircuitInspectionPlanResultAsync()
public async Task<string> SaveSecondaryCircuitInspectionPlanResultAsync(SecondaryCircuitInspectionResultSaveModel data)
{
try
{
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SaveSecondaryCircuitInspectionPlanResultUri, new object()));
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.SaveSecondaryCircuitInspectionPlanResultUri, data));
if (response != null)
{
var result = ExtractDataFromAbpResponse<string>(response);
return result;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
}
return string.Empty;
}
//调用Ai
public async Task<string> GetAIAnalysis(object data)
{
try
{
ThingWebClientHelper httpClient = new ThingWebClientHelper();
var result = await httpClient.PostJsonAsync(_apiEndpoints.GetAIAnalysisUri, data);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
}
return string.Empty;
}
//巡检计划执行结果AI诊断更新
public async Task<bool> UpdateReportWithAIAnalysisAsync(SecondaryCircuitInspectionAiSaveModel data)
{
try
{
var response = await Task.Run(() => HttpHelper.HttpPostRequestAsync<JObject>(_apiEndpoints.UpdateReportWithAIAnalysisUri, data));
if (response != null)
{
@ -952,5 +1044,53 @@ namespace YunDa.Server.ISMSTcp.Domain
return false;
}
//获取虚点信息
public async Task<List<ZzDataHistoryModel>> GetVariantBaseinfoAsync()
{
try
{
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>(_apiEndpoints.GetVariantBaseinfoUri));
if (response != null)
{
var result = ExtractDataFromAbpResponse<List<ZzDataHistoryModel>>(response);
return result;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
}
return null;
}
//获取网关信息
public async Task<List<ZzDataHistoryModel>> GetGateWayBaseInfoAsync()
{
try
{
var response = await Task.Run(() => ToolLibrary.HttpHelper.HttpGetRequest<JObject>(_apiEndpoints.GetGateWayBaseInfoUri));
if (response != null)
{
var result = ExtractDataFromAbpResponse<List<ZzDataHistoryModel>>(response);
return result;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Call GetOpticalCableConfigAsync Api");
}
return null;
}
}
}

View File

@ -129,6 +129,9 @@ namespace YunDa.Server.ISMSTcp.Extensions
services.AddSingleton<SecondaryCircuitInspectionPlanService>();
services.AddSingleton<ThingService>();
services.AddSingleton<ThingApiService>();
services.AddSingleton<GwErrorRatioService>();
services.AddSingleton<ZzTcpService>();
services.AddSingleton<ZzDataCacheContainerInit>();
// 日志过滤器配置
services.Configure<YunDa.Server.ISMSTcp.Filters.Configuration.LogFilterConfiguration>(

View File

@ -20,50 +20,4 @@ namespace YunDa.Server.ISMSTcp.Models
public string T { get; set; } = string.Empty;
}
public class CallYCByDataIdRequest
{
public List<string> Id { get; set; }
public int Times { get; set; }
}
public class YCResultValue
{
[JsonPropertyName("Value")]
public decimal Value { get; set; }
[JsonPropertyName("TimeStamp")]
public string TimeStamp { get; set; } = string.Empty;
}
public class YCResultData
{
[JsonPropertyName("Id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("Name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("DispatcherAddress")]
public int? DispatcherAddress { get; set; } = null;
[JsonPropertyName("Data")]
public List<YCResultValue> Data { get; set; } = new List<YCResultValue>();
public void ParseValue(List<YCData> datas)
{
Data.Clear();
foreach (var data in datas)
{
Data.Add(new YCResultValue() { Value = data.V, TimeStamp = data.T });
}
}
}
}

View File

@ -20,72 +20,72 @@ namespace YunDa.Server.ISMSTcp.Models
public string T { get; set; } = string.Empty;
}
public class YXCacheData
{
[JsonPropertyName("Id")]
public string Id { get; set; } = string.Empty;
//public class YXCacheData
//{
// [JsonPropertyName("Id")]
// public string Id { get; set; } = string.Empty;
[JsonPropertyName("Value")]
public int Value { get; set; }
// [JsonPropertyName("Value")]
// public int Value { get; set; }
[JsonPropertyName("ValueStr")]
public string ValueStr { get; set; } = string.Empty;
// [JsonPropertyName("ValueStr")]
// public string ValueStr { get; set; } = string.Empty;
[JsonPropertyName("TimeStamp")]
public string TimeStamp { get; set; } = string.Empty;
}
// [JsonPropertyName("TimeStamp")]
// public string TimeStamp { get; set; } = string.Empty;
//}
public class YXResultData
{
[JsonPropertyName("Id")]
public string Id { get; set; } = string.Empty;
//public class YXResultData
//{
// [JsonPropertyName("Id")]
// public string Id { get; set; } = string.Empty;
[JsonPropertyName("Name")]
public string Name { get; set; } = string.Empty;
// [JsonPropertyName("Name")]
// public string Name { get; set; } = string.Empty;
[JsonPropertyName("DispatcherAddress")]
public int? DispatcherAddress { get; set; } = null;
// [JsonPropertyName("DispatcherAddress")]
// public int? DispatcherAddress { get; set; } = null;
[JsonPropertyName("Data")]
public List<YXCacheData> Data { get; set; } = new List<YXCacheData>();
// [JsonPropertyName("Data")]
// public List<YXCacheData> Data { get; set; } = new List<YXCacheData>();
[JsonPropertyName("Value")]
public int Value { get; set; }
// [JsonPropertyName("Value")]
// public int Value { get; set; }
[JsonPropertyName("ValueStr")]
public string ValueStr { get; set; } = string.Empty;
// [JsonPropertyName("ValueStr")]
// public string ValueStr { get; set; } = string.Empty;
[JsonPropertyName("TimeStamp")]
public string TimeStamp { get; set; } = string.Empty;
// [JsonPropertyName("TimeStamp")]
// public string TimeStamp { get; set; } = string.Empty;
public void ParseValue(List<YXCacheData> datas)
{
Data.Clear();
// public void ParseValue(List<YXCacheData> datas)
// {
// Data.Clear();
foreach (var data in datas)
{
Data.Add(data);
}
// foreach (var data in datas)
// {
// Data.Add(data);
// }
if (Data.Count > 0)
{
Value = Data.Last().Value;
ValueStr = Data.Last().ValueStr;
TimeStamp = Data.Last().TimeStamp;
}
}
// if (Data.Count > 0)
// {
// Value = Data.Last().Value;
// ValueStr = Data.Last().ValueStr;
// TimeStamp = Data.Last().TimeStamp;
// }
// }
}
//}
public class YXResultValue
{
[JsonPropertyName("Value")]
public decimal Value { get; set; }
//public class YXResultValue
//{
// [JsonPropertyName("Value")]
// public decimal Value { get; set; }
[JsonPropertyName("TimeStamp")]
public string TimeStamp { get; set; } = string.Empty;
}
// [JsonPropertyName("TimeStamp")]
// public string TimeStamp { get; set; } = string.Empty;
//}
}

View File

@ -18,15 +18,22 @@ namespace YunDa.Server.ISMSTcp.Services
private readonly ITcpClient _tcpClient;
private readonly ISMSServerConfiguration _config;
private Timer? _connectionCheckTimer;
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
public AutoConnectionService(
ILogger<AutoConnectionService> logger,
ITcpClient tcpClient,
IOptions<ISMSServerConfiguration> config)
IOptions<ISMSServerConfiguration> config,
ZzDataCacheContainerInit zzDataCacheContainerInit)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_tcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient));
_config = config?.Value ?? throw new ArgumentNullException(nameof(config));
_zzDataCacheContainerInit = zzDataCacheContainerInit ?? throw new ArgumentNullException(nameof(zzDataCacheContainerInit));
zzDataCacheContainerInit.InitTcpService(_tcpClient);
}
/// <summary>

View File

@ -57,6 +57,7 @@ namespace YunDa.Server.ISMSTcp.Services
private readonly IRedisRepository<ProtectionDeviceCommInfoOutput, string> _protectionDeviceCommInfoRedis;
private readonly IApiEndpoints _apiEndpoints;
private readonly WebApiRequest _webApiRequest;
private readonly GwErrorRatioService _gwErrorRatioService;
//孪生体服务
private readonly ThingService _thingService;
@ -92,7 +93,8 @@ namespace YunDa.Server.ISMSTcp.Services
IApiEndpoints apiEndpoints,
WebApiRequest webApiRequest,
SecondaryCircuitInspectionPlanService secondaryCircuitInspectionPlanService,
ThingService thingService)
ThingService thingService,
GwErrorRatioService gwErrorRatioService)
{
_logger = logger;
_messageParser = messageParser;
@ -111,6 +113,7 @@ namespace YunDa.Server.ISMSTcp.Services
_webApiRequest = webApiRequest ?? throw new ArgumentNullException(nameof(webApiRequest));
_secondaryCircuitInspectionPlanService = secondaryCircuitInspectionPlanService ?? throw new ArgumentNullException(nameof(secondaryCircuitInspectionPlanService));
_thingService = thingService ?? throw new ArgumentNullException(nameof(thingService));
_gwErrorRatioService = gwErrorRatioService ?? throw new ArgumentNullException(nameof(gwErrorRatioService));
}
/// <summary>
@ -158,6 +161,7 @@ namespace YunDa.Server.ISMSTcp.Services
"WAVEDATA" => await ProcessWaveDataMessageAsync(contentToken),
"故障报告" => await ProcessFaultRptMessageAsync(contentToken),
"ALLDATA" => await ProcessAllDataMessageAsync(contentToken),
"GWERRORRATIO" => await ProcessGwErrorRatioMessageAsync(contentToken),
_ => ProcessUnknownMessageType(messageType, originalJson)
};
@ -254,19 +258,23 @@ namespace YunDa.Server.ISMSTcp.Services
if (contentToken is JArray contentArray && contentArray.Count > 0)
{
//2025-10-25 所有值加1
foreach (var item in contentArray)
{
if (item["V"] != null && item["V"].Type == JTokenType.String)
{
string val = item["V"].Value<string>();
if(int.TryParse(val, out int value))
{
item["V"] = (value + 1).ToString();
}
//2025-10-25 所有值加1 send CallYXByDevice|B001208
//foreach (var item in contentArray)
//{
// if (item["YX_ID"].Value<string>() == "YXB001103058")
// {
// int kk = 0;
// }
// if (item["V"] != null && item["V"].Type == JTokenType.String)
// {
// string val = item["V"].Value<string>();
// if(int.TryParse(val, out int value))
// {
// //item["V"] = (value + 1).ToString();
// }
}
}
// }
//}
List<TelesignalisationModel> telesignalisationModels = new List<TelesignalisationModel>();
@ -299,7 +307,7 @@ namespace YunDa.Server.ISMSTcp.Services
}
//将状态推送到孪生体
_thingService.UpdateThingYXStatus(contentArray.ToObject<List<YXData>>(), telesignalisationModels);
_thingService.UpdateThingYXStatus(telesignalisationModels);
}
else
@ -992,6 +1000,16 @@ namespace YunDa.Server.ISMSTcp.Services
}
}
/// <summary>
/// 处理网关通信数据消息,数据每小时发一次
/// </summary>
/// <param name="contentToken">消息内容</param>
/// <returns>处理结果</returns>
private async Task<Models.ProcessResult> ProcessGwErrorRatioMessageAsync(JToken contentToken)
{
return await _gwErrorRatioService.TranselateData(contentToken);
}
#endregion
/// <summary>

View File

@ -0,0 +1,134 @@
using Google.Protobuf.WellKnownTypes;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Utilities;
using Quartz;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using YunDa.Server.ISMSTcp.Models;
using YunDa.SOMS.DataTransferObject.MaintenanceAndOperations.SecondaryEquipment;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace YunDa.Server.ISMSTcp.Services
{
public class GwErrorRatioService
{
private readonly ILogger<AlarmService> _logger;
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
//数据缓冲队列
private ZzDataCacheContainer _cacheContainer = new ZzDataCacheContainer(2 * 60 + 1);//保留2小时的数据多1分钟兼容发送送数据发送延迟情况每1小时发送一次
public GwErrorRatioService(ILogger<AlarmService> logger, ZzDataCacheContainerInit zzDataCacheContainerInit)
{
_logger = logger;
_zzDataCacheContainerInit = zzDataCacheContainerInit;
_zzDataCacheContainerInit.InitGateWayBaseInfo(_cacheContainer);
}
//解析数据
public async Task<Models.ProcessResult> TranselateData(JToken contentToken)
{
try
{
// 输入验证
if (contentToken == null)
{
_logger.LogWarning("GwErrorRatio message content is null");
return Models.ProcessResult.Error("GwErrorRatio message content is null");
}
// 批量处理网关通信数据
List<GwErrorRatioDataModel> gatewayDatas;
gatewayDatas = contentToken.ToObject<List<GwErrorRatioDataModel>>();
if (gatewayDatas == null || gatewayDatas.Count == 0)
{
_logger.LogWarning("GwErrorRatio message contains no valid alert data");
}
else
{
ProcessData(gatewayDatas);
}
return Models.ProcessResult.Success(contentToken, "GWERRORRATIO");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing GwErrorRatio message");
return Models.ProcessResult.Error($"GwErrorRatio processing error: {ex.Message}");
}
}
//处理数据
private void ProcessData(List<GwErrorRatioDataModel> gatewayDatas)
{
foreach (var data in gatewayDatas)
{
data.DataTimeStamp = DateTime.Now;
SaveDataToCache(data);
}
}
//缓存遥信数据到内存
private void SaveDataToCache(GwErrorRatioDataModel data)
{
_cacheContainer.Write(data.GatewayID, data.ErrorRatio, data.DataTimeStamp, "", data.ErrorRatio.ToString("P2"));
}
//查询数据
public async Task<List<ZzDataResultModel>> GetDataByIds(List<string> ids, int seconds, CancellationToken cancellationToken)
{
try
{
if (seconds < 60 * 60)
{
seconds = 60 * 60 + 60; //多60秒兼容发送送数据发送延迟情况每1小时发送一次
}
else if (seconds < 2 * 60 * 60)
{
seconds = 2 * 60 * 60 + 60;
}
return await _cacheContainer.Read(ids, seconds, 0, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "GwErrorRatioService: GetDataByIds 出错");
return new List<ZzDataResultModel>();
}
}
}
public class GwErrorRatioDataModel
{
public string GatewayID { get; set; } = string.Empty; //网关Id
public double ErrorRatio { get; set; } //每小时的错误率
public DateTime DataTimeStamp { get; set; }
}
public class ErrorRatioDataByIdRequestModel
{
public List<string> Id { get; set; }
public int Times { get; set; }
public int TimeWindowType { get; set; }
}
}

View File

@ -1,18 +1,26 @@
using Jint;
using DotNetty.Common.Utilities;
using Jint;
using Jint.Native.Object;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@ -22,6 +30,7 @@ using YunDa.Server.ISMSTcp.Models;
using YunDa.SOMS.DataTransferObject.DataMonitoring.SecondaryCircuitInspection;
using YunDa.SOMS.DataTransferObject.DataMonitoring.SecondaryCircuitInspection.Configurations;
using YunDa.SOMS.DataTransferObject.MaintenanceAndOperations.SecondaryEquipment;
using YunDa.SOMS.Entities.DataMonitoring.SecondaryCircuitInspection;
namespace YunDa.Server.ISMSTcp.Services
{
@ -46,6 +55,19 @@ namespace YunDa.Server.ISMSTcp.Services
}
public class SecondaryCircuitInspectionItemOutputEx
{
public int TriggerType { get; set; }
public SecondaryCircuitInspectionItemOutput Item { get; set; }
public SecondaryCircuitInspectionItemOutputEx(int triggerType, SecondaryCircuitInspectionItemOutput item)
{
TriggerType = triggerType;
Item = item;
}
}
public class DeviceDzData
{
public List<DzInfo> UserDz { get; set; }
@ -66,26 +88,89 @@ namespace YunDa.Server.ISMSTcp.Services
public string Value { get; set; } = string.Empty;
}
//巡检结果
public class SecondaryCircuitInspectionResultModel
public class SecondaryCircuitInspectionStoreDataModel
{
//遥测数据
public List<YCResultData> TelemetryInfoData { get; set; }
public List<ZzDataResultModel> TelemetryData { get; set; }
//遥信数据
public List<YXResultData> TeleSignalData { get; set; }
public List<ZzDataResultModel> TeleSignalData { get; set; }
//定值
public List<DeviceDzData> DeviceDzData { get; set; }
public List<DeviceDzData> SettingData { get; set; }
//预置位的巡检结果
public List<InspectionResultData> InspectionResultData { get; set; }
//预置位
public List<InspectionResultData> PresetData { get; set; }
//虚点数据
public List<ZzDataResultModel> VariantData { get; set; }
//网关数据
public List<ZzDataResultModel> GatewayData { get; set; }
}
//巡检结果
public class SecondaryCircuitInspectionJsDataModel
{
public int TimeWindowSeconds { get; set; }
public int TimeWindowCount { get; set; }
public TimeWindowTypeEnum TimeWindowType { get; set; }
public SecondaryCircuitInspectionStoreDataModel StoreData { get; set; } = new SecondaryCircuitInspectionStoreDataModel();
public List<SecondaryCircuitInspectionTelemetryConfigOutput> TelemetryConfigs { get; set; }
public List<SecondaryCircuitInspectionTelesignalConfigOutput> TelesignalConfigs { get; set; }
public List<SecondaryCircuitInspectionCameraPresetOutput> CameraPresets { get; set; }
public List<SecondaryCircuitInspectionDeviceConfigOutput> DeviceConfigs { get; set; }
public List<SecondaryCircuitInspectionVariantOutput> VariantConfigs { get; set; }
public List<SecondaryCircuitInspectionGatewayOutput> GatewayConfigs { get; set; }
}
//巡检结果上传Model
public class SecondaryCircuitInspectionResultSaveModel
{
public string SecondaryCircuitInspectionItemId { get; set; } = string.Empty;
public string InspectionPlanId { get; set; } = string.Empty;
public DateTime ExecutionTime { get; set; } = DateTime.Now;
public long ExecutionDurationMs { get; set; }
public int TriggerType { get; set; } //1计划2事件
public string TriggerEventId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty; //"异常|正常|故障",
public string InspectionResult { get; set; } = string.Empty; //巡检结果 inspectionResult不能超过15个字符
public string CalculationProcess { get; set; } = string.Empty; //计算过程
public string VerificationResult { get; set; } = string.Empty; //校验结果
public SecondaryCircuitInspectionJsDataModel Data { get; set; }
public string Remark { get; set; } = string.Empty; //无用
}
//
public class SecondaryCircuitInspectionAiParamModel
{
public string InspectionResultId { get; set; } = string.Empty;
public string PlanName { get; set; } = string.Empty;
public SecondaryCircuitInspectionResultSaveModel SaveModel { get; set; }
}
public class SecondaryCircuitInspectionAiSaveModel
{
public string InspectionResultId { get; set; } = string.Empty;
public string AiAnalysisResult { get; set; } = string.Empty; // "AI结果状态失败|成功",
public string ResultHandleDescription { get; set; } = string.Empty; //"AI返回的处理结果"
}
//二次回路巡检计划
public class SecondaryCircuitInspectionPlanService
{
private readonly bool _isDebug = false;
private readonly ILogger<SecondaryCircuitInspectionPlanService> _logger;
private readonly IApiEndpoints _apiEndpoints;
private readonly WebApiRequest _webApiRequest;
@ -95,13 +180,15 @@ namespace YunDa.Server.ISMSTcp.Services
private Queue<SecondaryCircuitInspectionPlanStateModel> _planList;
private readonly object _planLock = new object();
private readonly AsyncLock _planLock = new AsyncLock();
private int _planCheckDay = 0;
private readonly Channel<SecondaryCircuitInspectionItemOutput> _singlePlanChannel;
private readonly Channel<SecondaryCircuitInspectionItemOutputEx> _singlePlanChannel; //巡检计划
private readonly Channel<SecondaryCircuitInspectionAiParamModel> _aiChannel; //Ai调用
//巡检事件
private Queue<SecondaryCircuitEventDrivenConfigOutput> _eventPlanList;
private readonly object _eventPlanLock = new object();
private readonly AsyncLock _eventPlanLock = new AsyncLock();
private bool _getEventPlanListOk = false;
public SecondaryCircuitInspectionPlanService(
@ -119,17 +206,21 @@ namespace YunDa.Server.ISMSTcp.Services
_planList = new Queue<SecondaryCircuitInspectionPlanStateModel>();
_singlePlanChannel = Channel.CreateUnbounded<SecondaryCircuitInspectionItemOutput>();
_singlePlanChannel = Channel.CreateUnbounded<SecondaryCircuitInspectionItemOutputEx>();
_aiChannel = Channel.CreateUnbounded<SecondaryCircuitInspectionAiParamModel>();
_eventPlanList = new Queue<SecondaryCircuitEventDrivenConfigOutput>();
//var kk = webApiRequest.GetTeleSignalData("08de219e-3524-41db-8d7d-86b547bac16a");
StartAsync();
}
private async Task StartAsync()
{
//更新计划
_ = Task.Run(async () =>
{
@ -184,7 +275,8 @@ namespace YunDa.Server.ISMSTcp.Services
});
//执行计划(两个线程同时执行)
int threadNumber = 3;
int threadNumber = _isDebug ? 1 : 3;
for (int i = 0; i < threadNumber; ++i)
{
_ = Task.Run(async () => {
@ -203,6 +295,25 @@ namespace YunDa.Server.ISMSTcp.Services
});
}
//调用Ai获取诊断结果
for (int i = 0; i < threadNumber; ++i)
{
_ = Task.Run(async () => {
var rand = new Random(Guid.NewGuid().GetHashCode());
await foreach (var item in _aiChannel.Reader.ReadAllAsync())
{
// 让每个线程在执行之间有不同的节奏
await Task.Delay(rand.Next(0, 300));
await CallAiAndSave(item);
await Task.Delay(500);
}
});
}
await Task.CompletedTask;
}
@ -211,40 +322,60 @@ namespace YunDa.Server.ISMSTcp.Services
{
try
{
var list = await _webApiRequest.GetSecondaryCircuitInspectionPlanListAsync();
if (list != null)
using (await _planLock.LockAsync())
{
lock (_planLock)
var oldPlan = _planList.ToArray();
_planList.Clear();
int start = 0;
while (start < 1000)
{
var oldPlan = _planList.ToArray();
_planList.Clear();
List<SecondaryCircuitInspectionPlanStateModel> planlist = new List<SecondaryCircuitInspectionPlanStateModel>();
foreach (var item in list)
var list = await _webApiRequest.GetSecondaryCircuitInspectionPlanListAsync(start);
if (list != null)
{
planlist.Add(new SecondaryCircuitInspectionPlanStateModel() { Plan = item });
}
foreach (var item in planlist)
{
var plan = oldPlan.FirstOrDefault(x => x.Plan.Id == item.Plan.Id && x.Plan.ScheduledTimeString == item.Plan.ScheduledTimeString);
if (plan != null)
List<SecondaryCircuitInspectionPlanStateModel> planlist = new List<SecondaryCircuitInspectionPlanStateModel>();
foreach (var item in list)
{
item.ExecuteTime = plan.ExecuteTime;
planlist.Add(new SecondaryCircuitInspectionPlanStateModel() { Plan = item });
}
_planList.Enqueue(item);
}
foreach (var item in planlist)
{
var plan = oldPlan.FirstOrDefault(x => x.Plan.Id == item.Plan.Id && x.Plan.ScheduledTimeString == item.Plan.ScheduledTimeString);
if (plan != null)
{
item.ExecuteTime = plan.ExecuteTime;
}
//获取到绑定信息,可以更新全部状态了
if (planlist.Count > 0)
_updatedPlanOk = true;
_planList.Enqueue(item);
}
if (list.Count < 10)
{
break;
}
else
{
start += 10;
}
}
else
{
break;
}
}
//获取到绑定信息,可以更新全部状态了
if (_planList.Count > 0)
_updatedPlanOk = true;
}
}
catch (Exception ex)
{
@ -259,21 +390,38 @@ namespace YunDa.Server.ISMSTcp.Services
{
try
{
var list = await _webApiRequest.GetCircuitEventDrivenConfigAsync();
if (list != null)
using (await _eventPlanLock.LockAsync())
{
lock (_eventPlanLock)
_eventPlanList.Clear();
int start = 0;
while (start < 1000)
{
_eventPlanList.Clear();
var list = await _webApiRequest.GetCircuitEventDrivenConfigAsync(start);
if (list != null)
{
foreach (var item in list)
_eventPlanList.Enqueue(item);
foreach (var item in list)
_eventPlanList.Enqueue(item);
//获取到绑定信息,可以更新全部状态了
if (_eventPlanList.Count > 0)
_getEventPlanListOk = true;
if (list.Count < 10)
{
break;
}
else
{
start += 10;
}
}
else
{
break;
}
}
//获取到绑定信息,可以更新全部状态了
if (_eventPlanList.Count > 0)
_getEventPlanListOk = true;
}
}
catch (Exception ex)
@ -295,7 +443,8 @@ namespace YunDa.Server.ISMSTcp.Services
try
{
SecondaryCircuitInspectionPlanStateModel[] planList;
lock (_planLock)
using (await _planLock.LockAsync())
{
planList = _planList.ToArray();
}
@ -305,7 +454,7 @@ namespace YunDa.Server.ISMSTcp.Services
if (now.Day != _planCheckDay)
{//翻天,清空已执行标记
lock (_planLock)
using (await _planLock.LockAsync())
{
foreach (var item in _planList)
item.ExecuteTime = DateTime.MinValue;
@ -322,9 +471,9 @@ namespace YunDa.Server.ISMSTcp.Services
{
if (item.Plan.ScheduledWeekDaysList.IndexOf(week) != -1)
{
await PushPlanToChannel(item.Plan);
await PushPlanToChannel(1, item.Plan);
lock (_planLock)
using (await _planLock.LockAsync())
{
item.ExecuteTime = DateTime.Now;
}
@ -345,7 +494,7 @@ namespace YunDa.Server.ISMSTcp.Services
}
//将需要执行的计划写到队列里
private async Task<bool> PushPlanToChannel(SecondaryCircuitInspectionPlanOutput plan)
private async Task<bool> PushPlanToChannel(int triggerType, SecondaryCircuitInspectionPlanOutput plan)
{
try
{
@ -358,7 +507,7 @@ namespace YunDa.Server.ISMSTcp.Services
if(item.IsActive && !string.IsNullOrWhiteSpace(item.CalculationExpression) && !string.IsNullOrWhiteSpace(id))
{
//写到队列里,由队列控制执行频率
await _singlePlanChannel.Writer.WriteAsync(item);
await _singlePlanChannel.Writer.WriteAsync(new SecondaryCircuitInspectionItemOutputEx(triggerType, item));
}
}
@ -373,43 +522,345 @@ namespace YunDa.Server.ISMSTcp.Services
}
//执行计划
private async Task ExecutePlan(SecondaryCircuitInspectionItemOutput item)
private async Task ExecutePlan(SecondaryCircuitInspectionItemOutputEx itemEx)
{
string func2 = "";
string data2 = "";
try
{
var item = itemEx.Item;
string id = item.Id.ToString();
if (!string.IsNullOrWhiteSpace(id))
{
SecondaryCircuitInspectionJsDataModel jsData = new SecondaryCircuitInspectionJsDataModel();
jsData.TimeWindowSeconds = item.TimeWindowSeconds;
jsData.TimeWindowCount = item.TimeWindowCount;
jsData.TimeWindowType = item.TimeWindowType;
jsData.TelemetryConfigs = item.TelemetryConfigs;
jsData.TelesignalConfigs = item.TelesignalConfigs;
jsData.CameraPresets = item.CameraPresets;
jsData.DeviceConfigs = item.DeviceConfigs;
jsData.VariantConfigs = item.VariantConfigs;
jsData.GatewayConfigs = item.GatewayConfigs;
var engine = new Jint.Engine();
SecondaryCircuitInspectionResultModel resultModel = new SecondaryCircuitInspectionResultModel();
var task1 = GetSourceDataAsync<List<YCResultData>>(id, 1);
var task2 = GetSourceDataAsync<List<YXResultData>>(id, 2);
var task3 = GetSourceDataAsync<List<DeviceDzData>>(id, 3);
var task4 = GetSourceDataAsync<List<InspectionResultData>>(id, 4);
// 等待全部完成
await Task.WhenAll(task1, task2, task3, task4);
resultModel.TelemetryInfoData = await task1;
resultModel.TeleSignalData = await task2;
resultModel.DeviceDzData = await task3;
resultModel.InspectionResultData = await task4;
engine.Execute(item.CalculationExpression);
var result = engine.Invoke("calculate", resultModel).AsObject();
func2 = item.CalculationExpression;
ObjectInstance? saveObj = null;
var sw = Stopwatch.StartNew();
for (int i = 0; i < item.TimeWindowCount; i++)
{
var tasks = new List<Task>();
Task<List<ZzDataResultModel>>? t1 = null;
Task<List<ZzDataResultModel>>? t2 = null;
Task<List<DeviceDzData>>? t3 = null;
Task<List<InspectionResultData>>? t4 = null;
Task<List<ZzDataResultModel>>? t5 = null;
Task<List<ZzDataResultModel>>? t6 = null;
if (item.TelemetryConfigCount > 0)
{
t1 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 1);
tasks.Add(t1);
}
if (item.TelesignalConfigCount > 0)
{
t2 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 2);
tasks.Add(t2);
}
if (item.DeviceConfigCount > 0)
{
t3 = GetSourceDataAsync<List<DeviceDzData>>(id, 3);
tasks.Add(t3);
}
if (item.CameraPresetCount > 0)
{
t4 = GetSourceDataAsync<List<InspectionResultData>>(id, 4);
tasks.Add(t4);
}
if (item.GatewayConfigCount > 0)
{
t5 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 5);
tasks.Add(t5);
}
if (item.VariantConfigCount > 0)
{
t6 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 6);
tasks.Add(t6);
}
await Task.WhenAll(tasks);
if (t1 != null) jsData.StoreData.TelemetryData = await t1;
if (t2 != null) jsData.StoreData.TeleSignalData = await t2;
if (t3 != null) jsData.StoreData.SettingData = await t3;
if (t4 != null) jsData.StoreData.PresetData = await t4;
if (t5 != null) jsData.StoreData.GatewayData = await t5;
if (t6 != null) jsData.StoreData.VariantData = await t6;
if(jsData.StoreData.TelemetryData == null && jsData.StoreData.TeleSignalData == null && jsData.StoreData.SettingData == null &&
jsData.StoreData.PresetData == null && jsData.StoreData.GatewayData == null && jsData.StoreData.VariantData == null)
{
continue;
}
var json = JsonConvert.SerializeObject(jsData,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
var js = $"JSON.parse('{json.Replace("'", "\\'")}')";
data2 = json;
var jsObj = engine.Evaluate(js).ToObject();
var jsResult = engine.Invoke("calculate", jsObj).AsObject();
if (jsResult != null && jsResult.HasProperty("status"))
{
saveObj = jsResult;
var status = jsResult.Get("status").ToString();
if (status == "正常" || string.IsNullOrWhiteSpace(status))
{//只要正常,就不用再检测了
break;
}
}
else
{
saveObj = null;
break;
}
}
sw.Stop();
//保存结果
if (saveObj != null)
{
SecondaryCircuitInspectionResultSaveModel saveData = new SecondaryCircuitInspectionResultSaveModel();
if(saveObj.HasProperty("status") && saveObj.HasProperty("inspectionResult") && saveObj.HasProperty("calculationProcess") && saveObj.HasProperty("verificationResult"))
{
saveData.SecondaryCircuitInspectionItemId = item.Id.ToString();
saveData.ExecutionDurationMs = sw.ElapsedMilliseconds;
saveData.TriggerType = itemEx.TriggerType;
if (saveData.TriggerType == 1)
saveData.InspectionPlanId = id;
else
saveData.TriggerEventId = id;
saveData.Status = saveObj.Get("status").ToString();
saveData.InspectionResult = saveObj.Get("inspectionResult").ToString();
saveData.CalculationProcess = saveObj.Get("calculationProcess").ToString();
saveData.VerificationResult = saveObj.Get("verificationResult").ToString();
saveData.Data = jsData;
var inspectionResultId = await _webApiRequest.SaveSecondaryCircuitInspectionPlanResultAsync(saveData);
//获取Ai诊断结果并保存不正常时才诊断
if (!string.IsNullOrWhiteSpace(inspectionResultId) && saveData.Status != "正常")
{
SecondaryCircuitInspectionAiParamModel aiParamModel = new SecondaryCircuitInspectionAiParamModel();
aiParamModel.InspectionResultId = inspectionResultId;
aiParamModel.PlanName = $"{itemEx.Item.Name} {itemEx.Item.Description}";
aiParamModel.SaveModel = saveData;
_aiChannel.Writer.WriteAsync(aiParamModel);
}
}
}
}
}
catch(Exception ex)
{
_logger.LogError(ex, $"SecondaryCircuitInspectionPlanService 执行巡检项失败:{item.Id.ToString()}");
_logger.LogError(ex, $"SecondaryCircuitInspectionPlanService 执行巡检项失败:{itemEx.Item.Id.ToString()}");
}
}
private async Task CallAiAndSave(SecondaryCircuitInspectionAiParamModel aiParamModel)
{
try
{
var aiParam = new
{
user_message = GetAiParamString(aiParamModel),
history = new List<string[]>()
};
var aiResultRaw = await _webApiRequest.GetAIAnalysis(aiParam);
bool isSuccess = false;
if (!string.IsNullOrWhiteSpace(aiResultRaw))
{
string aiResult = ParseAiResult(aiResultRaw);
if (!string.IsNullOrWhiteSpace(aiResult))
{
SecondaryCircuitInspectionAiSaveModel data = new SecondaryCircuitInspectionAiSaveModel();
data.InspectionResultId = aiParamModel.InspectionResultId;
data.AiAnalysisResult = "成功";
data.ResultHandleDescription = aiResult;
await _webApiRequest.UpdateReportWithAIAnalysisAsync(data);
isSuccess = true;
}
}
if(!isSuccess)
{
SecondaryCircuitInspectionAiSaveModel data = new SecondaryCircuitInspectionAiSaveModel();
data.InspectionResultId = aiParamModel.InspectionResultId;
data.AiAnalysisResult = "失败";
data.ResultHandleDescription = "";
await _webApiRequest.UpdateReportWithAIAnalysisAsync(data);
}
}
catch(Exception ex)
{
_logger.LogError(ex, $"SecondaryCircuitInspectionPlanService CallAiAndSave出错");
}
}
private static string GetAiParamString(SecondaryCircuitInspectionAiParamModel aiParamModel)
{
var sb = new StringBuilder();
sb.AppendLine("你是一个电力系统专家,请根据以下变电站二次巡检结果数据生成故障排除建议:");
sb.AppendLine();
sb.AppendLine("检修的建议要避免停止变电所的正常运行,要以宽松的方式,检修工作必须遵循“安全第一、预防为主”的核心原则,实施全过程精益化管理");
sb.AppendLine("检修前,需制定详尽的计划与方案,确保人员资质合格、物资工具齐备,并严格执行工作票制度,实现安全隔离。​检修中,必须强化现场安全管控,设专人监护,并严格按标准工艺施工,做好关键数据记录与过程质检。​检修后,要严格执行试验验收程序,确保设备性能达标,彻底清理现场,并完善检修报告与设备台账,形成闭环管理");
sb.AppendLine("整个流程旨在通过规范化、标准化的作业,确保人身、设备与电网安全,提升设备可靠性,保障电力稳定供应");
sb.AppendLine(",减少思考过程");
sb.AppendLine();
// 巡检结果
sb.AppendLine($"巡检内容为:");
sb.AppendLine(aiParamModel.PlanName);
sb.AppendLine();
sb.AppendLine("巡检结果为:");
sb.AppendLine($"巡检状态: {aiParamModel.SaveModel.Status}");
sb.AppendLine($"计算过程: {aiParamModel.SaveModel.CalculationProcess}");
sb.AppendLine($"巡检结果: {aiParamModel.SaveModel.InspectionResult}");
sb.AppendLine($"校验结果: {aiParamModel.SaveModel.VerificationResult}");
sb.AppendLine();
sb.AppendLine();
sb.AppendLine("请按以下格式生成排除建议直接建议内容每条建议前加序号如1、每条建议间加换行符");
return sb.ToString();
}
private static string GetAiParamString2(SecondaryCircuitInspectionAiParamModel aiParamModel)
{
var sb = new StringBuilder();
sb.AppendLine("你是一个电力系统专家,请根据以下变电站二次巡检结果数据生成检修策略:");
sb.AppendLine();
sb.Append("检修的开始时间不得晚于当前时间 ")
.AppendLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
sb.AppendLine("检修的建议要避免停止变电所的正常运行,要以宽松的方式,检修工作必须遵循“安全第一、预防为主”的核心原则,实施全过程精益化管理");
sb.AppendLine("检修前,需制定详尽的计划与方案,确保人员资质合格、物资工具齐备,并严格执行工作票制度,实现安全隔离。​检修中,必须强化现场安全管控,设专人监护,并严格按标准工艺施工,做好关键数据记录与过程质检。​检修后,要严格执行试验验收程序,确保设备性能达标,彻底清理现场,并完善检修报告与设备台账,形成闭环管理");
sb.AppendLine("整个流程旨在通过规范化、标准化的作业,确保人身、设备与电网安全,提升设备可靠性,保障电力稳定供应");
sb.AppendLine(",减少思考过程");
sb.AppendLine();
// 巡检结果
sb.AppendLine("巡检结果为:");
sb.AppendLine($"巡检状态: {aiParamModel.SaveModel.Status}");
sb.AppendLine($"计算过程: {aiParamModel.SaveModel.CalculationProcess}");
sb.AppendLine($"巡检结果: {aiParamModel.SaveModel.InspectionResult}");
sb.AppendLine($"校验结果: {aiParamModel.SaveModel.VerificationResult}");
sb.AppendLine();
sb.AppendLine();
sb.AppendLine("请按以下JSON格式生成策略");
sb.AppendLine(@"{
""level"": """",
""focusAreas"": [""1"", ""2""],
""steps"": [
{""duration"": """", ""content"": """"}
],
""resources"": [""1"", ""2""],
""schedule"": {
""startDate"": """",
""optimalWindow"": """",
""endDate"": """"
},
""risks"": [
{""title"": """", ""severity"": """", ""mitigation"": """"}
]
}");
return sb.ToString();
}
private static string ParseAiResult(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
return string.Empty;
var result = new StringBuilder();
foreach (string line in raw.Split('\n'))
{
var trimmed = line.Trim();
if (string.IsNullOrWhiteSpace(trimmed))
continue;
try
{
using var doc = JsonDocument.Parse(trimmed);
var root = doc.RootElement;
if (root.TryGetProperty("type", out var typeProp) &&
typeProp.GetString() == "content" &&
root.TryGetProperty("content", out var contentProp))
{
result.Append(contentProp.GetString());
}
}
catch
{
// 跳过非 JSON 行
continue;
}
}
return result.ToString();
}
//获取计划数据
@ -437,6 +888,14 @@ namespace YunDa.Server.ISMSTcp.Services
result = await _webApiRequest.GetInspectionResultData(id);
break;
case 5:
result = await _webApiRequest.GetGatewayInfoData(id);
break;
case 6:
result = await _webApiRequest.GetVirtualPointInfoData(id);
break;
default:
throw new ArgumentException("Invalid type");
}
@ -468,7 +927,7 @@ namespace YunDa.Server.ISMSTcp.Services
try
{
SecondaryCircuitEventDrivenConfigOutput[] planList;
lock (_planLock)
using (await _eventPlanLock.LockAsync())
{
planList = _eventPlanList.ToArray();
}
@ -485,26 +944,54 @@ namespace YunDa.Server.ISMSTcp.Services
{
var engine = new Jint.Engine();
SecondaryCircuitInspectionResultModel resultModel = new SecondaryCircuitInspectionResultModel();
SecondaryCircuitInspectionJsDataModel jsData = new SecondaryCircuitInspectionJsDataModel();
var task1 = GetSourceDataAsync<List<YCResultData>>(id, 1);
var task2 = GetSourceDataAsync<List<YXResultData>>(id, 2);
var tasks = new List<Task>();
// 等待全部完成
await Task.WhenAll(task1, task2);
Task<List<ZzDataResultModel>>? t1 = null;
Task<List<ZzDataResultModel>>? t2 = null;
resultModel.TelemetryInfoData = await task1;
resultModel.TeleSignalData = await task2;
if (item.TelemetryConfigs.Count > 0)
{
t1 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 1);
tasks.Add(t1);
}
if (item.TelesignalConfigs.Count > 0)
{
t2 = GetSourceDataAsync<List<ZzDataResultModel>>(id, 2);
tasks.Add(t2);
}
await Task.WhenAll(tasks);
if (t1 != null) jsData.StoreData.TelemetryData = await t1;
if (t2 != null) jsData.StoreData.TeleSignalData = await t2;
engine.Execute(item.TriggerExpression);
var canCheck = engine.Invoke("calculate", resultModel).AsBoolean();
bool canCheck = true;
if(!_isDebug)
{
canCheck = engine.Invoke("calculate", jsData).AsBoolean();
}
if(canCheck)
{
await Task.Delay(item.MandatoryWaitSeconds * 1000);
CheckPlanFormIds(item.SecondaryCircuitInspectionEventItems);
//CheckPlanFormIds(item.SecondaryCircuitInspectionEventItems);
//执行巡检
foreach(var planItem in item.SecondaryCircuitInspectionEventItems)
{
if (!string.IsNullOrWhiteSpace(planItem.CalculationExpression) && !string.IsNullOrWhiteSpace(planItem.Id.ToString()))
{
//写到队列里,由队列控制执行频率
await _singlePlanChannel.Writer.WriteAsync(new SecondaryCircuitInspectionItemOutputEx(2, planItem));
}
}
}
}
}
@ -519,45 +1006,7 @@ namespace YunDa.Server.ISMSTcp.Services
}
//检测巡检计划中的计划
private async Task CheckPlanFormIds(List<SecondaryCircuitInspectionItemOutput> planIds)
{
if (_planList == null)
return;
try
{
SecondaryCircuitInspectionPlanStateModel[] planList;
lock (_planLock)
{
planList = _planList.ToArray();
}
foreach (var plan in planList)
{
if(plan.Plan.IsActive)
{
foreach (var item in plan.Plan.InspectionItems)
{
if(planIds.Find( e => e.Id == item.Id) != null && item.IsActive)
{
if (!string.IsNullOrWhiteSpace(item.CalculationExpression) && !string.IsNullOrWhiteSpace(item.Id.ToString()))
{
//写到队列里,由队列控制执行频率
await _singlePlanChannel.Writer.WriteAsync(item);
}
}
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "SecondaryCircuitInspectionPlanService - CheckPlanFormIds :发生错误");
}
return;
}
////获取遥测数据
//public async Task<List<YCResultData>> CallYCByDataIdAsync(CallYCByDataIdRequest request, CancellationToken cancellationToken = default)

View File

@ -5,6 +5,7 @@ using StackExchange.Redis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
@ -62,10 +63,11 @@ namespace YunDa.Server.ISMSTcp.Services
// 🔧 新增:遥测数据召唤存储相关字段
// 存储召唤的遥测数据DataId -> List<TimestampedYCData>
private readonly ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>> _ycDataStorage = new ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>>();
private readonly Timer _cleanupTimer;
private const int CLEANUP_INTERVAL_MS = 60000; // 1分钟清理一次
private const int DATA_EXPIRATION_MINUTES = 5; // 数据保留5分钟
//private readonly ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>> _ycDataStorage = new ConcurrentDictionary<string, ConcurrentBag<TimestampedYCData>>();
//private readonly Timer _cleanupTimer;
//private const int CLEANUP_INTERVAL_MS = 60000; // 1分钟清理一次
//private const int DATA_EXPIRATION_MINUTES = 5; // 数据保留5分钟
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(3); // 数据保留3分钟
// 初始化状态
public volatile bool _isInitialized = false;
@ -98,7 +100,7 @@ namespace YunDa.Server.ISMSTcp.Services
_batchPushTimer = new Timer(ProcessBatchPush, null, BATCH_PUSH_INTERVAL_MS, BATCH_PUSH_INTERVAL_MS);
// 🔧 新增:初始化数据清理定时器
_cleanupTimer = new Timer(CleanupExpiredData, null, CLEANUP_INTERVAL_MS, CLEANUP_INTERVAL_MS);
//_cleanupTimer = new Timer(CleanupExpiredData, null, CLEANUP_INTERVAL_MS, CLEANUP_INTERVAL_MS);
}
/// <summary>
@ -178,6 +180,10 @@ namespace YunDa.Server.ISMSTcp.Services
if (_ycIdToHashKeysMapping[model.ismsbaseYCId].Contains(haskey))
{
mappingCount++;
//初始化
_zzDataCacheContainer.Write(model.ismsbaseYCId, model.ResultValue, model.ResultTime, model.Name, $"{model.ResultValue} {model.Unit}", model.DispatcherAddress);
}
}
else
@ -370,9 +376,6 @@ namespace YunDa.Server.ISMSTcp.Services
{
try
{
// 🔧 新增:存储遥测数据到内存,用于数据召唤功能
StoreYCDataForRetrieval(ycDataModel);
// 使用映射字典查找对应的haskey列表
if (!_ycIdToHashKeysMapping.TryGetValue(ycDataModel.YC_ID, out List<string> haskeys))
{
@ -417,6 +420,14 @@ namespace YunDa.Server.ISMSTcp.Services
// 保存到Redis
await _telemeteringModelListRedis.HashSetUpdateOneAsync(redisKey, haskey, telemeteringModel);
//if(ycDataModel.YC_ID == "YCB001101001")
//{
// Debug.WriteLine("YCB001101001 : ", ycDataModel.V.ToString(), telemeteringModel.ResultValue.ToString(), telemeteringModel.Coefficient.ToString());
//}
//保存到缓存
_zzDataCacheContainer.Write(ycDataModel.YC_ID, (float)ycDataModel.V, telemeteringModel.ResultTime, telemeteringModel.Name, $"{(float)ycDataModel.V} {telemeteringModel.Unit}", telemeteringModel.DispatcherAddress);
// 检查设备是否处于检修状态
equipmentId = telemeteringModel.EquipmentInfoId.Value;
var isInMaintenance = await IsEquipmentInMaintenanceAsync(equipmentId);
@ -641,241 +652,28 @@ namespace YunDa.Server.ISMSTcp.Services
}
}
/// <summary>
/// 🔧 新增:存储遥测数据用于召唤检索
/// </summary>
/// <param name="ycData">遥测数据</param>
private void StoreYCDataForRetrieval(YCData ycData)
{
try
{
if (ycData == null || string.IsNullOrEmpty(ycData.YC_ID))
{
return;
}
var timestampedData = new TimestampedYCData
{
Data = ycData,
ReceivedTime = DateTime.Now
};
// 获取或创建该DataId的存储列表
var dataList = _ycDataStorage.GetOrAdd(ycData.YC_ID, _ => new ConcurrentBag<TimestampedYCData>());
dataList.Add(timestampedData);
_logger.LogDebug("存储遥测数据 - YC_ID: {YcId}, Value: {Value}, Time: {Time}",
ycData.YC_ID, ycData.V, ycData.T);
}
catch (Exception ex)
{
_logger.LogError(ex, "存储遥测数据失败 - YC_ID: {YcId}", ycData?.YC_ID);
}
}
/// <summary>
/// 🔧 新增根据DataId列表获取召唤的遥测数据
/// </summary>
/// <param name="dataIds">DataId列表</param>
/// <returns>遥测数据列表</returns>
public List<YCData> GetYCDataByDataIds(List<string> dataIds)
public async Task<List<ZzDataResultModel>> GetYCDataByDataIds(List<string> ids, int seconds, int timeWindowType, CancellationToken cancellationToken, DateTime now = default)
{
var result = new List<YCData>();
try
if (!_isInitialized)
{
if (dataIds == null || dataIds.Count == 0)
{
_logger.LogWarning("DataId列表为空无法获取遥测数据");
return result;
}
foreach (var dataId in dataIds)
{
if (string.IsNullOrWhiteSpace(dataId))
{
continue;
}
if (_ycDataStorage.TryGetValue(dataId, out var dataList))
{
// 只返回未过期的数据5分钟内
var validData = dataList
.Where(d => (DateTime.Now - d.ReceivedTime).TotalMinutes <= DATA_EXPIRATION_MINUTES)
.Select(d => d.Data)
.ToList();
result.AddRange(validData);
_logger.LogDebug("获取遥测数据 - DataId: {DataId}, Count: {Count}", dataId, validData.Count);
}
else
{
_logger.LogDebug("未找到遥测数据 - DataId: {DataId}", dataId);
}
}
_logger.LogInformation("获取遥测数据完成 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
dataIds.Count, result.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取遥测数据失败");
await InitAsync();
}
var result = await _zzDataCacheContainer.Read(ids, seconds, timeWindowType, cancellationToken, now);
return result;
}
/// <summary>
/// 🔧 新增根据DataId列表获取召唤的遥测数据
/// </summary>
/// <param name="dataIds">DataId列表</param>
/// <returns>遥测数据列表</returns>
public async Task<List<YCResultData>> GetYCDataByDataIds(List<string> dataIds, DateTime cmdTime, int times)
{
var result = new List<YCResultData>();
try
{
if (dataIds == null || dataIds.Count == 0)
{
_logger.LogWarning("DataId列表为空无法获取遥测数据");
return result;
}
foreach (var dataId in dataIds)
{
if (string.IsNullOrWhiteSpace(dataId))
{
continue;
}
if (_ycDataStorage.TryGetValue(dataId, out var dataList))
{
var validData = dataList
.Where(d => d.ReceivedTime >= cmdTime)
.Select(d => d.Data)
.ToList();
if (validData.Count >= times)
{
var item = new YCResultData();
item.Id = dataId;
item.ParseValue(validData[^times..]);
result.Add(item);
}
_logger.LogDebug("获取遥测数据 - DataId: {DataId}, Count: {Count}", dataId, validData.Count);
}
else
{
_logger.LogDebug("未找到遥测数据 - DataId: {DataId}", dataId);
}
}
_logger.LogInformation("获取遥测数据完成 - 请求DataId数: {RequestCount}, 返回数据数: {ResultCount}",
dataIds.Count, result.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取遥测数据失败");
}
if(result.Count == dataIds.Count)
{
string redisKey = $"{_telemeteringModelListRediskey}_Zongzi";
foreach (var item in result)
{
if (!_ycIdToHashKeysMapping.TryGetValue(item.Id, out List<string> haskeys))
{
continue;
}
if(haskeys.Count >= 0)
{
string haskey = haskeys.First();
var telemeteringModel = await _telemeteringModelListRedis.HashSetGetOneAsync(redisKey, haskey);
if (telemeteringModel == null)
{
_logger.LogWarning("Redis中未找到遥测数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
continue; // 继续处理下一个haskey
}
item.Name = telemeteringModel.Name;
item.DispatcherAddress = telemeteringModel.DispatcherAddress;
}
}
}
return result;
}
/// <summary>
/// 🔧 新增:清理过期的遥测数据(定时器回调)
/// </summary>
/// <param name="state">定时器状态</param>
private void CleanupExpiredData(object state)
{
try
{
var now = DateTime.Now;
var expiredKeys = new List<string>();
var totalCleaned = 0;
foreach (var kvp in _ycDataStorage)
{
var dataId = kvp.Key;
var dataList = kvp.Value;
// 过滤出未过期的数据
var validData = dataList
.Where(d => (now - d.ReceivedTime).TotalMinutes <= DATA_EXPIRATION_MINUTES)
.ToList();
var cleanedCount = dataList.Count - validData.Count;
totalCleaned += cleanedCount;
if (validData.Count == 0)
{
// 如果没有有效数据,标记该键为待删除
expiredKeys.Add(dataId);
}
else if (cleanedCount > 0)
{
// 如果有部分数据过期,重新创建数据列表
var newBag = new ConcurrentBag<TimestampedYCData>();
foreach (var data in validData)
{
newBag.Add(data);
}
_ycDataStorage.TryUpdate(dataId, newBag, dataList);
}
}
// 删除没有有效数据的键
foreach (var key in expiredKeys)
{
_ycDataStorage.TryRemove(key, out _);
}
if (totalCleaned > 0 || expiredKeys.Count > 0)
{
_logger.LogInformation("清理过期遥测数据完成 - 清理数据数: {CleanedCount}, 删除键数: {RemovedKeys}",
totalCleaned, expiredKeys.Count);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "清理过期遥测数据时发生错误");
}
}
/// <summary>
/// 🔧 新增:释放资源
@ -883,7 +681,6 @@ namespace YunDa.Server.ISMSTcp.Services
public void Dispose()
{
_batchPushTimer?.Dispose();
_cleanupTimer?.Dispose();
}
}
}

View File

@ -53,9 +53,7 @@ namespace YunDa.Server.ISMSTcp.Services
private readonly object _expressionInitLock = new object();
//遥信数据缓冲队列
private static readonly ConcurrentQueue<(DateTime time, YXCacheData yxData)> _yxDataBuffer = new ConcurrentQueue<(DateTime time, YXCacheData yxData)>();
private const int YXDATA_BUFFER_SECONDS = 60; //只保留60秒的数据
private static int _yxDataRecvCount = 0; //每100次清除一下超时数据用于计数
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(30); //只保留5分钟的数据
@ -145,6 +143,9 @@ namespace YunDa.Server.ISMSTcp.Services
if (_yxIdToHashKeyMapping.TryAdd(model.ismsbaseYXId, haskey))
{
mappingCount++;
//初始化
_zzDataCacheContainer.Write(model.ismsbaseYXId, model.ResultValue, model.ResultTime, model.Name, model.ResultValueStr, model.DispatcherAddress);
}
}
@ -349,12 +350,25 @@ namespace YunDa.Server.ISMSTcp.Services
{
// 解析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;
}
// 使用映射字典查找对应的haskey
if (!_yxIdToHashKeyMapping.TryGetValue(yxDataModel.YX_ID, out string haskey))
{
@ -401,16 +415,7 @@ namespace YunDa.Server.ISMSTcp.Services
_telesignalisationModelListRedis.PublishAsync(channel, telesignalisationModel);
//缓存遥信数据到内存
YXCacheData yXCacheData = new YXCacheData
{
Id = yxDataModel.YX_ID,
Value = yxDataModel.V,
ValueStr = telesignalisationModel.ResultValueStr,
TimeStamp = resultTime.ToString("yyyy-MM-dd HH:mm:ss")
};
SaveYxDataToCache(resultTime, yXCacheData);
_zzDataCacheContainer.Write(yxDataModel.YX_ID, yxDataModel.V, resultTime, telesignalisationModel.Name, telesignalisationModel.ResultValueStr, telesignalisationModel.DispatcherAddress);
// 🔧 优化如果跳过转发仅更新Redis后直接返回
if (skipForwarding)
@ -458,115 +463,40 @@ namespace YunDa.Server.ISMSTcp.Services
return null;
}
}
//缓存遥信数据到内存
private void SaveYxDataToCache(DateTime dateTime, YXCacheData yxData)
{
_yxDataBuffer.Enqueue((dateTime, yxData));
// 每100次接收清理一次过期数据
if (Interlocked.Increment(ref _yxDataRecvCount) % 100 == 0)
{
var now = DateTime.Now;
while (_yxDataBuffer.TryPeek(out var oldest) &&
(now - oldest.time).TotalSeconds > YXDATA_BUFFER_SECONDS)
{
_yxDataBuffer.TryDequeue(out _);
}
}
}
public async Task<List<YXResultData>> GetYXDataByDataIds(List<string> ids, int times, CancellationToken cancellationToken)
public async Task<List<ZzDataResultModel>> GetYXDataByDataIds(List<string> ids, int times, int timeWindowType, CancellationToken cancellationToken)
{
var startTime = DateTime.Now;
var resultDict = new Dictionary<string, List<YXCacheData>>(); // key = YX_ID
// 初始化每个ID的结果列表
foreach (var id in ids)
resultDict[id] = new List<YXCacheData>();
var tcs = new TaskCompletionSource();
// 异步监听任务
_ = Task.Run(async () =>
{
while ((DateTime.Now - startTime).TotalSeconds < times)
{
var matched = _yxDataBuffer
.Where(x => x.time >= startTime && ids.Contains(x.yxData.Id))
.ToList();
// 按 ID 分类
foreach (var id in ids)
{
var datas = matched.Where(x => x.yxData.Id == id)
.Select(x => x.yxData)
.ToList();
lock (resultDict[id])
{
resultDict[id].Clear();
resultDict[id].AddRange(datas);
}
}
await Task.Delay(500, cancellationToken);
}
tcs.TrySetResult();
}, cancellationToken);
await Task.WhenAny(tcs.Task, Task.Delay(times * 1000, cancellationToken));
// 构建最终返回数据
var finalResult = new List<YXResultData>();
foreach (var id in ids)
{
var yxResult = new YXResultData
{
Id = id,
};
lock (resultDict[id])
{
yxResult.ParseValue(resultDict[id]);
}
finalResult.Add(yxResult);
}
//设置Name和DispatcherAddress并且检查数据是否为空如果为空从Redis里取最新值
//检查数据是否为空如果为空从Redis里取最新值
if(!_isInitialized)
{
await InitAsync();
}
var finalResult = await _zzDataCacheContainer.Read(ids, times, timeWindowType, cancellationToken);
string redisKey = $"{_telesignalisationModelListRediskey}_Zongzi";
foreach (var item in finalResult)
{
if (!_yxIdToHashKeyMapping.TryGetValue(item.Id, out string haskey))
if(item.TimeStamp == DateTime.MinValue)
{
continue;
}
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
}
var telesignalisationModel = await _telesignalisationModelListRedis.HashSetGetOneAsync(redisKey, haskey);
if (telesignalisationModel == null)
{
_logger.LogWarning("Redis中未找到遥信数据: RedisKey={RedisKey}, HasKey={HasKey}", redisKey, haskey);
continue; // 继续处理下一个haskey
}
item.Name = telesignalisationModel.Name;
item.DispatcherAddress = telesignalisationModel.DispatcherAddress;
if (item.Data.Count == 0)
{
item.Value = telesignalisationModel.ResultValue;
item.ValueStr = telesignalisationModel.ResultValueStr;
item.TimeStamp = telesignalisationModel.ResultTime.ToString("yyyy-MM-dd HH:mm:ss");
item.TimeStamp = telesignalisationModel.ResultTime;
}
}
return finalResult;

View File

@ -287,11 +287,12 @@ namespace YunDa.Server.ISMSTcp.Services
/******************配置信息*****************/
//更新孪生体与遥数据绑定关系
//更新孪生体与遥数据绑定关系
private async Task UpdateDeviceBindingConfig()
{
try
{
//db ex_younuo_twin_data_binding
List<ThingDeviceBindingModel> list = await _webApiRequest.GetThingDeviceBindingConfigAsync();
if (list != null)
@ -432,7 +433,7 @@ namespace YunDa.Server.ISMSTcp.Services
/******************设备状态(遥信)*****************/
//遥信状态有变化时,更新设备状态
private async Task UpdateDeviceChangeStatus(List<YXData> yXDatas)
private async Task UpdateDeviceChangeStatus(List<TelesignalisationModel> yXDatas)
{
if (yXDatas == null)
return;
@ -447,14 +448,17 @@ namespace YunDa.Server.ISMSTcp.Services
{
foreach (var item in yXDatas)
{
if (!string.IsNullOrWhiteSpace(item.YX_ID))
if (!string.IsNullOrWhiteSpace(item.ismsbaseYXId))
{
if (_deviceBindingConfigs.TryGetValue(item.YX_ID, out var bindingItem))
if (_deviceBindingConfigs.TryGetValue(item.ismsbaseYXId, out var bindingItem))
{
if(bindingItem.IsActive)
{
var metric = bindingItem.MetricList.Find(e => e.Val == item.T);
var val = bindingItem.ValList.Find(e => e.Val == item.T);
var yxValue = item.ResultValue.ToString();
var metric = bindingItem.MetricList.Find(e => e.Val == yxValue);
var val = bindingItem.ValList.Find(e => e.Val == yxValue);
if (metric != null && val != null)
{
@ -501,6 +505,8 @@ namespace YunDa.Server.ISMSTcp.Services
}
//实时更新遥信全体设备状态
public async Task UpdateYXDeviceAllStatus()
{
@ -526,10 +532,10 @@ namespace YunDa.Server.ISMSTcp.Services
{
if(bindingItem.IsActive)
{
int yxValue = item.ResultValue;
var yxValue = item.ResultValue.ToString();
var metric = bindingItem.MetricList.Find(e => e.Val == yxValue.ToString());
var val = bindingItem.ValList.Find(e => e.Val == yxValue.ToString());
var metric = bindingItem.MetricList.Find(e => e.Val == yxValue);
var val = bindingItem.ValList.Find(e => e.Val == yxValue);
if (metric != null && val != null)
{
@ -539,7 +545,7 @@ namespace YunDa.Server.ISMSTcp.Services
}
else
{
int k = 0;
//int k = 0;
}
}
}
@ -598,10 +604,10 @@ namespace YunDa.Server.ISMSTcp.Services
}
//遥信数据有变化时,将状态和性能数据推送到孪生体
public async Task UpdateThingYXStatus(List<YXData> yXDatas, List<TelesignalisationModel> telesignalisationModels)
public async Task UpdateThingYXStatus(List<TelesignalisationModel> telesignalisationModels)
{
//更新孪生体
await UpdateDeviceChangeStatus(yXDatas);
await UpdateDeviceChangeStatus(telesignalisationModels);
//推送性能数据
await UpdateYXSimDatasStatus(telesignalisationModels);
@ -929,20 +935,9 @@ namespace YunDa.Server.ISMSTcp.Services
}
// 根据虚点值处理报警
// Value = 0 表示正常状态Value = 1 表示故障状态
bool isAlarm = false;
if (Math.Abs(value - 1) < 0.0001) // 故障状态
{
isAlarm = true;
}
else if (Math.Abs(value) < 0.0001) // 正常状态
{
isAlarm = false;
}
else
{
continue;
}
//Boolean型变量的值(真,假,不定)2018-04-26从uPro设备
//TVariantBoolValue = (vbTrue = 0vbFalse = 1 vbUncertain = 2):
bool isAlarm = Math.Abs(value) < 0.0001;
TingAlarmPushDataModel alertData = new TingAlarmPushDataModel();
@ -952,7 +947,7 @@ namespace YunDa.Server.ISMSTcp.Services
isAlarm,
$"{configItem.P1DeviceName}{configItem.P2DeviceName}",
"光纤断线",
item.T
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
);
alertDatas.Add(alertData);
@ -1109,6 +1104,7 @@ namespace YunDa.Server.ISMSTcp.Services
configs = _opticalCableConfig.ToArray();
}
//虚点
if (virtualTerminalDatas != null)
{
foreach (var item in virtualTerminalDatas)
@ -1126,20 +1122,10 @@ namespace YunDa.Server.ISMSTcp.Services
}
// 根据虚点值处理报警
// Value = 0 表示正常状态Value = 1 表示故障状态
bool isAlarm = false;
if (Math.Abs(value - 1) < 0.0001) // 故障状态
{
isAlarm = true;
}
else if (Math.Abs(value) < 0.0001) // 正常状态
{
isAlarm = false;
}
else
{
continue;
}
// 根据虚点值处理报警
//Boolean型变量的值(真,假,不定)2018-04-26从uPro设备
//TVariantBoolValue = (vbTrue = 0vbFalse = 1 vbUncertain = 2):
bool isAlarm = Math.Abs(value) < 0.0001;
TingAlarmPushDataModel alertData = new TingAlarmPushDataModel();
@ -1149,7 +1135,7 @@ namespace YunDa.Server.ISMSTcp.Services
isAlarm,
$"{configItem.P1CabinetName}{configItem.P2CabinetName}",
"光缆断线",
item.T
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
);
alertDatas.Add(alertData);
@ -1160,7 +1146,6 @@ namespace YunDa.Server.ISMSTcp.Services
}
if (telesignalisationModels != null)
{
foreach (var item in telesignalisationModels)
@ -1226,6 +1211,8 @@ namespace YunDa.Server.ISMSTcp.Services
private string _apiToken = string.Empty;
private DateTime _apiTokenTime = DateTime.MinValue;
private readonly SemaphoreSlim _apiTokenSemaphore = new SemaphoreSlim(1, 1);
public ThingApiService(
ILogger<ThingApiService> logger)
@ -1242,30 +1229,57 @@ namespace YunDa.Server.ISMSTcp.Services
//获取Token
private async Task<string> GetTokenAsync()
{
await _apiTokenSemaphore.WaitAsync();
string src = string.Empty;
try
{
if (string.IsNullOrWhiteSpace(_apiToken) || (DateTime.Now - _apiTokenTime).TotalHours > 6)
{//每6小时更新一次token
var http = new ThingWebClientHelper();
int retryCnt = 3;
while (retryCnt-- > 0)
{
if (string.IsNullOrWhiteSpace(_apiToken) || (DateTime.Now - _apiTokenTime).TotalHours > 6)
{//每6小时更新一次token
var http = new ThingWebClientHelper();
var json = await http.PostJsonAsync($"{_apiServerUrl}:1662/thing/provider/rest/getToken", new { loginCode = _loginCode, loginKey = _loginKey });
var json = await http.PostJsonAsync($"{_apiServerUrl}:1662/thing/provider/rest/getToken", new { loginCode = _loginCode, loginKey = _loginKey });
JObject obj = JObject.Parse(json);
src = json ?? "";
_apiToken = obj["data"]?["token"]?.ToString();
JObject obj = JObject.Parse(json);
if(!string.IsNullOrWhiteSpace(_apiToken))
_apiTokenTime = DateTime.Now;
_apiToken = obj["data"]?["token"]?.ToString();
if (!string.IsNullOrWhiteSpace(_apiToken))
{
_apiTokenTime = DateTime.Now;
System.Console.WriteLine("ThingApiService - 获取token成功");
}
}
if (string.IsNullOrWhiteSpace(_apiToken))
{
await Task.Delay(6000); //此接 口每5秒只可以调用1次
}
else
{
break;
}
}
}
catch (Exception ex)
{
}
finally
{
_apiTokenSemaphore.Release();
}
if(string.IsNullOrWhiteSpace(_apiToken))
_logger.LogError("ThingApiService - 获取token失败");
if (string.IsNullOrWhiteSpace(_apiToken))
_logger.LogError("ThingApiService - 连续3次获取token失败,返回内容:" + src);
return _apiToken;
}

View File

@ -1,13 +1,17 @@
using System;
using System.Threading.Tasks;
using Microsoft.ClearScript.JavaScript;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using YunDa.Server.ISMSTcp.Models;
using YunDa.Server.ISMSTcp.Domain;
using YunDa.SOMS.DataTransferObject.ExternalEntities.BeijingYounuo;
using Org.BouncyCastle.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using YunDa.Server.ISMSTcp.Domain;
using YunDa.Server.ISMSTcp.Models;
using YunDa.SOMS.DataTransferObject.ExternalEntities.BeijingYounuo;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace YunDa.Server.ISMSTcp.Services
{
@ -19,6 +23,7 @@ namespace YunDa.Server.ISMSTcp.Services
{
private readonly ILogger<VirtualTerminalHandler> _logger;
private readonly WebApiRequest _webApiRequest;
private readonly ZzDataCacheContainerInit _zzDataCacheContainerInit;
// 🔧 新增:光纤虚点处理相关字段
private readonly ConcurrentDictionary<string, OpticalFiberDto> _virtualPointToOpticalFiberMapping = new ConcurrentDictionary<string, OpticalFiberDto>();
@ -27,10 +32,17 @@ namespace YunDa.Server.ISMSTcp.Services
public volatile bool _isOpticalFiberMappingInitialized = false;
private readonly object _opticalFiberInitLock = new object();
public VirtualTerminalHandler(ILogger<VirtualTerminalHandler> logger, WebApiRequest webApiRequest)
//数据缓冲队列
private ZzDataCacheContainer _zzDataCacheContainer = new ZzDataCacheContainer(5);
public VirtualTerminalHandler(ILogger<VirtualTerminalHandler> logger, WebApiRequest webApiRequest, ZzDataCacheContainerInit zzDataCacheContainerInit)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_webApiRequest = webApiRequest ?? throw new ArgumentNullException(nameof(webApiRequest));
_zzDataCacheContainerInit = zzDataCacheContainerInit;
_zzDataCacheContainerInit.InitVariantBaseinfo(_zzDataCacheContainer);
}
/// <summary>
@ -172,6 +184,9 @@ namespace YunDa.Server.ISMSTcp.Services
// 根据UA_ID执行不同的处理逻辑
await ProcessByUAId(virtualTerminalData);
//存入缓存
SaveDataToCache(virtualTerminalData.VA_ID, value, timestamp);
_logger.LogInformation("虚端子数据处理完成:{UA_ID}", virtualTerminalData.VA_ID);
}
catch (Exception ex)
@ -229,12 +244,13 @@ namespace YunDa.Server.ISMSTcp.Services
_logger.LogDebug("处理光纤虚点: {VA_ID}, 值: {Value}, 光纤: {TwinId}", data.VA_ID, value, opticalFiber.TwinId);
// 根据虚点值处理报警
// Value = 0 表示正常状态Value = 1 表示故障状态
if (Math.Abs(value - 1) < 0.0001) // 故障状态
//Boolean型变量的值(真,假,不定)2018-04-26从uPro设备
//TVariantBoolValue = (vbTrue = 0vbFalse = 1 vbUncertain = 2):
if (Math.Abs(value) < 0.0001) // 故障状态
{
await PushOpticalFiberAlarmAsync(opticalFiber, data);
}
else if (Math.Abs(value) < 0.0001) // 正常状态
else if (Math.Abs(value - 1) < 0.0001) // 正常状态
{
await DeleteOpticalFiberAlarmAsync(opticalFiber, data);
}
@ -352,5 +368,38 @@ namespace YunDa.Server.ISMSTcp.Services
opticalFiber.TwinId, opticalFiber.VirtualPointCode);
}
}
//缓存遥信数据到内存
private void SaveDataToCache(string id, double value, DateTime timestamp)
{
try
{
_zzDataCacheContainer.Write(id, value, timestamp, "");
}
catch (Exception ex)
{
_logger.LogError(ex, "VirtualTerminalHandler: SaveDataToCache 出错");
}
}
//查询数据
public async Task<List<ZzDataResultModel>> GetDataByIds(List<string> ids, int seconds, int timeWindowType, CancellationToken cancellationToken)
{
try
{
return await _zzDataCacheContainer.Read(ids, seconds, timeWindowType, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "VirtualTerminalHandler: GetDataByIds 出错");
return new List<ZzDataResultModel>();
}
}
}
}

View File

@ -0,0 +1,303 @@
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using YunDa.Server.ISMSTcp.Domain;
using YunDa.Server.ISMSTcp.Interfaces;
namespace YunDa.Server.ISMSTcp.Services
{
public class ZzDataPoint
{
public DateTime TimeStamp { get; set; }
public double Value { get; }
public string ValueStr { get; }
public ZzDataPoint(DateTime time, double value, string valueStr)
{
TimeStamp = time;
Value = value;
ValueStr = valueStr;
}
}
public class ZzData
{
public TimeSpan Retention { get; set; }
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int? DispatcherAddress { get; set; } = null;
/////////////////////////////////////////////////////////////////////////////////////
public double Value { get; set; }
public string ValueStr { get; set; } = string.Empty;
public DateTime TimeStamp { get; set; }
/////////////////////////////////////////////////////////////////////////////////////
private readonly ConcurrentQueue<(DateTime time, ZzDataPoint point)> _queue = new ConcurrentQueue<(DateTime time, ZzDataPoint point)>();
public ZzData(int minutes, string id, string name, int dispatcherAddress)
{
Retention = TimeSpan.FromMinutes(minutes);
Id = id;
Name = name;
DispatcherAddress = dispatcherAddress;
}
public void AddData(ZzDataPoint point)
{
DateTime now = DateTime.Now;
point.TimeStamp = now; //2025-11-18 用当前时间(临时修改)
_queue.Enqueue((now, point));
TimeStamp = point.TimeStamp;
Value = point.Value;
ValueStr = point.ValueStr;
CleanupOldData();
}
private void CleanupOldData()
{
DateTime limit = DateTime.Now - Retention;
while (_queue.TryPeek(out var dp) && dp.time < limit)
{
_queue.TryDequeue(out _);
}
}
public List<ZzDataPoint> GetData(DateTime start, DateTime end)
{
return _queue.Where(dp => dp.point.TimeStamp >= start && dp.point.TimeStamp <= end)
.Select(x => x.point)
.ToList();
}
}
public class ZzDataRequestModel
{
public List<string> Id { get; set; }
public int Times { get; set; }
public int TimeWindowType { get; set; }
}
public class ZzDataResultModel
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int? DispatcherAddress { get; set; } = null;
public double Value { get; set; }
public string ValueStr { get; set; } = string.Empty;
public DateTime TimeStamp { get; set; } = DateTime.MinValue;
public List<ZzDataPoint> Data { get; set; } = new List<ZzDataPoint>();
public ZzDataResultModel()
{
}
public ZzDataResultModel(ZzData data)
{
Id = data.Id;
Name = data.Name;
Value = data.Value;
ValueStr = data.ValueStr;
TimeStamp = data.TimeStamp;
DispatcherAddress = data.DispatcherAddress;
}
}
public class ZzDataHistoryModel
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
}
public class ZzDataCacheContainer
{
private readonly ConcurrentDictionary<string, ZzData> _datas = new ConcurrentDictionary<string, ZzData>();
private readonly int _cleanupMinutes = 5;
public ZzDataCacheContainer(int cleanupMinutes)
{
_cleanupMinutes = cleanupMinutes;
}
public void Write(string id, double val, DateTime time, string name)
{
var data = _datas.GetOrAdd(id, _ => new ZzData(_cleanupMinutes, id, name, 0));
data.AddData(new ZzDataPoint(time, val, ""));
}
public void Write(string id, double val, DateTime time, string name, string valStr)
{
var data = _datas.GetOrAdd(id, _ => new ZzData(_cleanupMinutes, id, name, 0));
data.AddData(new ZzDataPoint(time, val, valStr));
}
public void Write(string id, double val, DateTime time, string name, string valStr, int dispatcherAddress)
{
//if (id == "YCB001101001")
//{
// int kk = 0;
//}
var data = _datas.GetOrAdd(id, _ => new ZzData(_cleanupMinutes, id, name, dispatcherAddress));
data.AddData(new ZzDataPoint(time, val, valStr));
}
public Dictionary<string, ZzDataResultModel> Read(List<string> ids, DateTime start, DateTime end)
{
var result = new Dictionary<string, ZzDataResultModel>();
foreach (var id in ids)
{
//if (id == "YCB001101001")
//{
// int kk = 0;
//}
if (_datas.TryGetValue(id, out var channel))
{
ZzDataResultModel data = new ZzDataResultModel(channel);
data.Data = channel.GetData(start, end);
if(data.Data.Count == 0 && data.TimeStamp != DateTime.MinValue)
{
ZzDataPoint zzDataPoint = new ZzDataPoint(DateTime.Now, data.Value, data.ValueStr);
data.Data.Add(zzDataPoint);
}
result[id] = data;
}
}
return result;
}
public async Task<List<ZzDataResultModel>> Read(List<string> ids, int seconds, int timeWindowType, CancellationToken cancellationToken, DateTime now = default)
{
try
{
if(now == default)
now = DateTime.Now;
//寻找匹配的值
Dictionary<string, ZzDataResultModel> matched1 = new Dictionary<string, ZzDataResultModel>();
Dictionary<string, ZzDataResultModel> matched2 = new Dictionary<string, ZzDataResultModel>();
if (timeWindowType == 0 || timeWindowType == 2)
{
matched1 = Read(ids, now.AddSeconds(-seconds), now);
}
if (timeWindowType == 1 || timeWindowType == 2)
{
int span = seconds*1000 - (int)(DateTime.Now - now).TotalMilliseconds;
if(span > 0)
await Task.Delay(span, cancellationToken);
matched2 = Read(ids, now, DateTime.Now);
}
foreach (var kv in matched2)
{
if (!matched1.TryAdd(kv.Key, kv.Value))
{
matched1[kv.Key].Data.AddRange(kv.Value.Data);
}
}
return matched1.Values.ToList();
}
catch (Exception ex)
{
return new List<ZzDataResultModel>();
}
}
}
public class ZzDataCacheContainerInit
{
private readonly ILogger<SecondaryCircuitInspectionPlanService> _logger;
private readonly WebApiRequest _webApiRequest;
private readonly ZzTcpService _zTcpService = null;
public ZzDataCacheContainerInit(ILogger<SecondaryCircuitInspectionPlanService> logger, WebApiRequest webApiRequest, ZzTcpService zTcpService)
{
_logger = logger;
_webApiRequest = webApiRequest;
_zTcpService = zTcpService;
}
public void InitTcpService(ITcpClient tcpClient)
{
_zTcpService.Init(tcpClient);
}
//初始化虚点信息
public async Task InitVariantBaseinfo(ZzDataCacheContainer cache)
{
//从数据库获取
var list = await _webApiRequest.GetVariantBaseinfoAsync();
if (list != null)
{
foreach (var item in list)
{
cache.Write(item.Id, 2, DateTime.Now, item.Name, "不定");
}
}
//发送命令从综自获取
if(_zTcpService != null)
{
string cmd = "CallVAByStat|B001";
for (int i = 0; i < 3; i++)
{
var sendResult = await _zTcpService.SendTcpMessageAsync(cmd, CancellationToken.None);
if (sendResult.Success)
{
break;
}
await Task.Delay(1000);
}
}
}
//初始化网关信息
public async Task InitGateWayBaseInfo(ZzDataCacheContainer cache)
{
var list = await _webApiRequest.GetGateWayBaseInfoAsync();
if (list != null)
{
foreach (var item in list)
{
cache.Write(item.Id, 0, DateTime.Now, item.Name, "0.00 %");
}
}
}
}
}

View File

@ -0,0 +1,69 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using YunDa.Server.ISMSTcp.Controllers;
using YunDa.Server.ISMSTcp.Interfaces;
using YunDa.SOMS.ExternalInteraction.DataTransferObject.InspectionEquipment;
namespace YunDa.Server.ISMSTcp.Services
{
public class ZzTcpService
{
private readonly ILogger<QueryController> _logger;
private ITcpClient _tcpClient = null;
public ZzTcpService(ILogger<QueryController> logger)
{
_logger = logger;
}
public void Init(ITcpClient tcpClient)
{
_tcpClient = tcpClient;
}
public async Task<(bool Success, string ErrorMessage)> SendTcpMessageAsync(string message, CancellationToken cancellationToken)
{
try
{
// 使用QueryService的TCP客户端发送消息
// 注意这里直接通过ITcpClient发送不需要等待响应
var tcpClient = GetTcpClientFromQueryService();
if (tcpClient == null)
{
return (false, "TCP客户端未初始化");
}
if (!tcpClient.IsConnected)
{
return (false, "TCP连接未建立");
}
var sendResult = await tcpClient.SendMessageAsync(message, cancellationToken);
if (!sendResult)
{
return (false, "TCP消息发送失败");
}
return (true, string.Empty);
}
catch (Exception ex)
{
_logger.LogError(ex, "发送TCP消息时发生异常");
return (false, ex.Message);
}
}
/// <summary>
/// 从QueryService获取TCP客户端
/// </summary>
/// <returns>TCP客户端实例</returns>
private ITcpClient GetTcpClientFromQueryService()
{
return _tcpClient;
}
}
}

View File

@ -39,7 +39,10 @@ namespace YunDa.Server.ISMSTcp
// 添加控制器
services.AddControllers()
.AddNewtonsoftJson(); // 使用Newtonsoft.Json
.AddNewtonsoftJson(options => // 使用Newtonsoft.Json
{
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
// 添加CORS支持
services.AddCors(options =>