2025-07-05 01:31:44 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using S7.Net;
|
|
|
|
|
|
using PMSWPF.Models;
|
|
|
|
|
|
using PMSWPF.Enums;
|
|
|
|
|
|
using PMSWPF.Helper;
|
|
|
|
|
|
|
|
|
|
|
|
namespace PMSWPF.Services
|
|
|
|
|
|
{
|
|
|
|
|
|
public class S7BackgroundService : BackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ILogger<S7BackgroundService> _logger;
|
|
|
|
|
|
private readonly DataServices _dataServices;
|
|
|
|
|
|
private readonly Dictionary<int, Plc> _s7PlcClients = new Dictionary<int, Plc>();
|
|
|
|
|
|
private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(1); // 轮询间隔
|
2025-07-05 02:22:36 +08:00
|
|
|
|
private List<Device>? _s7Devices;
|
2025-07-05 01:31:44 +08:00
|
|
|
|
|
|
|
|
|
|
public S7BackgroundService(ILogger<S7BackgroundService> logger, DataServices dataServices)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_dataServices = dataServices;
|
|
|
|
|
|
_dataServices.OnDeviceListChanged += HandleDeviceListChanged;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleDeviceListChanged(List<Device> devices)
|
|
|
|
|
|
{
|
2025-07-05 02:22:36 +08:00
|
|
|
|
_s7Devices = devices.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive)
|
|
|
|
|
|
.ToList();
|
2025-07-05 01:31:44 +08:00
|
|
|
|
// 当设备列表变化时,更新PLC客户端
|
|
|
|
|
|
// 这里需要更复杂的逻辑来处理连接的关闭和新连接的建立
|
|
|
|
|
|
// 简单起见,这里只做日志记录
|
|
|
|
|
|
_logger.LogInformation("Device list changed. S7 clients might need to be reinitialized.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("S7 Background Service is starting.");
|
|
|
|
|
|
|
|
|
|
|
|
stoppingToken.Register(() => _logger.LogInformation("S7 Background Service is stopping."));
|
|
|
|
|
|
|
2025-07-05 02:22:36 +08:00
|
|
|
|
_s7Devices = _dataServices.Devices?.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
2025-07-05 01:31:44 +08:00
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogDebug("S7 Background Service is doing background work.");
|
|
|
|
|
|
|
|
|
|
|
|
await PollS7Devices(stoppingToken);
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(_pollingInterval, stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (TaskCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When the stopping token is canceled, a TaskCanceledException is thrown.
|
|
|
|
|
|
// We should catch it to exit gracefully.
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("S7 Background Service has stopped.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task PollS7Devices(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
2025-07-05 02:22:36 +08:00
|
|
|
|
if (_s7Devices == null || !_s7Devices.Any())
|
2025-07-05 01:31:44 +08:00
|
|
|
|
{
|
|
|
|
|
|
_logger.LogDebug("No active S7 devices found to poll.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-05 02:22:36 +08:00
|
|
|
|
foreach (var device in _s7Devices)
|
2025-07-05 01:31:44 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (stoppingToken.IsCancellationRequested) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!_s7PlcClients.ContainsKey(device.Id))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Initialize Plc client for the device
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var plc = new Plc(device.CpuType, device.Ip, (short)device.Prot, device.Rack, device.Slot);
|
|
|
|
|
|
await plc.OpenAsync();
|
|
|
|
|
|
_s7PlcClients[device.Id] = plc;
|
|
|
|
|
|
_logger.LogInformation($"Connected to S7 PLC: {device.Name} ({device.Ip})");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, $"Failed to connect to S7 PLC: {device.Name} ({device.Ip})");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var plcClient = _s7PlcClients[device.Id];
|
|
|
|
|
|
if (!plcClient.IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await plcClient.OpenAsync();
|
|
|
|
|
|
_logger.LogInformation($"Reconnected to S7 PLC: {device.Name} ({device.Ip})");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, $"Failed to reconnect to S7 PLC: {device.Name} ({device.Ip})");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Filter variables for the current device and S7 protocol
|
2025-07-05 02:22:36 +08:00
|
|
|
|
var s7VariablesTemp = device
|
|
|
|
|
|
.VariableTables.Where(vd => vd.ProtocolType == ProtocolType.S7 && vd.IsActive)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var s7Variables = s7VariablesTemp.SelectMany(vt => vt.DataVariables)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
// ?.SelectMany(vt => vt.DataVariables)
|
|
|
|
|
|
|
2025-07-05 01:31:44 +08:00
|
|
|
|
|
|
|
|
|
|
if (s7Variables == null || !s7Variables.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogDebug($"No active S7 variables found for device: {device.Name}");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Batch read variables
|
2025-07-05 02:22:36 +08:00
|
|
|
|
var addressesToRead = s7Variables.Select(vd => vd.S7Address)
|
|
|
|
|
|
.ToList();
|
2025-07-05 01:31:44 +08:00
|
|
|
|
if (!addressesToRead.Any()) continue;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// S7.Net.Plus library supports ReadMultiple, but it's more complex for different data types.
|
|
|
|
|
|
// For simplicity, we'll read them one by one for now, or use a more advanced batch read if all are same type.
|
|
|
|
|
|
// A more robust solution would involve grouping by data block and type.
|
|
|
|
|
|
|
|
|
|
|
|
// Example of reading multiple items (assuming all are of type DWord for simplicity)
|
|
|
|
|
|
// This part needs to be refined based on actual variable types and addresses
|
|
|
|
|
|
// var dataItems = addressesToRead.Select(addr => new DataItem { DataType = DataType.DataBlock, VarType = VarType.DWord, StringLength = 1, DB = 1, StartByteAdr = 0 }).ToList();
|
|
|
|
|
|
// plcClient.ReadMultiple(dataItems);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var variable in s7Variables)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (stoppingToken.IsCancellationRequested) return;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// This is a simplified read. In a real scenario, you'd parse S7Address
|
|
|
|
|
|
// to get DataType, DB, StartByteAdr, BitAdr, etc.
|
|
|
|
|
|
// For now, assuming S7Address is directly readable by Read method (e.g., "DB1.DBW0")
|
|
|
|
|
|
var value = await plcClient.ReadAsync(variable.S7Address);
|
|
|
|
|
|
if (value != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Update the variable's DataValue and DisplayValue
|
|
|
|
|
|
variable.DataValue = value.ToString();
|
2025-07-05 02:22:36 +08:00
|
|
|
|
variable.DisplayValue
|
|
|
|
|
|
= SiemensHelper.ConvertS7Value(value, variable.DataType, variable.Converstion);
|
2025-07-05 01:31:44 +08:00
|
|
|
|
_logger.LogDebug($"Read {variable.Name}: {variable.DataValue}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, $"Failed to read variable {variable.Name} from {device.Name}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, $"Error during batch read for device {device.Name}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override async Task StopAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("S7 Background Service is stopping.");
|
|
|
|
|
|
|
|
|
|
|
|
// Close all active PLC connections
|
|
|
|
|
|
foreach (var plcClient in _s7PlcClients.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (plcClient.IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
plcClient.Close();
|
|
|
|
|
|
_logger.LogInformation($"Closed S7 PLC connection: {plcClient.IP}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-05 02:22:36 +08:00
|
|
|
|
|
2025-07-05 01:31:44 +08:00
|
|
|
|
_s7PlcClients.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
await base.StopAsync(stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-05 02:22:36 +08:00
|
|
|
|
}
|