完成S7变量启用和停用更新
This commit is contained in:
285
DMS.Infrastructure/Services/S7/S7DeviceAgent.cs
Normal file
285
DMS.Infrastructure/Services/S7/S7DeviceAgent.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using DMS.Application.DTOs;
|
||||
using DMS.Application.Models;
|
||||
using DMS.Core.Models;
|
||||
using DMS.Infrastructure.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using S7.Net;
|
||||
using S7.Net.Types;
|
||||
using CpuType = DMS.Core.Enums.CpuType;
|
||||
using DateTime = System.DateTime;
|
||||
|
||||
namespace DMS.Infrastructure.Services.S7
|
||||
{
|
||||
/// <summary>
|
||||
/// S7设备代理类,专门负责与单个S7 PLC进行所有通信
|
||||
/// </summary>
|
||||
public class S7DeviceAgent : IAsyncDisposable
|
||||
{
|
||||
private readonly Device _deviceConfig;
|
||||
private readonly IChannelBus _channelBus;
|
||||
private readonly IMessenger _messenger;
|
||||
private readonly ILogger<S7DeviceAgent> _logger;
|
||||
private Plc _plc;
|
||||
private bool _isConnected;
|
||||
private readonly Dictionary<int, List<Variable>> _variablesByPollingInterval;
|
||||
private readonly Dictionary<int, DateTime> _lastPollTimes;
|
||||
|
||||
public S7DeviceAgent(Device device, IChannelBus channelBus, IMessenger messenger, ILogger<S7DeviceAgent> logger)
|
||||
{
|
||||
_deviceConfig = device ?? throw new ArgumentNullException(nameof(device));
|
||||
_channelBus = channelBus ?? throw new ArgumentNullException(nameof(channelBus));
|
||||
_messenger = messenger ?? throw new ArgumentNullException(nameof(messenger));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_variablesByPollingInterval = new Dictionary<int, List<Variable>>();
|
||||
_lastPollTimes = new Dictionary<int, DateTime>();
|
||||
|
||||
InitializePlc();
|
||||
}
|
||||
|
||||
private void InitializePlc()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cpuType = ConvertCpuType(_deviceConfig.CpuType);
|
||||
_plc = new Plc(cpuType, _deviceConfig.IpAddress, (short)_deviceConfig.Port, _deviceConfig.Rack, _deviceConfig.Slot);
|
||||
_logger.LogInformation($"S7DeviceAgent: 初始化PLC客户端 {_deviceConfig.Name} ({_deviceConfig.IpAddress})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"S7DeviceAgent: 初始化PLC客户端 {_deviceConfig.Name} ({_deviceConfig.IpAddress}) 失败。");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private global::S7.Net.CpuType ConvertCpuType(CpuType cpuType)
|
||||
{
|
||||
return cpuType switch
|
||||
{
|
||||
CpuType.S7200 => global::S7.Net.CpuType.S7200,
|
||||
CpuType.S7300 => global::S7.Net.CpuType.S7300,
|
||||
CpuType.S7400 => global::S7.Net.CpuType.S7400,
|
||||
CpuType.S71200 => global::S7.Net.CpuType.S71200,
|
||||
CpuType.S71500 => global::S7.Net.CpuType.S71500,
|
||||
_ => global::S7.Net.CpuType.S71200
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到PLC
|
||||
/// </summary>
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_plc != null && !_isConnected)
|
||||
{
|
||||
await _plc.OpenAsync();
|
||||
_isConnected = _plc.IsConnected;
|
||||
|
||||
if (_isConnected)
|
||||
{
|
||||
_logger.LogInformation($"S7DeviceAgent: 成功连接到设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress})");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"S7DeviceAgent: 无法连接到设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress})");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"S7DeviceAgent: 连接设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress}) 失败。");
|
||||
_isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开PLC连接
|
||||
/// </summary>
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_plc != null && _isConnected)
|
||||
{
|
||||
_plc.Close();
|
||||
_isConnected = false;
|
||||
_logger.LogInformation($"S7DeviceAgent: 断开设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress}) 连接");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"S7DeviceAgent: 断开设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress}) 连接失败。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新设备变量配置
|
||||
/// </summary>
|
||||
public void UpdateVariables(List<Variable> variables)
|
||||
{
|
||||
// 清空现有的变量分组
|
||||
_variablesByPollingInterval.Clear();
|
||||
_lastPollTimes.Clear();
|
||||
|
||||
// 按轮询间隔分组变量
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
if (!_variablesByPollingInterval.ContainsKey(variable.PollingInterval))
|
||||
{
|
||||
_variablesByPollingInterval[variable.PollingInterval] = new List<Variable>();
|
||||
_lastPollTimes[variable.PollingInterval] = DateTime.MinValue;
|
||||
}
|
||||
_variablesByPollingInterval[variable.PollingInterval].Add(variable);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"S7DeviceAgent: 更新设备 {_deviceConfig.Name} 的变量配置,共 {variables.Count} 个变量");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询设备变量
|
||||
/// </summary>
|
||||
public async Task PollVariablesAsync()
|
||||
{
|
||||
if (!_isConnected || _plc == null)
|
||||
{
|
||||
_logger.LogWarning($"S7DeviceAgent: 设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress}) 未连接,跳过轮询。");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 按轮询间隔依次轮询
|
||||
foreach (var kvp in _variablesByPollingInterval)
|
||||
{
|
||||
var pollingInterval = kvp.Key;
|
||||
var variables = kvp.Value;
|
||||
|
||||
// 检查是否到了轮询时间
|
||||
if (ShouldPoll(pollingInterval))
|
||||
{
|
||||
await PollVariablesByLevelAsync(variables, pollingInterval);
|
||||
_lastPollTimes[pollingInterval] = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// _messenger.Send(new LogMessage(LogLevel.Error, ex, $"S7DeviceAgent: 设备 {_deviceConfig.Name} ({_deviceConfig.IpAddress}) 轮询错误。"));
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldPoll(int PollingInterval)
|
||||
{
|
||||
// 获取轮询间隔
|
||||
var interval = GetPollingInterval(PollingInterval);
|
||||
|
||||
// 检查是否到了轮询时间
|
||||
if (_lastPollTimes.TryGetValue(PollingInterval, out var lastPollTime))
|
||||
{
|
||||
return DateTime.Now - lastPollTime >= interval;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private TimeSpan GetPollingInterval(int pollingInterval)
|
||||
{
|
||||
return pollingInterval switch
|
||||
{
|
||||
10 => TimeSpan.FromMilliseconds(10), // TenMilliseconds
|
||||
100 => TimeSpan.FromMilliseconds(100), // HundredMilliseconds
|
||||
500 => TimeSpan.FromMilliseconds(500), // FiveHundredMilliseconds
|
||||
1000 => TimeSpan.FromMilliseconds(1000), // OneSecond
|
||||
5000 => TimeSpan.FromMilliseconds(5000), // FiveSeconds
|
||||
10000 => TimeSpan.FromMilliseconds(10000), // TenSeconds
|
||||
20000 => TimeSpan.FromMilliseconds(20000), // TwentySeconds
|
||||
30000 => TimeSpan.FromMilliseconds(30000), // ThirtySeconds
|
||||
60000 => TimeSpan.FromMilliseconds(60000), // OneMinute
|
||||
180000 => TimeSpan.FromMilliseconds(180000), // ThreeMinutes
|
||||
300000 => TimeSpan.FromMilliseconds(300000), // FiveMinutes
|
||||
600000 => TimeSpan.FromMilliseconds(600000), // TenMinutes
|
||||
1800000 => TimeSpan.FromMilliseconds(1800000), // ThirtyMinutes
|
||||
_ => TimeSpan.FromMilliseconds(1000)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task PollVariablesByLevelAsync(List<Variable> variables, int pollingInterval)
|
||||
{
|
||||
// 批量读取变量
|
||||
var dataItems = new List<DataItem>();
|
||||
var variableLookup = new Dictionary<int, Variable>(); // 用于关联DataItem和Variable
|
||||
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataItem = DataItem.FromAddress(variable.S7Address);
|
||||
dataItems.Add(dataItem);
|
||||
variableLookup[dataItems.Count - 1] = variable; // 记录索引和变量的对应关系
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"S7DeviceAgent: 解析变量 {variable.Name} ({variable.S7Address}) 地址失败。");
|
||||
}
|
||||
}
|
||||
|
||||
if (dataItems.Count == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 执行批量读取
|
||||
await _plc.ReadMultipleVarsAsync(dataItems);
|
||||
|
||||
// 处理读取结果
|
||||
for (int i = 0; i < dataItems.Count; i++)
|
||||
{
|
||||
if (variableLookup.TryGetValue(i, out var variable))
|
||||
{
|
||||
var dataItem = dataItems[i];
|
||||
if (dataItem?.Value != null)
|
||||
{
|
||||
// 更新变量值
|
||||
variable.DataValue = dataItem.Value.ToString();
|
||||
variable.DisplayValue = dataItem.Value.ToString();
|
||||
variable.UpdatedAt = DateTime.Now;
|
||||
|
||||
// 创建VariableDto对象
|
||||
var variableDto = new VariableDto
|
||||
{
|
||||
Id = variable.Id,
|
||||
Name = variable.Name,
|
||||
S7Address = variable.S7Address,
|
||||
DataValue = variable.DataValue,
|
||||
DisplayValue = variable.DisplayValue,
|
||||
PollingInterval = variable.PollingInterval,
|
||||
Protocol = variable.Protocol,
|
||||
UpdatedAt = variable.UpdatedAt
|
||||
// 可以根据需要添加其他属性
|
||||
};
|
||||
|
||||
// 发送变量更新消息
|
||||
var variableContext = new VariableContext(variableDto);
|
||||
|
||||
// 写入通道总线
|
||||
await _channelBus.GetWriter<VariableContext>("DataProcessingQueue").WriteAsync(variableContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug($"S7DeviceAgent: 设备 {_deviceConfig.Name} 完成 {pollingInterval} 毫秒轮询间隔,共处理 {dataItems.Count} 个变量");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"S7DeviceAgent: 设备 {_deviceConfig.Name} 批量读取变量失败。");
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisconnectAsync();
|
||||
_plc?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user