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

6.4 KiB
Raw Blame History

软件开发文档 - S7后台服务设计

本文档详细阐述了S7协议后台服务的设计。此修订版采纳了**“编排者-代理” (Orchestrator-Agent)** 模式,并与中央的 IChannelBus 服务集成,以实现高效、健壮且动态的设备通信。

1. 核心架构

  • S7BackgroundService (编排者): 一个单例的托管服务 (IHostedService)作为所有S7通信的总指挥。它不直接与PLC交互而是负责创建、管理和销毁每个设备专属的 S7DeviceAgent
  • S7DeviceAgent (代理): 一个专门负责与单个S7 PLC进行所有通信的代理类。它管理着与该PLC的连接并根据不同的轮询率并行地读取变量。
  • DataProcessingService (消费者): 一个独立的消费者服务,它从 IChannelBus 获取数据处理队列并执行后续的数据处理链和UI通知。

2. DMS.Core 模型扩展

为支持按频率轮询和存储设备连接参数,DeviceVariable 模型需要扩展。

// 文件: 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 获取队列写入器,实现了与队列管理逻辑的解耦。

// 文件: 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 并将其传递给它创建的每一个代理。

// 文件: 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 获取队列读取器。

// 文件: 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 的热重载逻辑。