Files
DMS/软件设计文档/01.第一版设计文档/07-S7后台服务设计.md

174 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 软件开发文档 - 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` 的热重载逻辑。