diff --git a/Data/Repositories/MenuRepository.cs b/Data/Repositories/MenuRepository.cs index 5b212e0..40eaf43 100644 --- a/Data/Repositories/MenuRepository.cs +++ b/Data/Repositories/MenuRepository.cs @@ -30,6 +30,7 @@ public class MenuRepository public async Task DeleteMenu(MenuBean menu, SqlSugarClient db) { + Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); var childList = await db.Queryable() diff --git a/Data/Repositories/MqttRepository.cs b/Data/Repositories/MqttRepository.cs index eac7d3c..7f72584 100644 --- a/Data/Repositories/MqttRepository.cs +++ b/Data/Repositories/MqttRepository.cs @@ -158,7 +158,11 @@ public class MqttRepository .ExecuteCommandAsync(); // Delete menu entry var menu = await _menuRepository.GetMenuByDataId(mqtt.Id, MenuType.MqttMenu); - await _menuRepository.DeleteMenu(menu, db); + if (menu!=null ) + { + await _menuRepository.DeleteMenu(menu, db); + } + await db.CommitTranAsync(); stopwatch.Stop(); NlogHelper.Info($"删除Mqtt配置ID '{mqtt.Id}' 耗时:{stopwatch.ElapsedMilliseconds}ms"); diff --git a/Helper/NotificationHelper.cs b/Helper/NotificationHelper.cs index 81cf43c..6d1ddd1 100644 --- a/Helper/NotificationHelper.cs +++ b/Helper/NotificationHelper.cs @@ -154,4 +154,19 @@ public static class NotificationHelper { SendNotificationInternal(msg, NotificationType.Info, throttle, null, callerFilePath, callerMember, callerLineNumber); } + /// + /// 显示一个信息通知消息,并记录信息日志。支持节流。 + /// + /// 信息消息内容。 + /// 是否启用通知和日志节流。 + /// 自动捕获:调用此方法的源文件完整路径。 + /// 自动捕获:调用此方法的成员或属性名称。 + /// 自动捕获:调用此方法的行号。 + public static void ShowWarn(string msg, bool throttle = true, + [CallerFilePath] string callerFilePath = "", + [CallerMemberName] string callerMember = "", + [CallerLineNumber] int callerLineNumber = 0) + { + SendNotificationInternal(msg, NotificationType.Warning, throttle, null, callerFilePath, callerMember, callerLineNumber); + } } diff --git a/Services/MqttBackgroundService.cs b/Services/MqttBackgroundService.cs index 87495ca..3040018 100644 --- a/Services/MqttBackgroundService.cs +++ b/Services/MqttBackgroundService.cs @@ -26,13 +26,15 @@ namespace PMSWPF.Services private readonly Dictionary _mqttClients; // 存储MQTT配置的字典,键为MQTT配置ID,值为Mqtt模型对象。 - private readonly Dictionary _mqttConfigurations; + private readonly Dictionary _mqttConfigDic; - // 存储与MQTT配置关联的变量数据的字典,键为MQTT配置ID,值为VariableData列表。 - private readonly Dictionary> _mqttVariableData; // 定时器,用于周期性地执行数据发布任务。 private Timer _timer; + private Thread _serviceMainThread; + + private ManualResetEvent _reloadEvent = new ManualResetEvent(false); + private ManualResetEvent _stopEvent = new ManualResetEvent(false); /// /// 构造函数,注入DataServices。 @@ -42,8 +44,7 @@ namespace PMSWPF.Services { _dataServices = dataServices; _mqttClients = new Dictionary(); - _mqttConfigurations = new Dictionary(); - _mqttVariableData = new Dictionary>(); + _mqttConfigDic = new Dictionary(); } /// @@ -51,151 +52,83 @@ namespace PMSWPF.Services /// public async void StartService() { - NlogHelper.Info("MqttBackgroundService started."); // 记录服务启动信息 - // 订阅MQTT列表和变量数据变化的事件,以便在数据更新时重新加载配置和数据。 _dataServices.OnMqttListChanged += HandleMqttListChanged; - _dataServices.OnDeviceListChanged += HandleDeviceListChanged; - - // 初始加载MQTT配置和变量数据。 - await LoadMqttConfigurations(); - await LoadVariableData(); - - // 初始化定时器,每5秒执行一次DoWork方法,用于周期性地发布数据。 - _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); // 每5秒轮询一次 - - // 使服务保持运行,直到收到停止请求。 - // await Task.Delay(Timeout.Infinite, stoppingToken); + NlogHelper.Info("Mqtt后台服务启动"); // 记录服务启动信息 + _reloadEvent.Set(); + _stopEvent.Reset(); + _serviceMainThread = new Thread(Execute); + _serviceMainThread.IsBackground = true; + _serviceMainThread.Name = "MqttServiceMainThread"; + _serviceMainThread.Start(); + } - private async void HandleDeviceListChanged( List devices) + private void Execute() { - NlogHelper.Info("Variable data changed. Reloading variable associations."); // 记录变量数据变化信息 - // 重新加载变量数据。 - await LoadVariableData(); + while (!_stopEvent.WaitOne(0)) + { + if (_dataServices.Mqtts==null || _dataServices.Mqtts.Count==0) + { + _reloadEvent.Reset(); + continue; + } + + _reloadEvent.WaitOne(); + // 初始加载MQTT配置和变量数据。 + LoadMqttConfigurations(); + ConnectMqttClient(); + + _reloadEvent.Reset(); + } } + /// /// 停止MQTT后台服务。 /// - public async void StopService() + public void StopService() { - NlogHelper.Info("MqttBackgroundService stopping."); // 记录服务停止信息 - - // 停止定时器。 - _timer?.Change(Timeout.Infinite, 0); + NlogHelper.Info("Mqtt后台服务开始停止...."); // 记录服务停止信息 + _stopEvent.Set(); // 取消订阅事件。 _dataServices.OnMqttListChanged -= HandleMqttListChanged; - _dataServices.OnDeviceListChanged -= HandleDeviceListChanged; // 断开所有已连接的MQTT客户端。 - foreach (var client in _mqttClients.Values) + foreach (var mqttId in _mqttClients.Keys.ToList()) { + var client=_mqttClients[mqttId]; + var mqtt=_mqttConfigDic[mqttId]; + mqtt.IsConnected = false; if (client.IsConnected) { - await client.DisconnectAsync(); + client.DisconnectAsync().GetAwaiter().GetResult(); } } // 清空所有字典。 _mqttClients.Clear(); - _mqttConfigurations.Clear(); - _mqttVariableData.Clear(); + _mqttConfigDic.Clear(); + NlogHelper.Info("Mqtt后台服务已停止。"); // 记录服务停止信息 } - /// - /// 定时器回调方法,用于周期性地检查并发布已修改的变量数据。 - /// - /// 定时器状态对象(此处未使用)。 - 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); - NlogHelper.Info( - $"Published {variable.Name} = {variable.DataValue} to {topic}/{variable.Name}", - throttle: true); // 记录发布信息 - variable.IsModified = false; // 发布后重置修改标志。 - } - } - } - } - } - } /// /// 加载并连接MQTT配置。 /// /// 表示异步操作的任务。 - private async Task LoadMqttConfigurations() + private void LoadMqttConfigurations() { + NlogHelper.Info("开始加载Mqtt配置文件..."); // 从数据服务获取所有MQTT配置。 - var allMqtts = await _dataServices.GetMqttsAsync(); - var activeMqtts = allMqtts.Where(m => m.IsActive) - .ToList(); - var activeMqttIds = activeMqtts.Select(m => m.Id) - .ToHashSet(); - - // 断开并移除不再活跃或已删除的MQTT客户端。 - var clientsToDisconnect = _mqttClients.Keys.Except(activeMqttIds) - .ToList(); - foreach (var id in clientsToDisconnect) + var _mqttConfigList = _dataServices.Mqtts.Where(m => m.IsActive) + .ToList(); + foreach (var mqtt in _mqttConfigList) { - if (_mqttClients.TryGetValue(id, out var client)) - { - if (client.IsConnected) - { - await client.DisconnectAsync(); - // 更新模型中的连接状态 - if (_mqttConfigurations.TryGetValue(id, out var mqttConfig)) - { - mqttConfig.IsConnected = false; - } - } - - _mqttClients.Remove(id); - NlogHelper.Info( - $"Disconnected and removed MQTT client for ID: {id} (no longer active or removed)."); - } - - _mqttConfigurations.Remove(id); - _mqttVariableData.Remove(id); - } - - // 连接或更新活跃的客户端。 - foreach (var mqtt in activeMqtts) - { - if (!_mqttClients.ContainsKey(mqtt.Id)) - { - await ConnectMqttClient(mqtt); - } - - // 始终更新或添加MQTT配置到字典。 - _mqttConfigurations[mqtt.Id] = mqtt; + _mqttConfigDic[mqtt.Id] = mqtt; } + NlogHelper.Info($"Mqtt配置文件加载成功,开启的Mqtt客户端:{_mqttConfigList.Count}个。"); } /// @@ -203,103 +136,78 @@ namespace PMSWPF.Services /// /// MQTT配置对象。 /// 表示异步操作的任务。 - private async Task ConnectMqttClient(Mqtt mqtt) + private void ConnectMqttClient() { - try + foreach (Mqtt mqtt in _mqttConfigDic.Values.ToList()) { - // 创建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 => + try { - NlogHelper.Info($"Connected to MQTT broker: {mqtt.Name}"); - NotificationHelper.ShowSuccess($"已连接到MQTT服务器: {mqtt.Name}"); - mqtt.IsConnected = true; - }); + NlogHelper.Info($"开始连接:{mqtt.Name}的服务器..."); + // 创建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.UseDisconnectedHandler(async e => - { - NlogHelper.Warn($"Disconnected from MQTT broker: {mqtt.Name}. Reason: {e.Reason}"); - NotificationHelper.ShowInfo($"与MQTT服务器断开连接: {mqtt.Name}"); - mqtt.IsConnected = false; - // 尝试重新连接。 - await Task.Delay(TimeSpan.FromSeconds(5)); // 等待5秒后重连 - try + // 设置连接成功事件处理程序。 + client.UseConnectedHandler(e => { - await client.ConnectAsync(options, CancellationToken.None); - } - catch (Exception ex) + NotificationHelper.ShowSuccess($"已连接到MQTT服务器: {mqtt.Name}"); + mqtt.IsConnected = true; + }); + + // 设置断开连接事件处理程序。 + client.UseDisconnectedHandler(async e => { - NlogHelper.Error($"Failed to reconnect to MQTT broker: {mqtt.Name}", ex); - } - }); - - // 尝试连接到MQTT代理。 - await client.ConnectAsync(options, CancellationToken.None); - // 将连接成功的客户端添加到字典。 - _mqttClients[mqtt.Id] = client; - } - catch (Exception ex) - { - NotificationHelper.ShowError($"连接MQTT服务器失败: {mqtt.Name} - {ex.Message}", ex); - } - } - - /// - /// 加载所有变量数据并按MQTT配置ID进行分组。 - /// - /// 表示异步操作的任务。 - private async Task LoadVariableData() - { - // 从数据服务获取所有变量数据。 - var allVariables = _dataServices.VariableDatas; - if (!allVariables.Any()) - return; - _mqttVariableData.Clear(); // 清空现有数据 - - // 遍历所有变量,并根据其关联的MQTT配置进行分组。 - foreach (var variable in allVariables) - { - if (variable.Mqtts != null) - { - foreach (var mqtt in variable.Mqtts) - { - // 如果字典中没有该MQTT配置的条目,则创建一个新的列表。 - if (!_mqttVariableData.ContainsKey(mqtt.Id)) + NotificationHelper.ShowWarn($"与MQTT服务器断开连接: {mqtt.Name}"); + mqtt.IsConnected = false; + // 服务停止 + if (_stopEvent.WaitOne(0)) + return; + + NlogHelper.Info($"5秒后重新连接Mqtt服务器:{mqtt.Name}"); + // 尝试重新连接。 + await Task.Delay(TimeSpan.FromSeconds(5)); // 等待5秒后重连 + try { - _mqttVariableData[mqtt.Id] = new List(); + await client.ConnectAsync(options, CancellationToken.None); } + catch (Exception ex) + { + NlogHelper.Error($"重新与Mqtt服务器连接失败: {mqtt.Name}", ex); + } + }); - // 将变量添加到对应MQTT配置的列表中。 - _mqttVariableData[mqtt.Id] - .Add(variable); - } + // 尝试连接到MQTT代理。 + client.ConnectAsync(options, CancellationToken.None) + .GetAwaiter() + .GetResult(); + // 将连接成功的客户端添加到字典。 + _mqttClients[mqtt.Id] = client; + } + catch (Exception ex) + { + NotificationHelper.ShowError($"连接MQTT服务器失败: {mqtt.Name} - {ex.Message}", ex); } } } + /// /// 处理MQTT列表变化事件的回调方法。 /// /// 事件发送者。 /// 更新后的MQTT配置列表。 - private async void HandleMqttListChanged( List mqtts) + private async void HandleMqttListChanged(List mqtts) { - NlogHelper.Info("MQTT list changed. Reloading configurations."); // 记录MQTT列表变化信息 + NlogHelper.Info("Mqtt列表发生了变化,正在重新加载数据..."); // 记录MQTT列表变化信息 // 重新加载MQTT配置和变量数据。 - await LoadMqttConfigurations(); - await LoadVariableData(); // 重新加载变量数据,以防关联发生变化 + _reloadEvent.Set(); } - } } \ No newline at end of file diff --git a/Services/S7BackgroundService.cs b/Services/S7BackgroundService.cs index 1e591d6..b410c3b 100644 --- a/Services/S7BackgroundService.cs +++ b/Services/S7BackgroundService.cs @@ -11,6 +11,7 @@ using PMSWPF.Models; using PMSWPF.Enums; using PMSWPF.Helper; using S7.Net.Types; +using SqlSugar; using DateTime = System.DateTime; namespace PMSWPF.Services @@ -280,6 +281,7 @@ namespace PMSWPF.Services int varCount = 0; foreach (var device in _s7Devices) { + device.IsRuning = true; _deviceDic.Add(device.Id, device); // 过滤出当前设备和S7协议相关的变量。 var s7Variables = device.VariableTables @@ -324,7 +326,12 @@ namespace PMSWPF.Services _pollingThread.Interrupt(); _serviceMainThread.Interrupt(); DisconnectAllPlc(); - + + foreach (Device device in _deviceDic.Values.ToList()) + { + device.IsRuning = false; + } + // 关闭事件 _reloadEvent.Close(); _stopEvent.Reset(); _stopEvent.Close(); diff --git a/Views/DevicesView.xaml b/Views/DevicesView.xaml index a42174f..2b0c7d4 100644 --- a/Views/DevicesView.xaml +++ b/Views/DevicesView.xaml @@ -24,6 +24,18 @@ Opacity="0.1" BlurRadius="5" /> + + + + + @@ -35,7 +47,7 @@