Files
XplorePlane/XP.Hardware.PLC/Documents

XP.Hardware.PLC

工业 PLC 通讯模块 | Industrial PLC Communication Module


项目概述 | Project Overview

XP.Hardware.PLC 是 XplorePlane X 射线检测系统的 PLC 通讯模块,提供与西门子 S7 系列 PLC 的实时数据交互能力。该模块采用模块化设计,支持多种 PLC 型号,提供统一的异步读写接口、自动重连机制,以及基于信号逻辑名称的高级数据交互服务。

主要特性 | Key Features

  • 支持西门子 S7 系列 PLCS200Smart/S300/S400/S1200/S1500
  • 异步读写操作,不阻塞 UI 线程
  • 自动连接监控和断线重连
  • 类型安全的泛型读写接口
  • 信号数据交互服务(ISignalDataService:通过 XML 信号逻辑名称读写 PLC 数据,业务模块无需关心物理地址
  • 跨模块连接状态接口(IPlcService:供其他硬件模块查询 PLC 连接状态,支持 INotifyPropertyChanged
  • 多 DB 块支持XML 信号配置基于 <Group> 分组结构,每个 Group 对应一个 DB 块,支持同时读写多个 DB 块
  • 自动读取路由:信号地址落在批量读取范围内走缓存解析,否则自动走单点读取
  • 周期性批量读取缓存(Bulk_Read_CachePlcService 以 100ms 周期批量读取指定 DB 块并缓存,供高频信号解析
  • 写入队列(PlcWriteQueue:后台单线程顺序写入,避免并发冲突
  • 直接写入+回读校验:高优先级写入通道,绕过队列并回读验证数据准确性
  • XML 信号地址定义解析(XmlSignalParser)和数据块解析(PlcDataBlock
  • 完整的日志记录和异常处理
  • 基于 Prism 的依赖注入和模块化架构

框架架构 | Architecture

XP.Hardware.PLC/
├── Abstractions/              # 抽象层 | Abstraction Layer
│   ├── IPlcClient.cs         # PLC 客户端接口
│   ├── IPlcService.cs        # PLC 服务接口(跨模块连接状态查询)
│   └── ISignalDataService.cs # 信号数据交互服务接口(对外暴露)
├── Core/                      # 核心实现 | Core Implementation
│   └── S7PlcClient.cs        # 西门子 S7 PLC 客户端
├── Services/                  # 服务层 | Service Layer
│   ├── PlcService.cs         # PLC 业务服务(单例):连接管理 + 批量读取缓存 + 统一信号字典,实现 IPlcService
│   ├── PlcWriteQueue.cs      # PLC 写入队列(单例):后台单线程顺序写入
│   └── SignalDataService.cs  # 信号数据交互服务实现(单例):自动路由读取 / 队列写入 / 直接写入+回读
├── Configs/                   # 配置层 | Configuration Layer
│   └── PlcConfig.cs          # PLC 配置模型(已移除 WriteDbBlock
├── Models/                    # 数据模型 | Data Models
│   ├── SignalEntry.cs        # 信号条目模型(Name, Type, StartAddr, IndexOrLength, GroupId, DBNumber
│   └── SignalGroup.cs        # 信号分组模型(GroupId, DBNumber, Signals
├── Helpers/                   # 辅助工具 | Helpers
│   ├── ConfigLoader.cs       # 配置加载器
│   ├── PlcHelper.cs          # PLC 工具类
│   ├── PlcDataBlock.cs       # PLC 数据块解析器(大端字节序)
│   ├── XmlSignalParser.cs    # XML 信号地址定义解析器(Group 分组格式)
│   └── HexFormatter.cs       # 十六进制格式化工具
├── Exceptions/                # 异常定义 | Exceptions
│   └── PlcException.cs       # PLC 自定义异常
├── Resources/                 # 多语言资源 | Localization Resources
├── ViewModels/                # 视图模型 | View Models
├── Views/                     # WPF 视图 | WPF Views
├── Documents/                 # 文档 | Documentation
└── PLCModule.cs              # Prism 模块注册

三通道架构 | Three-Channel Architecture

模块采用三个独立的 IPlcClient 实例(S7PlcClient),各自管理独立的 PLC 连接,避免并发竞争:

通道 持有者 用途
主通讯通道 PlcService 周期性批量读取(100ms 间隔)
写入队列通道 PlcWriteQueue 后台单线程顺序写入
直接写入通道 SignalDataService 高优先级写入+回读校验

设计模式 | Design Patterns

  • 策略模式IPlcClient 接口支持多种 PLC 实现
  • 单例模式PlcServicePlcWriteQueueSignalDataService 作为全局单例
  • 依赖注入:通过 Prism 容器管理服务生命周期
  • 异步模式:所有 I/O 操作采用 async/await
  • 生产者-消费者模式PlcWriteQueue 使用 BlockingCollection 实现队列写入

核心功能 | Core Features

1. 连接管理 | Connection Management

// 初始化连接 | Initialize connection
var config = _configLoader.LoadPlcConfig();
bool success = await _plcService.InitializeAsync(config);

// 加载信号定义(连接成功后)| Load signal definitions (after connection)
_plcService.LoadSignalDefinitions("PlcAddrDfn.xml");

// 自动重连(配置启用后)| Auto-reconnect (when enabled)
// 每 5 秒检测连接状态,断开时自动重连

通过 ISignalDataService 接口,外部模块仅需使用信号逻辑名称即可完成读写,无需关心物理地址:

// 注入 ISignalDataService | Inject ISignalDataService
private readonly ISignalDataService _signalService;

// 读取信号:自动路由(批量缓存 or 单点读取)| Read signal: auto-routed (cache or single-point)
object value = _signalService.GetValueByName("PlcLive");
byte plcLive = _signalService.GetValueByName<byte>("PlcLive");
bool isReady = _signalService.GetValueByName<bool>("SystemReady");

// 队列写入:提交到写入队列,后台顺序处理 | Queue write: enqueue for background sequential processing
bool enqueued = _signalService.EnqueueWrite("SoftLive", (byte)1);
bool enqueued2 = _signalService.EnqueueWrite("TargetVoltage", 150);

// 直接写入+回读校验:高优先级,绕过队列 | Direct write + read-back verify: high priority, bypasses queue
bool verified = await _signalService.WriteDirectWithVerify("EmergencyStop", true);

3. 底层直接读写 | Low-Level Direct Read/Write

PlcService 仍保留底层泛型读写方法,适用于不通过信号定义的场景:

// 泛型读取 | Generic read
int intValue = await _plcService.ReadValueAsync<int>("DB1.4");
string strValue = await _plcService.ReadStringAsync("DB1.20", 10);

// 泛型写入 | Generic write
bool success = await _plcService.WriteValueAsync("DB1.4", 12345);
bool success2 = await _plcService.WriteStringAsync("DB1.20", "Hello", 10);

4. 状态监控 | Status Monitoring

// 连接状态(可绑定到 UI| Connection status (bindable to UI)
bool isConnected = _plcService.IsConnected;

// 状态文本(可绑定到 UI| Status text (bindable to UI)
string statusText = _plcService.StatusText;

信号定义文件 | Signal Definition File

信号地址通过 PlcAddrDfn.xml 文件定义,采用 <Group> 分组结构,由 XmlSignalParser 解析:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Config>
    <!-- 组 1: 批量读取 DB(高频缓存信号)-->
    <Group ID="SignalList_Read" DBNumber="1">
        <Signal Name="PlcLive" Type="byte" StartAddr="200" IndexOrLength="" Remark="PLC 心跳" />
        <Signal Name="SystemReady" Type="bool" StartAddr="221" IndexOrLength="1" Remark="系统就绪" />
    </Group>
    <!-- 组 2: 写入 DB -->
    <Group ID="SignalList_Write" DBNumber="31">
        <Signal Name="SoftLive" Type="byte" StartAddr="0" IndexOrLength="" Remark="软件心跳" />
        <Signal Name="StartCmd" Type="bool" StartAddr="221" IndexOrLength="0" Remark="启动命令" />
        <Signal Name="DeviceName" Type="string" StartAddr="240" IndexOrLength="20" Remark="设备名称" />
    </Group>
    <!-- 组 3: 其他 DB(单点读取)-->
    <Group ID="Status" DBNumber="100">
        <Signal Name="ScanMode" Type="byte" StartAddr="201" IndexOrLength="" Remark="扫描模式" />
    </Group>
</Config>

读取路由规则 | Read Routing Rules

GetValueByName 根据以下条件自动选择读取路径:

条件 路径
signal.DBNumber == ReadDbBlock 块号signal.StartAddr[ReadStartAddress, ReadStartAddress+ReadLength-1] 范围内 批量缓存读取(无 PLC 通讯)
其他情况(不同 DB 块,或同 DB 块但地址超出缓存范围) 单点读取(IPlcClient.ReadAsync<T>

支持的信号类型 | Supported Signal Types

SignalEntry.Type CLR 类型 字节长度 IndexOrLength 含义
bool bool 1 bit 位索引(0-7
byte byte 1 不使用
short short 2 不使用
int int 4 不使用
single float 4 不使用
double double 8 不使用
string string 可变 字符串长度(字节数)

信号名称全局唯一性 | Global Signal Name Uniqueness

所有 Group 中的信号名称必须全局唯一。LoadSignalDefinitions 加载时会检查,重复时抛出 PlcException


技术要求 | Technical Requirements

运行环境 | Runtime Environment

  • .NET 8.0 (net8.0-windows7.0)
  • Windows 操作系统WPF 依赖)
  • Visual Studio 2022 或更高版本

核心依赖 | Core Dependencies

依赖库 版本 用途
Prism.Wpf 9.0.537 MVVM 框架和依赖注入
HslCommunication - 西门子 PLC 通讯库
Serilog - 结构化日志记录(通过 XP.Common)

PLC 硬件要求 | PLC Hardware Requirements

  • 支持的 PLC 型号:S7-200Smart / S7-300 / S7-400 / S7-1200 / S7-1500
  • 网络连接:TCP/IP(默认端口 102)
  • PLC 需开启 S7 通讯功能

快速开始 | Quick Start

1. 配置文件设置

参见 App.config.example

2. 注册模块

protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
    moduleCatalog.AddModule<PLCModule>();
}

3. 使用信号数据服务(推荐)

public class YourViewModel : BindableBase
{
    private readonly ISignalDataService _signalService;

    public YourViewModel(ISignalDataService signalService)
    {
        _signalService = signalService;
    }

    public byte ReadPlcLive()
    {
        // 自动路由:PlcLive 在批量读取范围内走缓存,否则走单点读取
        return _signalService.GetValueByName<byte>("PlcLive");
    }

    public bool WriteSoftLive(byte value)
    {
        return _signalService.EnqueueWrite("SoftLive", value);
    }
}

4. 跨模块查询 PLC 连接状态

其他硬件模块可通过 IPlcService 接口查询 PLC 连接状态,无需依赖 PlcService 具体类:

using XP.Hardware.Plc.Abstractions;

public class YourHardwareService
{
    private readonly IPlcService _plcService;

    public YourHardwareService(IPlcService plcService)
    {
        _plcService = plcService;
    }

    public void DoWork()
    {
        // 检查 PLC 连接状态 | Check PLC connection status
        if (!_plcService.IsConnected) return;

        // 监听连接状态变化(INotifyPropertyChanged| Listen for connection changes
        _plcService.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(IPlcService.IsConnected))
            {
                // 连接状态变化处理 | Handle connection change
            }
        };
    }
}

异常处理 | Exception Handling

模块使用自定义 PlcException 异常,覆盖以下场景:

场景 行为
信号名称未找到 抛出 PlcException
数据缓存未就绪 抛出 PlcException
单点读取失败 抛出 PlcException(含信号名、DB 块号、错误原因)
类型转换失败 抛出 PlcException
信号名称全局重复 LoadSignalDefinitions 抛出 PlcException
批量读取失败 保留上次缓存,记录 Error 日志
写入队列已满/已停止 EnqueueWrite 返回 false
直接写入通道未连接 WriteDirectWithVerify 返回 false
回读值与写入值不一致 WriteDirectWithVerify 返回 false

日志记录 | Logging

  • Debug:信号读取结果(含路由路径)、队列写入详情
  • Info:连接成功、信号定义加载、直接写入校验结果
  • Warn:连接断开、重连尝试、队列未运行
  • Error:连接失败、批量读取失败、单点读取失败、类型转换失败、回读校验失败

资源释放 | Resource Disposal

protected override void OnExit(ExitEventArgs e)
{
    var plcService = Container.Resolve<PlcService>();
    plcService?.Dispose();  // 停止批量读取定时任务、清空缓存、释放连接
    base.OnExit(e);
}

文档索引 | Documentation Index


最后更新 | Last Updated: 2026-04-14