# 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 对应一个 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("PlcLive"); bool isReady = _signalService.GetValueByName("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("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` 文件定义,采用 `` 分组结构,由 `XmlSignalParser` 解析: ```xml ``` ### 读取路由规则 | Read Routing Rules `GetValueByName` 根据以下条件自动选择读取路径: | 条件 | 路径 | |------|------| | `signal.DBNumber == ReadDbBlock 块号` 且 `signal.StartAddr` 在 `[ReadStartAddress, ReadStartAddress+ReadLength-1]` 范围内 | 批量缓存读取(无 PLC 通讯) | | 其他情况(不同 DB 块,或同 DB 块但地址超出缓存范围) | 单点读取(`IPlcClient.ReadAsync`) | ### 支持的信号类型 | 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(); } ``` ### 3. 使用信号数据服务(推荐) ```csharp public class YourViewModel : BindableBase { private readonly ISignalDataService _signalService; public YourViewModel(ISignalDataService signalService) { _signalService = signalService; } public byte ReadPlcLive() { // 自动路由:PlcLive 在批量读取范围内走缓存,否则走单点读取 return _signalService.GetValueByName("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?.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