13 KiB
13 KiB
XP.Hardware.PLC
工业 PLC 通讯模块 | Industrial PLC Communication Module
项目概述 | Project Overview
XP.Hardware.PLC 是 XplorePlane X 射线检测系统的 PLC 通讯模块,提供与西门子 S7 系列 PLC 的实时数据交互能力。该模块采用模块化设计,支持多种 PLC 型号,提供统一的异步读写接口、自动重连机制,以及基于信号逻辑名称的高级数据交互服务。
主要特性 | Key Features
- 支持西门子 S7 系列 PLC(S200Smart/S300/S400/S1200/S1500)
- 异步读写操作,不阻塞 UI 线程
- 自动连接监控和断线重连
- 类型安全的泛型读写接口
- 信号数据交互服务(ISignalDataService):通过 XML 信号逻辑名称读写 PLC 数据,业务模块无需关心物理地址
- 跨模块连接状态接口(IPlcService):供其他硬件模块查询 PLC 连接状态,支持 INotifyPropertyChanged
- 多 DB 块支持:XML 信号配置基于
<Group>分组结构,每个 Group 对应一个 DB 块,支持同时读写多个 DB 块 - 自动读取路由:信号地址落在批量读取范围内走缓存解析,否则自动走单点读取
- 周期性批量读取缓存(Bulk_Read_Cache):PlcService 以 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 实现 - 单例模式:
PlcService、PlcWriteQueue、SignalDataService作为全局单例 - 依赖注入:通过 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 秒检测连接状态,断开时自动重连
2. 信号数据交互(推荐方式)| Signal Data Interaction (Recommended)
通过 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. 配置文件设置
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
- GUIDENCE.md - 详细使用指南,包含完整代码示例
- App.config.example - 配置文件示例
- README.md - 本文档,项目概述和快速参考
最后更新 | Last Updated: 2026-04-14