2025-09-05 15:59:14 +08:00
|
|
|
|
using System.Collections.Concurrent;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
using System.Diagnostics;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
using DMS.Application.DTOs;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
using DMS.Application.Events;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
using DMS.Application.Interfaces;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
using DMS.Application.Models;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
using DMS.Core.Enums;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
using DMS.Core.Events;
|
|
|
|
|
|
using DMS.Infrastructure.Interfaces.Services;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
using DateTime = System.DateTime;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
|
2025-09-16 14:42:23 +08:00
|
|
|
|
namespace DMS.Infrastructure.Services.S7;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 优化的S7后台服务,继承自BackgroundService,用于在后台高效地轮询S7 PLC设备数据。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class OptimizedS7BackgroundService : BackgroundService
|
|
|
|
|
|
{
|
2025-09-09 13:35:16 +08:00
|
|
|
|
private readonly IAppDataCenterService _appDataCenterService;
|
2025-09-09 15:28:07 +08:00
|
|
|
|
private readonly IAppDataStorageService _appDataStorageService;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
private readonly IEventService _eventService;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
private readonly IDataProcessingService _dataProcessingService;
|
|
|
|
|
|
private readonly IS7ServiceManager _s7ServiceManager;
|
|
|
|
|
|
private readonly ILogger<OptimizedS7BackgroundService> _logger;
|
|
|
|
|
|
private readonly SemaphoreSlim _reloadSemaphore = new SemaphoreSlim(0);
|
|
|
|
|
|
|
|
|
|
|
|
// S7轮询一遍后的等待时间
|
|
|
|
|
|
private readonly int _s7PollOnceSleepTimeMs = 50;
|
|
|
|
|
|
|
2025-09-05 20:24:27 +08:00
|
|
|
|
// 存储每个设备的变量按轮询间隔分组
|
|
|
|
|
|
private readonly ConcurrentDictionary<int, Dictionary<int, List<VariableDto>>> _variablesByPollingInterval = new();
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构造函数,注入数据服务和数据处理服务。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public OptimizedS7BackgroundService(
|
2025-09-09 13:35:16 +08:00
|
|
|
|
IAppDataCenterService appDataCenterService,
|
2025-09-09 15:28:07 +08:00
|
|
|
|
IAppDataStorageService appDataStorageService,
|
2025-09-16 14:42:23 +08:00
|
|
|
|
IEventService eventService,
|
2025-09-05 15:59:14 +08:00
|
|
|
|
IDataProcessingService dataProcessingService,
|
|
|
|
|
|
IS7ServiceManager s7ServiceManager,
|
|
|
|
|
|
ILogger<OptimizedS7BackgroundService> logger)
|
|
|
|
|
|
{
|
2025-09-09 13:35:16 +08:00
|
|
|
|
_appDataCenterService = appDataCenterService;
|
2025-09-09 15:28:07 +08:00
|
|
|
|
_appDataStorageService = appDataStorageService;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
_eventService = eventService;
|
2025-09-05 15:59:14 +08:00
|
|
|
|
_dataProcessingService = dataProcessingService;
|
|
|
|
|
|
_s7ServiceManager = s7ServiceManager;
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
2025-09-09 15:28:07 +08:00
|
|
|
|
_appDataCenterService.DataLoaderService.OnLoadDataCompleted += OnLoadDataCompleted;
|
2025-09-16 14:42:23 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
}
|
2025-09-16 14:42:23 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
|
2025-09-06 15:19:04 +08:00
|
|
|
|
private void OnLoadDataCompleted(object? sender, DataLoadCompletedEventArgs e)
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
|
|
|
|
|
_reloadSemaphore.Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("优化的S7后台服务正在启动。");
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _reloadSemaphore.WaitAsync(stoppingToken); // Wait for a reload signal
|
|
|
|
|
|
|
|
|
|
|
|
if (stoppingToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-09 15:28:07 +08:00
|
|
|
|
if (_appDataStorageService.Devices.IsEmpty)
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("没有可用的S7设备,等待设备列表更新...");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var isLoaded = LoadVariables();
|
|
|
|
|
|
if (!isLoaded)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("加载变量过程中发生了错误,停止后面的操作。");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await ConnectS7ServiceAsync(stoppingToken);
|
|
|
|
|
|
_logger.LogInformation("优化的S7后台服务已启动。");
|
|
|
|
|
|
|
|
|
|
|
|
// 持续轮询,直到取消请求或需要重新加载
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested && _reloadSemaphore.CurrentCount == 0)
|
|
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
await PollS7VariablesAsync(stoppingToken);
|
2025-09-05 15:59:14 +08:00
|
|
|
|
await Task.Delay(_s7PollOnceSleepTimeMs, stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("优化的S7后台服务已停止。");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, $"优化的S7后台服务运行中发生了错误:{e.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
await DisconnectAllS7SessionsAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 从数据库加载所有活动的 S7 变量,并按轮询级别进行分组。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool LoadVariables()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-09-05 20:24:27 +08:00
|
|
|
|
_variablesByPollingInterval.Clear();
|
2025-09-05 15:59:14 +08:00
|
|
|
|
_logger.LogInformation("开始加载S7变量....");
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-09 15:28:07 +08:00
|
|
|
|
var s7Devices = _appDataStorageService
|
|
|
|
|
|
.Devices.Values.Where(d => d.Protocol == ProtocolType.S7 && d.IsActive == true)
|
|
|
|
|
|
.ToList();
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
foreach (var s7Device in s7Devices)
|
|
|
|
|
|
{
|
|
|
|
|
|
_s7ServiceManager.AddDevice(s7Device);
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
// 查找设备中所有要轮询的变量
|
2025-09-15 20:54:32 +08:00
|
|
|
|
var variables = new List<VariableDto>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var variableTable in s7Device.VariableTables)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (variableTable.IsActive && variableTable.Protocol == ProtocolType.S7)
|
|
|
|
|
|
{
|
|
|
|
|
|
variables.AddRange(variableTable.Variables.Where(v => v.IsActive));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
_s7ServiceManager.UpdateVariables(s7Device.Id, variables);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation($"S7 变量加载成功,共加载S7设备:{s7Devices.Count}个");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, $"加载S7变量的过程中发生了错误:{e.Message}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连接到 S7 服务器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task ConnectS7ServiceAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
2025-09-09 15:28:07 +08:00
|
|
|
|
var s7Devices = _appDataStorageService
|
|
|
|
|
|
.Devices.Values.Where(d => d.Protocol == ProtocolType.S7 && d.IsActive == true)
|
|
|
|
|
|
.ToList();
|
2025-09-05 15:59:14 +08:00
|
|
|
|
|
2025-09-15 20:54:32 +08:00
|
|
|
|
var deviceIds = s7Devices.Select(d => d.Id)
|
|
|
|
|
|
.ToList();
|
2025-09-05 15:59:14 +08:00
|
|
|
|
await _s7ServiceManager.ConnectDevicesAsync(deviceIds, stoppingToken);
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
_logger.LogInformation("已连接所有S7设备");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-09-05 20:24:27 +08:00
|
|
|
|
/// 按轮询间隔轮询S7变量
|
2025-09-05 15:59:14 +08:00
|
|
|
|
/// </summary>
|
2025-09-15 20:54:32 +08:00
|
|
|
|
private async Task PollS7VariablesAsync(CancellationToken stoppingToken)
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
var s7DeviceContexts = _s7ServiceManager.GetAllDeviceContexts();
|
|
|
|
|
|
foreach (var context in s7DeviceContexts)
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
if (stoppingToken.IsCancellationRequested) break;
|
|
|
|
|
|
|
|
|
|
|
|
// 收集该设备所有需要轮询的变量
|
|
|
|
|
|
var variablesToPoll = context.Variables.Values.ToList();
|
2025-09-05 15:59:14 +08:00
|
|
|
|
|
2025-09-15 20:54:32 +08:00
|
|
|
|
if (variablesToPoll.Any())
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
await PollVariablesForDeviceAsync(context, variablesToPoll, stoppingToken);
|
2025-09-05 15:59:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-05 20:24:27 +08:00
|
|
|
|
_logger.LogError(ex, $"按轮询间隔轮询S7变量时发生错误:{ex.Message}");
|
2025-09-05 15:59:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 轮询设备的变量
|
|
|
|
|
|
/// </summary>
|
2025-09-15 20:54:32 +08:00
|
|
|
|
private async Task PollVariablesForDeviceAsync(S7DeviceContext context, List<VariableDto> variables,
|
|
|
|
|
|
CancellationToken stoppingToken)
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
if (!_appDataStorageService.Devices.TryGetValue(context.Device.Id, out var device))
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
_logger.LogWarning($"轮询时没有找到设备ID:{context.Device.Id}");
|
2025-09-05 15:59:14 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
|
|
|
|
|
var s7Service = context.S7Service;
|
|
|
|
|
|
if (s7Service == null || !s7Service.IsConnected)
|
2025-09-05 15:59:14 +08:00
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
_logger.LogWarning($"轮询时设备 {device.Name} 没有连接或服务不可用");
|
2025-09-05 15:59:14 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var stopwatch = Stopwatch.StartNew();
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
// 按地址分组变量以进行批量读取
|
2025-09-15 20:54:32 +08:00
|
|
|
|
var addresses = variables.Where(v=>(DateTime.Now-v.UpdatedAt)>=TimeSpan.FromMilliseconds(v.PollingInterval)).Select(v => v.S7Address)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
if (!addresses.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量读取变量值
|
|
|
|
|
|
var readResults = await s7Service.ReadVariablesAsync(addresses);
|
|
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
stopwatch.Stop();
|
|
|
|
|
|
_logger.LogDebug($"设备 {device.Name} 轮询 {variables.Count} 个变量耗时 {stopwatch.ElapsedMilliseconds} ms");
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
// 更新变量值并推送到处理队列
|
|
|
|
|
|
foreach (var variable in variables)
|
|
|
|
|
|
{
|
2025-09-15 20:54:32 +08:00
|
|
|
|
if (readResults.TryGetValue(variable.S7Address, out var value))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 将更新后的数据推入处理队列。
|
2025-10-02 17:35:35 +08:00
|
|
|
|
await _dataProcessingService.EnqueueAsync(new VariableContext(variable, value?.ToString()));
|
2025-09-15 20:54:32 +08:00
|
|
|
|
}
|
2025-09-16 14:42:23 +08:00
|
|
|
|
// else
|
|
|
|
|
|
// {
|
|
|
|
|
|
// _logger.LogWarning($"未能从设备 {device.Name} 读取变量 {variable.S7Address} 的值");
|
|
|
|
|
|
// }
|
2025-09-05 15:59:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, $"轮询设备 {device.Name} 的变量时发生错误:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 断开所有 S7 会话。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task DisconnectAllS7SessionsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("正在断开所有 S7 会话...");
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
var deviceIds = _s7ServiceManager.GetMonitoredDeviceIds();
|
|
|
|
|
|
await _s7ServiceManager.DisconnectDevicesAsync(deviceIds);
|
2025-09-15 20:54:32 +08:00
|
|
|
|
|
2025-09-05 15:59:14 +08:00
|
|
|
|
_logger.LogInformation("已断开所有 S7 会话");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|