Files
XplorePlane/XP.Hardware.PLC/Documents/README.md
T

347 lines
13 KiB
Markdown
Raw 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.
# 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_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
```csharp
// 初始化连接 | 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` 接口,外部模块仅需使用信号逻辑名称即可完成读写,无需关心物理地址:
```csharp
// 注入 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 仍保留底层泛型读写方法,适用于不通过信号定义的场景:
```csharp
// 泛型读取 | 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
```csharp
// 连接状态(可绑定到 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
<?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](./App.config.example)
### 2. 注册模块
```csharp
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<PLCModule>();
}
```
### 3. 使用信号数据服务(推荐)
```csharp
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` 具体类:
```csharp
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
```csharp
protected override void OnExit(ExitEventArgs e)
{
var plcService = Container.Resolve<PlcService>();
plcService?.Dispose(); // 停止批量读取定时任务、清空缓存、释放连接
base.OnExit(e);
}
```
---
## 文档索引 | Documentation Index
- **[GUIDENCE.md](./GUIDENCE.md)** - 详细使用指南,包含完整代码示例
- **[App.config.example](./App.config.example)** - 配置文件示例
- **[README.md](./README.md)** - 本文档,项目概述和快速参考
---
**最后更新 | Last Updated**: 2026-04-14