按照软件设计文档开始重构代码01
This commit is contained in:
173
软件设计文档/原始文档/07-S7后台服务设计.md
Normal file
173
软件设计文档/原始文档/07-S7后台服务设计.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 软件开发文档 - 07. S7后台服务设计 (修订版)
|
||||
|
||||
本文档详细阐述了S7协议后台服务的设计。此修订版采纳了**“编排者-代理” (Orchestrator-Agent)** 模式,并与中央的 **`IChannelBus`** 服务集成,以实现高效、健壮且动态的设备通信。
|
||||
|
||||
## 1. 核心架构
|
||||
|
||||
* **`S7BackgroundService` (编排者)**: 一个单例的托管服务 (`IHostedService`),作为所有S7通信的总指挥。它不直接与PLC交互,而是负责创建、管理和销毁每个设备专属的 `S7DeviceAgent`。
|
||||
* **`S7DeviceAgent` (代理)**: 一个专门负责与**单个**S7 PLC进行所有通信的代理类。它管理着与该PLC的连接,并根据不同的轮询率并行地读取变量。
|
||||
* **`DataProcessingService` (消费者)**: 一个独立的消费者服务,它从 `IChannelBus` 获取数据处理队列,并执行后续的数据处理链和UI通知。
|
||||
|
||||
## 2. `DMS.Core` 模型扩展
|
||||
|
||||
为支持按频率轮询和存储设备连接参数,`Device` 和 `Variable` 模型需要扩展。
|
||||
|
||||
```csharp
|
||||
// 文件: DMS.Core/Enums/PollLevelType.cs (新增)
|
||||
public enum PollLevelType { Off, High, Medium, Low }
|
||||
|
||||
// 文件: DMS.Core/Models/Device.cs (修改)
|
||||
public class Device
|
||||
{
|
||||
// ...
|
||||
public int Rack { get; set; } // 机架号
|
||||
public int Slot { get; set; } // 槽号
|
||||
}
|
||||
|
||||
// 文件: DMS.Core/Models/Variable.cs (修改)
|
||||
public class Variable
|
||||
{
|
||||
// ...
|
||||
public PollLevelType PollLevel { get; set; }
|
||||
|
||||
[NotMapped] // 此属性不在数据库中,仅用于运行时
|
||||
public object DataValue { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
## 3. `DMS.Infrastructure` 服务实现
|
||||
|
||||
### 3.1. `S7DeviceAgent.cs` (代理)
|
||||
|
||||
代理类现在通过 `IChannelBus` 获取队列写入器,实现了与队列管理逻辑的解耦。
|
||||
|
||||
```csharp
|
||||
// 文件: DMS.Infrastructure/Services/S7DeviceAgent.cs
|
||||
using DMS.WPF.Services; // 引用 IChannelBus
|
||||
using System.Threading.Channels;
|
||||
|
||||
public class S7DeviceAgent : IAsyncDisposable
|
||||
{
|
||||
private readonly Device _deviceConfig;
|
||||
private readonly ChannelWriter<VariableContext> _processingQueueWriter;
|
||||
private readonly Plc _plcClient;
|
||||
private CancellationTokenSource _cts;
|
||||
private List<Variable> _highFreqVars, _mediumFreqVars, _lowFreqVars;
|
||||
|
||||
public S7DeviceAgent(Device device, IChannelBus channelBus, IMessenger messenger)
|
||||
{
|
||||
_deviceConfig = device;
|
||||
_plcClient = new Plc(/*...*/);
|
||||
|
||||
// 从中央总线获取指定名称的通道的写入端
|
||||
_processingQueueWriter = channelBus.GetWriter<VariableContext>("DataProcessingQueue");
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken token = default) { /*...*/ }
|
||||
|
||||
// 热重载方法,用于响应配置变更
|
||||
public void UpdateVariableLists(List<Variable> allActiveVariables) { /*...*/ }
|
||||
|
||||
private async Task PollingLoopAsync(List<Variable> varsToRead, int interval, CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
if (!varsToRead.Any()) { await Task.Delay(interval, token); continue; }
|
||||
try
|
||||
{
|
||||
await _plcClient.ReadMultipleVarsAsync(varsToRead);
|
||||
foreach (var variable in varsToRead)
|
||||
{
|
||||
var context = new VariableContext(variable, variable.DataValue);
|
||||
await _processingQueueWriter.WriteAsync(context, token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { /* Log error */ }
|
||||
await Task.Delay(interval, token);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() { /*...*/ }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2. `S7BackgroundService.cs` (编排者)
|
||||
|
||||
编排者注入 `IChannelBus` 并将其传递给它创建的每一个代理。
|
||||
|
||||
```csharp
|
||||
// 文件: DMS.Infrastructure/Services/S7BackgroundService.cs
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
public class S7BackgroundService : IHostedService
|
||||
{
|
||||
private readonly IRepositoryManager _repoManager;
|
||||
private readonly IChannelBus _channelBus;
|
||||
private readonly IMessenger _messenger;
|
||||
private readonly ConcurrentDictionary<int, S7DeviceAgent> _activeAgents = new();
|
||||
|
||||
public S7BackgroundService(IRepositoryManager repo, IChannelBus channelBus, IMessenger messenger)
|
||||
{
|
||||
_repoManager = repo;
|
||||
_channelBus = channelBus;
|
||||
_messenger = messenger;
|
||||
_messenger.Register<ConfigChangedMessage>(this, async (r, m) => await HandleConfigChange(m));
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var s7Devices = await _repoManager.Devices.GetActiveDevicesWithDetailsAsync(ProtocolType.S7);
|
||||
foreach (var device in s7Devices)
|
||||
{
|
||||
var agent = new S7DeviceAgent(device, _channelBus, _messenger);
|
||||
if (_activeAgents.TryAdd(device.Id, agent))
|
||||
{
|
||||
await agent.StartAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动数据处理消费者
|
||||
var dataProcessor = new DataProcessingService(_channelBus, _messenger, _repoManager);
|
||||
_ = dataProcessor.StartProcessingAsync(cancellationToken); // 在后台运行,不阻塞启动
|
||||
}
|
||||
|
||||
private async Task HandleConfigChange(ConfigChangedMessage message) { /* ... */ }
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3. `DataProcessingService.cs` (消费者)
|
||||
|
||||
消费者同样从 `IChannelBus` 获取队列读取器。
|
||||
|
||||
```csharp
|
||||
// 文件: DMS.Infrastructure/Services/DataProcessingService.cs
|
||||
public class DataProcessingService
|
||||
{
|
||||
private readonly ChannelReader<VariableContext> _queueReader;
|
||||
private readonly IMessenger _messenger;
|
||||
// ... 注入处理链需要的服务
|
||||
|
||||
public DataProcessingService(IChannelBus channelBus, IMessenger messenger, IRepositoryManager repo)
|
||||
{
|
||||
_queueReader = channelBus.GetReader<VariableContext>("DataProcessingQueue");
|
||||
_messenger = messenger;
|
||||
// ...
|
||||
}
|
||||
|
||||
public async Task StartProcessingAsync(CancellationToken token)
|
||||
{
|
||||
await foreach (var context in _queueReader.ReadAllAsync(token))
|
||||
{
|
||||
// 1. 执行数据处理链...
|
||||
// 2. 发送UI更新消息
|
||||
_messenger.Send(new VariableValueUpdatedMessage(context.Variable.Id, context.CurrentValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. UI层 (`DMS.WPF`)
|
||||
|
||||
UI层需要定义 `VariableItemViewModel` 来响应 `VariableValueUpdatedMessage` 消息,从而实现实时数据显示。当用户在前台修改配置(如添加变量)后,应发送 `ConfigChangedMessage` 消息,`S7BackgroundService` 会捕获此消息并触发对应 `S7DeviceAgent` 的热重载逻辑。
|
||||
Reference in New Issue
Block a user