using System; using System.Collections.Generic; using System.Diagnostics; 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 _logger; private readonly DataServices _dataServices; private readonly Dictionary _s7PlcClients = new Dictionary(); private Thread _pollingThread; private CancellationTokenSource _cancellationTokenSource; private readonly Dictionary _pollingIntervals = new Dictionary { { PollLevelType.TenMilliseconds, TimeSpan.FromMilliseconds((int)PollLevelType.TenMilliseconds) }, { PollLevelType.HundredMilliseconds, TimeSpan.FromMilliseconds((int)PollLevelType.HundredMilliseconds) }, { PollLevelType.FiveHundredMilliseconds, TimeSpan.FromMilliseconds((int)PollLevelType.FiveHundredMilliseconds) }, { PollLevelType.OneSecond, TimeSpan.FromMilliseconds((int)PollLevelType.OneSecond) }, { PollLevelType.FiveSeconds, TimeSpan.FromMilliseconds((int)PollLevelType.FiveSeconds) }, { PollLevelType.TenSeconds, TimeSpan.FromMilliseconds((int)PollLevelType.TenSeconds) }, { PollLevelType.TwentySeconds, TimeSpan.FromMilliseconds((int)PollLevelType.TwentySeconds) }, { PollLevelType.ThirtySeconds, TimeSpan.FromMilliseconds((int)PollLevelType.ThirtySeconds) }, { PollLevelType.OneMinute, TimeSpan.FromMilliseconds((int)PollLevelType.OneMinute) }, { PollLevelType.ThreeMinutes, TimeSpan.FromMilliseconds((int)PollLevelType.ThreeMinutes) }, { PollLevelType.FiveMinutes, TimeSpan.FromMilliseconds((int)PollLevelType.FiveMinutes) }, { PollLevelType.TenMinutes, TimeSpan.FromMilliseconds((int)PollLevelType.TenMinutes) }, { PollLevelType.ThirtyMinutes, TimeSpan.FromMilliseconds((int)PollLevelType.ThirtyMinutes) } }; private List? _s7Devices; public S7BackgroundService(ILogger logger, DataServices dataServices) { _logger = logger; _dataServices = dataServices; _dataServices.OnDeviceListChanged += HandleDeviceListChanged; } private void HandleDeviceListChanged(List devices) { _s7Devices = devices.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive) .ToList(); // 当设备列表变化时,更新PLC客户端 // 这里需要更复杂的逻辑来处理连接的关闭和新连接的建立 // 简单起见,这里只做日志记录 _logger.LogInformation("设备列表已更改。S7客户端可能需要重新初始化。"); } protected override Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("S7后台服务正在启动。"); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); _pollingThread = new Thread(() => PollingLoop(_cancellationTokenSource.Token)) { IsBackground = true }; _pollingThread.Start(); return Task.CompletedTask; } private void PollingLoop(CancellationToken stoppingToken) { _logger.LogInformation("S7轮询线程已启动。"); stoppingToken.Register(() => _logger.LogInformation("S7后台服务正在停止。")); _s7Devices = _dataServices.Devices?.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive) .ToList(); while (!stoppingToken.IsCancellationRequested) { // _logger.LogDebug("S7后台服务正在执行后台工作。"); PollS7Devices(stoppingToken); // 短暂休眠以防止CPU占用过高 Thread.Sleep(100); } _logger.LogInformation("S7轮询线程已停止。"); } /// /// 初始化或重新连接PLC客户端 /// /// S7设备 /// 连接成功的Plc客户端实例,如果连接失败则返回null private Plc? InitializePlcClient(Device device) { // 检查字典中是否已存在该设备的PLC客户端 if (!_s7PlcClients.TryGetValue(device.Id, out var plcClient)) { // 如果不存在,则创建新的Plc客户端 try { plcClient = new Plc(device.CpuType, device.Ip, (short)device.Prot, device.Rack, device.Slot); plcClient.Open(); // 尝试打开连接 _s7PlcClients[device.Id] = plcClient; // 将新创建的客户端添加到字典 _logger.LogInformation($"已连接到S7 PLC: {device.Name} ({device.Ip})"); } catch (Exception ex) { _logger.LogError(ex, $"连接S7 PLC失败: {device.Name} ({device.Ip})"); return null; // 连接失败,返回null } } else if (!plcClient.IsConnected) { // 如果存在但未连接,则尝试重新连接 try { plcClient.Open(); // 尝试重新打开连接 _logger.LogInformation($"已重新连接到S7 PLC: {device.Name} ({device.Ip})"); } catch (Exception ex) { _logger.LogError(ex, $"重新连接S7 PLC失败: {device.Name} ({device.Ip})"); return null; // 重新连接失败,返回null } } return plcClient; // 返回连接成功的Plc客户端 } /// /// 轮询S7设备数据 /// /// 取消令牌 private void PollS7Devices(CancellationToken stoppingToken) { if (_s7Devices == null || !_s7Devices.Any()) { _logger.LogDebug("未找到活跃的S7设备进行轮询。等待5秒后重试。"); try { // 使用CancellationToken来使等待可取消 Task.Delay(TimeSpan.FromSeconds(5), stoppingToken).Wait(stoppingToken); } catch (OperationCanceledException) { // 如果在等待期间取消,则退出 return; } return; } foreach (var device in _s7Devices) { if (stoppingToken.IsCancellationRequested) return; // 尝试获取或初始化PLC客户端连接 var plcClient = InitializePlcClient(device); if (plcClient == null) { continue; // 如果连接失败,则跳过当前设备 } // 读取设备变量 ReadDeviceVariables(plcClient, device, stoppingToken); } } /// /// 读取设备的S7变量并更新其值。 /// /// 已连接的Plc客户端实例。 /// S7设备。 /// 取消令牌。 private void ReadDeviceVariables(Plc plcClient, Device device, CancellationToken stoppingToken) { // 过滤出当前设备和S7协议相关的变量 var s7Variables = device.VariableTables .Where(vt => vt.ProtocolType == ProtocolType.S7 && vt.IsActive) .SelectMany(vt => vt.DataVariables) .ToList(); if (!s7Variables.Any()) { _logger.LogDebug($"设备 {device.Name} 没有找到活跃的S7变量。"); return; } try { // 遍历并读取每个S7变量 foreach (var variable in s7Variables) { if (stoppingToken.IsCancellationRequested) return; // 如果取消令牌被请求,则停止读取 // 获取变量的轮询间隔 if (!_pollingIntervals.TryGetValue(variable.PollLevelType, out var interval)) { _logger.LogWarning($"未知轮询级别 {variable.PollLevelType},跳过变量 {variable.Name}。"); continue; } Thread.Sleep(100); // 检查是否达到轮询时间 if ((DateTime.Now - variable.LastPollTime) < interval) { continue; // 未到轮询时间,跳过 } try { // 从PLC读取变量值 var value = plcClient.Read(variable.S7Address); if (value != null) { // 更新变量的原始数据值和显示值 variable.DataValue = value.ToString(); variable.DisplayValue = SiemensHelper.ConvertS7Value(value, variable.DataType, variable.Converstion); variable.LastPollTime = DateTime.Now; // 更新最后轮询时间 _logger.LogDebug($"线程ID:{Environment.CurrentManagedThreadId},已读取变量 {variable.Name}: {variable.DataValue}"); } } catch (Exception ex) { _logger.LogError(ex, $"从设备 {device.Name} 读取变量 {variable.Name} 失败。"); } } } catch (Exception ex) { _logger.LogError(ex, $"设备 {device.Name} 批量读取过程中发生错误。"); } } public override async Task StopAsync(CancellationToken stoppingToken) { _logger.LogInformation("S7 Background Service is stopping."); // Signal the polling thread to stop _cancellationTokenSource?.Cancel(); // Wait for the polling thread to finish _pollingThread?.Join(); // Close all active PLC connections foreach (var plcClient in _s7PlcClients.Values) { if (plcClient.IsConnected) { plcClient.Close(); _logger.LogInformation($"Closed S7 PLC connection: {plcClient.IP}"); } } _s7PlcClients.Clear(); await base.StopAsync(stoppingToken); } } }