diff --git a/App.xaml.cs b/App.xaml.cs index 47f5785..dff7dcd 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -72,6 +72,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddHostedService(); // Register as HostedService + services.AddHostedService(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -193,6 +194,7 @@ public partial class App : Application _db.CodeFirst.InitTables(); _db.CodeFirst.InitTables(); _db.CodeFirst.InitTables(); + _db.CodeFirst.InitTables(); _db.CodeFirst.InitTables(); } } \ No newline at end of file diff --git a/PMSWPF.csproj b/PMSWPF.csproj index 728ad5f..f8808e7 100644 --- a/PMSWPF.csproj +++ b/PMSWPF.csproj @@ -19,6 +19,7 @@ + diff --git a/Services/DataServices.cs b/Services/DataServices.cs index 859448c..68cc936 100644 --- a/Services/DataServices.cs +++ b/Services/DataServices.cs @@ -12,100 +12,182 @@ using SqlSugar; namespace PMSWPF.Services; +/// +/// 数据服务类,负责从数据库加载和管理各种数据,并提供数据变更通知。 +/// 继承自ObservableRecipient,可以接收消息;实现IRecipient,处理加载消息。 +/// public partial class DataServices : ObservableRecipient, IRecipient { + // 日志记录器,用于记录数据服务中的操作和错误。 private readonly ILogger _logger; + // 设备列表,使用ObservableProperty特性,当值改变时会自动触发属性变更通知。 [ObservableProperty] private List _devices; + // 变量表列表。 [ObservableProperty] private List _variableTables; + // 变量数据列表。 + [ObservableProperty] private List _variableDatas; + // 菜单树列表。 [ObservableProperty] private List menuTrees; + // MQTT配置列表。 [ObservableProperty] private List _mqtts; + + // 设备数据仓库,用于设备数据的CRUD操作。 private readonly DeviceRepository _deviceRepository; + // 菜单数据仓库,用于菜单数据的CRUD操作。 private readonly MenuRepository _menuRepository; + // MQTT数据仓库,用于MQTT配置数据的CRUD操作。 private readonly MqttRepository _mqttRepository; + // 变量数据仓库,用于变量数据的CRUD操作。 + private readonly VarDataRepository _varDataRepository; + // 设备列表变更事件,当设备列表数据更新时触发。 + public event EventHandler> OnDeviceListChanged; + // 菜单树列表变更事件,当菜单树数据更新时触发。 + public event EventHandler> OnMenuTreeListChanged; + // MQTT列表变更事件,当MQTT配置数据更新时触发。 + public event EventHandler> OnMqttListChanged; + // 变量数据变更事件,当变量数据更新时触发。 + public event EventHandler> OnVariableDataChanged; - public event Action> OnDeviceListChanged; - public event Action> OnMenuTreeListChanged; - public event Action> OnMqttListChanged; - - + /// + /// 当_devices属性值改变时触发的局部方法,用于调用OnDeviceListChanged事件。 + /// + /// 新的设备列表。 partial void OnDevicesChanged(List devices) { - OnDeviceListChanged?.Invoke(devices); + OnDeviceListChanged?.Invoke(this, devices); } + /// + /// 当menuTrees属性值改变时触发的局部方法,用于调用OnMenuTreeListChanged事件。 + /// + /// 新的菜单树列表。 partial void OnMenuTreesChanged(List MenuTrees) { - OnMenuTreeListChanged?.Invoke(MenuTrees); + OnMenuTreeListChanged?.Invoke(this, MenuTrees); } + /// + /// 当_mqtts属性值改变时触发的局部方法,用于调用OnMqttListChanged事件。 + /// + /// 新的MQTT配置列表。 partial void OnMqttsChanged(List mqtts) { - OnMqttListChanged?.Invoke(mqtts); + OnMqttListChanged?.Invoke(this, mqtts); } + // 注释掉的代码块,可能用于变量数据变更事件的触发,但目前未启用。 + // { + // OnVariableDataChanged?.Invoke(this, value); + // } + /// + /// DataServices类的构造函数。 + /// 注入ILogger,并初始化各个数据仓库。 + /// + /// 日志记录器实例。 public DataServices(ILogger logger) { _logger = logger; - IsActive = true; + IsActive = true; // 激活消息接收器 _deviceRepository = new DeviceRepository(); _menuRepository = new MenuRepository(); _mqttRepository = new MqttRepository(); + _varDataRepository = new VarDataRepository(); } - /// - /// 接受加载消息,收到消息后从数据库加载对应的数据 + /// 接收加载消息,根据消息类型从数据库加载对应的数据。 /// - /// 消息的类型,如加载菜单LoadMessage.Menu - /// + /// 加载消息,包含要加载的数据类型。 + /// 如果加载类型未知,可能会抛出此异常(尽管当前实现中未显式抛出)。 public async void Receive(LoadMessage message) { try { switch (message.LoadType) { - case LoadTypes.All: + case LoadTypes.All: // 加载所有数据 await LoadDevices(); await LoadMenus(); await LoadMqtts(); + await LoadVariableDatas(); break; - case LoadTypes.Devices: + case LoadTypes.Devices: // 仅加载设备数据 await LoadDevices(); break; - case LoadTypes.Menu: + case LoadTypes.Menu: // 仅加载菜单数据 await LoadMenus(); break; - case LoadTypes.Mqtts: + case LoadTypes.Mqtts: // 仅加载MQTT配置数据 await LoadMqtts(); break; } } catch (Exception e) { + // 捕获加载数据时发生的异常,并通过通知和日志记录错误信息。 NotificationHelper.ShowMessage($"加载数据出现了错误:{e.Message}"); _logger.LogError($"加载数据出现了错误:{e}"); } } + /// + /// 异步加载设备数据。 + /// + /// 表示异步操作的任务。 private async Task LoadDevices() { Devices = await _deviceRepository.GetAll(); } + /// + /// 异步加载菜单数据,并进行父级关联和排序。 + /// + /// 表示异步操作的任务。 private async Task LoadMenus() { MenuTrees = await _menuRepository.GetMenuTrees(); foreach (MenuBean menu in MenuTrees) { - MenuHelper.MenuAddParent(menu); - DataServicesHelper.SortMenus(menu); + MenuHelper.MenuAddParent(menu); // 为菜单添加父级引用 + DataServicesHelper.SortMenus(menu); // 排序菜单 } } + /// + /// 异步获取所有MQTT配置。 + /// + /// 包含所有MQTT配置的列表。 + public async Task> GetMqttsAsync() + { + return await _mqttRepository.GetAll(); + } + + /// + /// 异步加载MQTT配置数据。 + /// + /// 表示异步操作的任务。 private async Task LoadMqtts() { Mqtts = await _mqttRepository.GetAll(); } + + /// + /// 异步获取所有变量数据。 + /// + /// 包含所有变量数据的列表。 + public async Task> GetAllVariableDataAsync() + { + return await _varDataRepository.GetAllAsync(); + } + + /// + /// 异步加载变量数据。 + /// + /// 表示异步操作的任务。 + private async Task LoadVariableDatas() + { + VariableDatas = await _varDataRepository.GetAllAsync(); + } } \ No newline at end of file diff --git a/Services/MqttBackgroundService.cs b/Services/MqttBackgroundService.cs new file mode 100644 index 0000000..b6d2bcb --- /dev/null +++ b/Services/MqttBackgroundService.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using NLog; +using MQTTnet; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using PMSWPF.Models; +using PMSWPF.Services; +using PMSWPF.Helper; +using PMSWPF.Enums; + +namespace PMSWPF.Services +{ + /// + /// MQTT后台服务,继承自BackgroundService,用于在后台管理MQTT连接和数据发布。 + /// + public class MqttBackgroundService : BackgroundService + { + // NLog日志记录器,用于记录服务运行时的信息、警告和错误。 + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + // 数据服务实例,用于访问和操作应用程序数据,如MQTT配置和变量数据。 + private readonly DataServices _dataServices; + // 存储MQTT客户端实例的字典,键为MQTT配置ID,值为IMqttClient对象。 + private readonly Dictionary _mqttClients; + // 存储MQTT配置的字典,键为MQTT配置ID,值为Mqtt模型对象。 + private readonly Dictionary _mqttConfigurations; + // 存储与MQTT配置关联的变量数据的字典,键为MQTT配置ID,值为VariableData列表。 + private readonly Dictionary> _mqttVariableData; + // 定时器,用于周期性地执行数据发布任务。 + private Timer _timer; + + /// + /// 构造函数,注入DataServices。 + /// + /// 数据服务实例。 + public MqttBackgroundService(DataServices dataServices) + { + _dataServices = dataServices; + _mqttClients = new Dictionary(); + _mqttConfigurations = new Dictionary(); + _mqttVariableData = new Dictionary>(); + } + + /// + /// 后台服务的执行方法,当服务启动时调用。 + /// + /// 用于取消操作的CancellationToken。 + /// 表示异步操作的任务。 + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + Logger.Info("MqttBackgroundService started."); // 记录服务启动信息 + + // 订阅MQTT列表和变量数据变化的事件,以便在数据更新时重新加载配置和数据。 + _dataServices.OnMqttListChanged += HandleMqttListChanged; + _dataServices.OnVariableDataChanged += HandleVariableDataChanged; + + // 初始加载MQTT配置和变量数据。 + await LoadMqttConfigurations(); + await LoadVariableData(); + + // 初始化定时器,每5秒执行一次DoWork方法,用于周期性地发布数据。 + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); // 每5秒轮询一次 + + // 使服务保持运行,直到收到停止请求。 + await Task.Delay(Timeout.Infinite, stoppingToken); + } + + /// + /// 后台服务的停止方法,当服务停止时调用。 + /// + /// 用于取消操作的CancellationToken。 + /// 表示异步操作的任务。 + public override async Task StopAsync(CancellationToken stoppingToken) + { + Logger.Info("MqttBackgroundService stopping."); // 记录服务停止信息 + + // 停止定时器。 + _timer?.Change(Timeout.Infinite, 0); + + // 取消订阅事件。 + _dataServices.OnMqttListChanged -= HandleMqttListChanged; + _dataServices.OnVariableDataChanged -= HandleVariableDataChanged; + + // 断开所有已连接的MQTT客户端。 + foreach (var client in _mqttClients.Values) + { + if (client.IsConnected) + { + await client.DisconnectAsync(); + } + } + // 清空所有字典。 + _mqttClients.Clear(); + _mqttConfigurations.Clear(); + _mqttVariableData.Clear(); + + await base.StopAsync(stoppingToken); + } + + /// + /// 定时器回调方法,用于周期性地检查并发布已修改的变量数据。 + /// + /// 定时器状态对象(此处未使用)。 + private async void DoWork(object state) + { + // 遍历所有MQTT配置关联的变量数据。 + foreach (var mqttConfigId in _mqttVariableData.Keys) + { + // 检查MQTT客户端是否连接。 + if (_mqttClients.TryGetValue(mqttConfigId, out var client) && client.IsConnected) + { + var variables = _mqttVariableData[mqttConfigId]; + // 遍历与当前MQTT配置关联的变量。 + foreach (var variable in variables) + { + // 如果变量已被修改(IsModified标志为true)。 + if (variable.IsModified) + { + // 获取发布主题。 + var topic = _mqttConfigurations[mqttConfigId].PublishTopic; + if (!string.IsNullOrEmpty(topic)) + { + // 构建MQTT消息。 + var message = new MqttApplicationMessageBuilder() + .WithTopic($"{topic}/{variable.Name}") // 主题格式:PublishTopic/VariableName + .WithPayload(variable.DataValue) // 消息载荷为变量的值 + .Build(); + + // 发布MQTT消息。 + await client.PublishAsync(message); + Logger.Info($"Published {variable.Name} = {variable.DataValue} to {topic}/{variable.Name}"); // 记录发布信息 + variable.IsModified = false; // 发布后重置修改标志。 + } + } + } + } + } + } + + /// + /// 加载并连接MQTT配置。 + /// + /// 表示异步操作的任务。 + private async Task LoadMqttConfigurations() + { + // 从数据服务获取所有MQTT配置。 + var mqtts = await _dataServices.GetMqttsAsync(); + foreach (var mqtt in mqtts) + { + // 如果客户端字典中不包含当前MQTT配置的客户端,则尝试连接。 + if (!_mqttClients.ContainsKey(mqtt.Id)) + { + await ConnectMqttClient(mqtt); + } + // 更新或添加MQTT配置到字典。 + _mqttConfigurations[mqtt.Id] = mqtt; + } + + // 断开并移除不再配置中的MQTT客户端。 + var removedMqttIds = _mqttClients.Keys.Except(mqtts.Select(m => m.Id)).ToList(); + foreach (var id in removedMqttIds) + { + if (_mqttClients.ContainsKey(id)) + { + var client = _mqttClients[id]; + if (client.IsConnected) + { + await client.DisconnectAsync(); + } + _mqttClients.Remove(id); + Logger.Info($"Disconnected and removed MQTT client for ID: {id}"); + } + _mqttConfigurations.Remove(id); + _mqttVariableData.Remove(id); + } + } + + /// + /// 连接到指定的MQTT代理。 + /// + /// MQTT配置对象。 + /// 表示异步操作的任务。 + private async Task ConnectMqttClient(Mqtt mqtt) + { + try + { + // 创建MQTT客户端工厂和客户端实例。 + var factory = new MqttFactory(); + var client = factory.CreateMqttClient(); + // 构建MQTT客户端连接选项。 + var options = new MqttClientOptionsBuilder() + .WithClientId(mqtt.ClientID) + .WithTcpServer(mqtt.Host, mqtt.Port) + .WithCredentials(mqtt.UserName, mqtt.PassWord) + .WithCleanSession() // 清理会话,每次连接都是新会话 + .Build(); + + // 设置连接成功事件处理程序。 + client.UseConnectedHandler(e => + { + Logger.Info($"Connected to MQTT broker: {mqtt.Name}"); + NotificationHelper.ShowMessage($"已连接到MQTT服务器: {mqtt.Name}", NotificationType.Success); + }); + + // 设置断开连接事件处理程序。 + client.UseDisconnectedHandler(async e => + { + Logger.Warn($"Disconnected from MQTT broker: {mqtt.Name}. Reason: {e.Reason}"); + NotificationHelper.ShowMessage($"与MQTT服务器断开连接: {mqtt.Name}", NotificationType.Warning); + // 尝试重新连接。 + await Task.Delay(TimeSpan.FromSeconds(5)); // 等待5秒后重连 + try + { + await client.ConnectAsync(options, CancellationToken.None); + } + catch (Exception ex) + { + Logger.Error(ex, $"Failed to reconnect to MQTT broker: {mqtt.Name}"); + } + }); + + // 尝试连接到MQTT代理。 + await client.ConnectAsync(options, CancellationToken.None); + // 将连接成功的客户端添加到字典。 + _mqttClients[mqtt.Id] = client; + } + catch (Exception ex) + { + Logger.Error(ex, $"Failed to connect to MQTT broker: {mqtt.Name}"); + NotificationHelper.ShowMessage($"连接MQTT服务器失败: {mqtt.Name} - {ex.Message}", NotificationType.Error); + } + } + + /// + /// 加载所有变量数据并按MQTT配置ID进行分组。 + /// + /// 表示异步操作的任务。 + private async Task LoadVariableData() + { + // 从数据服务获取所有变量数据。 + var allVariables = await _dataServices.GetAllVariableDataAsync(); + _mqttVariableData.Clear(); // 清空现有数据 + + // 遍历所有变量,并根据其关联的MQTT配置进行分组。 + foreach (var variable in allVariables) + { + if (variable.Mqtts != null) + { + foreach (var mqtt in variable.Mqtts) + { + // 如果字典中没有该MQTT配置的条目,则创建一个新的列表。 + if (!_mqttVariableData.ContainsKey(mqtt.Id)) + { + _mqttVariableData[mqtt.Id] = new List(); + } + // 将变量添加到对应MQTT配置的列表中。 + _mqttVariableData[mqtt.Id].Add(variable); + } + } + } + } + + /// + /// 处理MQTT列表变化事件的回调方法。 + /// + /// 事件发送者。 + /// 更新后的MQTT配置列表。 + private async void HandleMqttListChanged(object sender, List mqtts) + { + Logger.Info("MQTT list changed. Reloading configurations."); // 记录MQTT列表变化信息 + // 重新加载MQTT配置和变量数据。 + await LoadMqttConfigurations(); + await LoadVariableData(); // 重新加载变量数据,以防关联发生变化 + } + + /// + /// 处理变量数据变化事件的回调方法。 + /// + /// 事件发送者。 + /// 更新后的变量数据列表。 + private async void HandleVariableDataChanged(object sender, List variableDatas) + { + Logger.Info("Variable data changed. Reloading variable associations."); // 记录变量数据变化信息 + // 重新加载变量数据。 + await LoadVariableData(); + } + } +} diff --git a/Services/S7BackgroundService.cs b/Services/S7BackgroundService.cs index 9a5d456..f44deb9 100644 --- a/Services/S7BackgroundService.cs +++ b/Services/S7BackgroundService.cs @@ -13,17 +13,28 @@ using PMSWPF.Helper; namespace PMSWPF.Services { + /// + /// S7后台服务,继承自BackgroundService,用于在后台周期性地轮询S7 PLC设备数据。 + /// public class S7BackgroundService : BackgroundService { + // 日志记录器,用于记录服务运行时的信息、警告和错误。 private readonly ILogger _logger; + // 数据服务实例,用于访问和操作应用程序数据,如设备配置。 private readonly DataServices _dataServices; + // 存储S7 PLC客户端实例的字典,键为设备ID,值为Plc对象。 private readonly Dictionary _s7PlcClients = new Dictionary(); + // 轮询数据的线程。 private Thread _pollingThread; + // 用于取消轮询操作的CancellationTokenSource。 private CancellationTokenSource _cancellationTokenSource; + // 读取变量计数器。 private int readCount = 0; + // 跳过变量计数器(未到轮询时间)。 private int TGCount = 0; + // 定义不同轮询级别的间隔时间。 private readonly Dictionary _pollingIntervals = new Dictionary { { PollLevelType.TenMilliseconds, TimeSpan.FromMilliseconds((int)PollLevelType.TenMilliseconds) }, @@ -46,17 +57,30 @@ namespace PMSWPF.Services { PollLevelType.ThirtyMinutes, TimeSpan.FromMilliseconds((int)PollLevelType.ThirtyMinutes) } }; + // 存储S7设备列表。 private List? _s7Devices; + /// + /// 构造函数,注入ILogger和DataServices。 + /// + /// 日志记录器实例。 + /// 数据服务实例。 public S7BackgroundService(ILogger logger, DataServices dataServices) { _logger = logger; _dataServices = dataServices; + // 订阅设备列表变更事件,以便在设备配置更新时重新加载。 _dataServices.OnDeviceListChanged += HandleDeviceListChanged; } - private void HandleDeviceListChanged(List devices) + /// + /// 处理设备列表变更事件的回调方法。 + /// + /// 事件发送者。 + /// 更新后的设备列表。 + private void HandleDeviceListChanged(object sender, List devices) { + // 过滤出S7协议且激活的设备。 _s7Devices = devices.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive) .ToList(); // 当设备列表变化时,更新PLC客户端 @@ -65,28 +89,42 @@ namespace PMSWPF.Services _logger.LogInformation("设备列表已更改。S7客户端可能需要重新初始化。"); } + /// + /// 后台服务的执行方法,当服务启动时调用。 + /// + /// 用于取消操作的CancellationToken。 + /// 表示异步操作的任务。 protected override Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("S7后台服务正在启动。"); + // 创建一个CancellationTokenSource,用于控制轮询线程的取消。 _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); + // 创建并启动轮询线程。 _pollingThread = new Thread(() => PollingLoop(_cancellationTokenSource.Token)) { - IsBackground = true + IsBackground = true // 设置为后台线程,随主程序退出而退出 }; _pollingThread.Start(); return Task.CompletedTask; } + /// + /// 轮询循环方法,在新线程中执行,周期性地读取S7设备数据。 + /// + /// 用于取消轮询的CancellationToken。 private void PollingLoop(CancellationToken stoppingToken) { _logger.LogInformation("S7轮询线程已启动。"); + // 注册取消回调,当服务停止时记录日志。 stoppingToken.Register(() => _logger.LogInformation("S7后台服务正在停止。")); + // 初始加载S7设备列表。 _s7Devices = _dataServices.Devices?.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive) .ToList(); + // 轮询循环,直到收到取消请求。 while (!stoppingToken.IsCancellationRequested) { // _logger.LogDebug("S7后台服务正在执行后台工作。"); @@ -94,13 +132,13 @@ namespace PMSWPF.Services readCount = 0; TGCount = 0; - Stopwatch stopwatch = Stopwatch.StartNew(); - PollS7Devices(stoppingToken); - stopwatch.Stop(); + Stopwatch stopwatch = Stopwatch.StartNew(); // 启动计时器,测量轮询耗时 + PollS7Devices(stoppingToken); // 执行S7设备轮询 + stopwatch.Stop(); // 停止计时器 // _logger.LogDebug($"结束轮询变量,当前时间:{DateTime.Now}"); _logger.LogDebug($"读取变量数:{readCount}个,跳过变量数:{TGCount}总耗时:{stopwatch.ElapsedMilliseconds}ms"); - // 短暂休眠以防止CPU占用过高 + // 短暂休眠以防止CPU占用过高,并控制轮询频率。 Thread.Sleep(1000); } @@ -109,84 +147,86 @@ namespace PMSWPF.Services /// - /// 初始化或重新连接PLC客户端 + /// 初始化或重新连接PLC客户端。 /// - /// S7设备 - /// 连接成功的Plc客户端实例,如果连接失败则返回null + /// S7设备。 + /// 连接成功的Plc客户端实例,如果连接失败则返回null。 private Plc? InitializePlcClient(Device device) { - // 检查字典中是否已存在该设备的PLC客户端 + // 检查字典中是否已存在该设备的PLC客户端。 if (!_s7PlcClients.TryGetValue(device.Id, out var plcClient)) { - // 如果不存在,则创建新的Plc客户端 + // 如果不存在,则创建新的Plc客户端。 try { plcClient = new Plc(device.CpuType, device.Ip, (short)device.Prot, device.Rack, device.Slot); - plcClient.Open(); // 尝试打开连接 - _s7PlcClients[device.Id] = plcClient; // 将新创建的客户端添加到字典 + 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 + return null; // 连接失败,返回null。 } } else if (!plcClient.IsConnected) { - // 如果存在但未连接,则尝试重新连接 + // 如果存在但未连接,则尝试重新连接。 try { - plcClient.Open(); // 尝试重新打开连接 + 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 null; // 重新连接失败,返回null。 } } - return plcClient; // 返回连接成功的Plc客户端 + return plcClient; // 返回连接成功的Plc客户端。 } /// - /// 轮询S7设备数据 + /// 轮询S7设备数据。 /// - /// 取消令牌 + /// 取消令牌。 private void PollS7Devices(CancellationToken stoppingToken) { + // 如果没有活跃的S7设备,则等待一段时间后重试。 if (_s7Devices == null || !_s7Devices.Any()) { _logger.LogDebug("未找到活跃的S7设备进行轮询。等待5秒后重试。"); try { - // 使用CancellationToken来使等待可取消 + // 使用CancellationToken来使等待可取消。 Task.Delay(TimeSpan.FromSeconds(5), stoppingToken) .Wait(stoppingToken); } catch (OperationCanceledException) { - // 如果在等待期间取消,则退出 + // 如果在等待期间取消,则退出。 return; } return; } + // 遍历所有S7设备。 foreach (var device in _s7Devices) { - if (stoppingToken.IsCancellationRequested) return; + if (stoppingToken.IsCancellationRequested) return; // 如果收到取消请求,则停止。 - // 尝试获取或初始化PLC客户端连接 + // 尝试获取或初始化PLC客户端连接。 var plcClient = InitializePlcClient(device); if (plcClient == null) { - continue; // 如果连接失败,则跳过当前设备 + continue; // 如果连接失败,则跳过当前设备。 } - // 读取设备变量 + // 读取设备变量。 ReadDeviceVariables(plcClient, device, stoppingToken); } } @@ -199,7 +239,7 @@ namespace PMSWPF.Services /// 取消令牌。 private void ReadDeviceVariables(Plc plcClient, Device device, CancellationToken stoppingToken) { - // 过滤出当前设备和S7协议相关的变量 + // 过滤出当前设备和S7协议相关的变量。 var s7Variables = device.VariableTables .Where(vt => vt.ProtocolType == ProtocolType.S7 && vt.IsActive) .SelectMany(vt => vt.DataVariables) @@ -213,40 +253,40 @@ namespace PMSWPF.Services try { - // 遍历并读取每个S7变量 + // 遍历并读取每个S7变量。 foreach (var variable in s7Variables) { // Stopwatch stopwatch = Stopwatch.StartNew(); - if (stoppingToken.IsCancellationRequested) return; // 如果取消令牌被请求,则停止读取 + if (stoppingToken.IsCancellationRequested) return; // 如果取消令牌被请求,则停止读取。 - // 获取变量的轮询间隔 + // 获取变量的轮询间隔。 if (!_pollingIntervals.TryGetValue(variable.PollLevelType, out var interval)) { _logger.LogWarning($"未知轮询级别 {variable.PollLevelType},跳过变量 {variable.Name}。"); continue; } - // 检查是否达到轮询时间 + // 检查是否达到轮询时间。 if ((DateTime.Now - variable.LastPollTime) < interval) { TGCount++; - continue; // 未到轮询时间,跳过 + continue; // 未到轮询时间,跳过。 } try { - // 从PLC读取变量值 + // 从PLC读取变量值。 var value = plcClient.Read(variable.S7Address); if (value != null) { - // 更新变量的原始数据值和显示值 + // 更新变量的原始数据值和显示值。 variable.DataValue = value.ToString(); variable.DisplayValue = SiemensHelper.ConvertS7Value(value, variable.DataType, variable.Converstion); variable.UpdateTime = DateTime.Now; - variable.LastPollTime = DateTime.Now; // 更新最后轮询时间 + variable.LastPollTime = DateTime.Now; // 更新最后轮询时间。 readCount++; // _logger.LogDebug($"线程ID:{Environment.CurrentManagedThreadId},已读取变量 {variable.Name}: {variable.DataValue}"); } @@ -266,16 +306,21 @@ namespace PMSWPF.Services } } + /// + /// 后台服务的停止方法,当服务停止时调用。 + /// + /// 用于取消操作的CancellationToken。 + /// 表示异步操作的任务。 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 + // 关闭所有活跃的PLC连接。 foreach (var plcClient in _s7PlcClients.Values) { if (plcClient.IsConnected) @@ -285,7 +330,7 @@ namespace PMSWPF.Services } } - _s7PlcClients.Clear(); + _s7PlcClients.Clear(); // 清空PLC客户端字典。 await base.StopAsync(stoppingToken); } diff --git a/ViewModels/DevicesViewModel.cs b/ViewModels/DevicesViewModel.cs index 1c6e4e6..870521a 100644 --- a/ViewModels/DevicesViewModel.cs +++ b/ViewModels/DevicesViewModel.cs @@ -53,7 +53,7 @@ public partial class DevicesViewModel : ViewModelBase _dataServices = dataServices; MessageHelper.SendLoadMessage(LoadTypes.Devices); - _dataServices.OnDeviceListChanged += (devices) => { Devices = new ObservableCollection(devices); }; + _dataServices.OnDeviceListChanged += (sender, devices) => { Devices = new ObservableCollection(devices); }; } /// diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 8aad9ec..ae91bfc 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -61,7 +61,7 @@ public partial class MainViewModel : ViewModelBase // 发送消息加载数据 MessageHelper.SendLoadMessage(LoadTypes.All); // 当菜单加载成功后,在前台显示菜单 - dataServices.OnMenuTreeListChanged += (menus) => { Menus = new ObservableCollection(menus); }; + dataServices.OnMenuTreeListChanged += (sender, menus) => { Menus = new ObservableCollection(menus); }; } /// diff --git a/ViewModels/MqttsViewModel.cs b/ViewModels/MqttsViewModel.cs index f5389c3..0a44896 100644 --- a/ViewModels/MqttsViewModel.cs +++ b/ViewModels/MqttsViewModel.cs @@ -43,7 +43,7 @@ public partial class MqttsViewModel : ViewModelBase } - _dataServices.OnMqttListChanged += (mqtts) => + _dataServices.OnMqttListChanged += (sender, mqtts) => { Mqtts = new ObservableCollection(mqtts); };