# PVT **Repository Path**: erick1999/pvt ## Basic Information - **Project Name**: PVT - **Description**: 硬件测试系统 通讯模块拆分 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-06 - **Last Updated**: 2026-06-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # PVT — WPF 测试执行平台 ## 项目概述 PVT 是一个基于 WPF .NET 9.0 的多工位测试执行平台,支持插件化设备通讯、可视化脚本编辑、实时运行监控。 --- ## 软件架构设计 ### 1. 整体架构 ``` +----------------------------------------------------------+ | PVT.Wpf (主程序) | | +----------+ +----------+ +----------+ +-------------+ | | | Models | |Views/XAML| |ViewModels| | Services | | | | 数据模型 | | UI 视图 | | MVVM绑定 | | 核心服务引擎 | | | +----------+ +----------+ +----------+ +------+------+ | | | | | +---------------------+ | | | 调用 | | v | | +--------------------------------------------------+ | | | PluginLoader (插件加载/执行) | | | | 动态加载 DLL -> 发现插件 -> 缓存元数据 -> 执行操作 | | | +--------------------+-----------------------------+ | | | ICommunicationPlugin | | v | | +--------------------------------------------------+ | | | StepExecutionService (步骤执行引擎) | | | | 逐步骤解析操作码 -> 构建 DeviceConnectionInfo | | | | -> 注入 CommunicationProvider -> 调用插件执行 | | | +--------------------------------------------------+ | +----------------------------------------------------------+ | | | | 引用 | 引用 | 引用 v v v +-----------------+ +-----------------+ +-----------------+ | PluginContracts | | PVT.Communication| | Plugins/ | | (接口契约) | | (通讯库) | | (4个插件DLL) | +-----------------+ +-----------------+ +-----------------+ |ICommunication | |SerialPort | |ModbusRTUPlugin | | Plugin | | Provider | |SerialPortPlugin | |ICommunication | |SocketProvider | |SocketPlugin | | Provider | |ModbusRTUProvider| |NullPlugin | |IPluginOperation | |ProviderFactory | +-----------------+ | Descriptions | |ModbusUtils | |DeviceConnection | +-----------------+ | Info | |PluginResult | |SerialPortMutex | +-----------------+ ``` ### 2. 通讯层分离设计 (核心架构决策) #### 2.1 设计目标 将 **串口通讯、Socket 通讯、Modbus RTU/TCP 协议** 从插件中提取到主程序的共享通讯库 `PVT.Communication`,实现 **通讯与业务逻辑分离**。 #### 2.2 核心接口: ICommunicationProvider ```csharp // 定义在 PVT.PluginContracts,主程序和插件共同引用 public interface ICommunicationProvider { string CommunicationType { get; } // 通讯类型标识 Task ConnectAsync(CancellationToken token); // 建立连接 void Disconnect(); // 断开连接 bool IsConnected { get; } // 连接状态 Task SendAndReceiveAsync(byte[] data, ...); // 发送+接收字节 Task SendAsync(byte[] data, ...); // 仅发送 Task SendStringAndReceiveAsync(string msg, ...); // 发送+接收字符串 Task SendStringAsync(string msg, ...); // 仅发送字符串 } ``` #### 2.3 Provider 实现 | Provider | 通讯方式 | 内部封装 | |----------|----------|----------| | `SerialPortProvider` | 通用串口 | `System.IO.Ports.SerialPort` + `SerialPortMutex` 互斥锁 | | `SocketProvider` | TCP Socket | `TcpClient` + 连接池 + `SemaphoreSlim` 流同步 | | `ModbusRTUProvider` | Modbus RTU | 基于 `SerialPortProvider`,封装帧 CRC 校验 | #### 2.4 Provider 注入流程 调用链 ``` StepExecutionService.ExecuteAsync() │ ├─ 1️⃣ 构建设备信息 │ var deviceInfo = BuildDeviceInfo(step, program, DeviceOverride); │ // BuildDeviceInfo 内部: │ info.CommunicationProvider = CommunicationProviderFactory.TryCreate(info); │ // 根据 CommunicationType 创建对应 Provider 塞进 DeviceConnectionInfo │ ├─ 2️⃣ 传给 PluginLoader │ pluginResult = await _pluginLoader.ExecuteAsync( │ pluginName, operation, commandParam, deviceInfo, token); │ ↑ │ 包含 CommunicationProvider │ ├─ 3️⃣ PluginLoader 转发给插件实例 │ var plugin = _plugins["ModbusRTU"]; // 已加载的插件对象 │ return await plugin.ExecuteAsync(operation, parameter, deviceConfig, token); │ ↑ │ DeviceConnectionInfo │ └─ 4️⃣ 插件从 deviceConfig 取出 Provider var provider = deviceConfig?.CommunicationProvider; if (provider is null) return PluginResult.Fail("未注入通讯提供者"); await provider.ConnectAsync(token); // 打开连接 var resp = await provider.SendAndReceiveAsync(frame, token); // 收发数据 provider.Disconnect(); // 关闭连接 ``` provider逻辑 ``` ┌─────────────────────────────────────────────────────────────┐ │ DeviceConnectionInfo(只是一个属性包,主程序和插件都能访问) │ │ │ │ .PortNumber = "COM3" ← 设备参数(主程序填充) │ │ .BaudRate = 9600 │ │ .SlaveAddress = 1 │ │ .CommunicationProvider = ───────────┐ ← 关键是这个属性 │ │ │ │ │ .LogService = ... │ │ │ .DialogService = ... │ │ └──────────────────────────────────────┼──────────────────────┘ │ 主程序创建并塞进去 │ 插件取出来用 │ StepExecutionService │ ModbusRTUPlugin ┌─────────────────────┐ │ ┌──────────────────────┐ │ var info = new │ │ │ var provider = │ │ DeviceConnection │ │ │ deviceConfig. │ │ Info { ... }; │ │ │ Communication │ │ info.Communication │ ──传递──▶ │ │ Provider; │ │ Provider = │ │ │ │ │ Factory.TryCreate │ │ │ provider.SendAnd │ │ (info); │ │ │ ReceiveAsync(...) │ └─────────────────────┘ │ └──────────────────────┘ │ ┌────────────┘ ▼ CommunicationProviderFactory ┌────────────────────────────┐ │ "ModbusRTU" → new │ │ ModbusRTUProvider(info) │ │ "SerialPort" → new │ │ SerialPortProvider(...) │ │ "Socket" → new │ │ SocketProvider(...) │ └────────────────────────────┘ ``` **`DeviceConnectionInfo` 是主程序和插件之间的数据传递载体 (DTO)**,包含设备参数和已创建好的 Provider 实例。 #### 2.5 重构前后对比 | | 重构前 (插件内含通讯) | 重构后 (通讯层分离) | |---|---|---| | **新插件开发** | 每个插件重复实现 SerialPort/TcpClient | 注入 Provider,调 SendAndReceiveAsync 即可 | | **新增通讯方式** | 在新插件中重复写通讯代码 | 实现 ICommunicationProvider -> Factory 注册 | | **串口并发互斥** | 每个插件各自调用 SerialPortMutex | Provider 内部统一管理 | | **单元测试** | 依赖真实串口,无法测试 | Mock ICommunicationProvider 即可测试 | | **插件职责** | 通讯 + 协议 + 操作路由 | **仅操作路由 + 操作描述** | ### 3. 插件系统 #### 3.1 插件接口 ```csharp public interface ICommunicationPlugin { string Name { get; } // "ModbusRTU" string Version { get; } // "1.0" List GetOperations(); // ["01","02","03"...] Task ExecuteAsync(string operation, // 执行操作 string parameter, DeviceConnectionInfo deviceConfig, CancellationToken token); } // 可选接口: 提供操作码的中文描述 public interface IPluginOperationDescriptions { Dictionary GetOperationDescriptions(); } ``` #### 3.2 内置插件 | 插件 | Name | 操作码 | 是否依赖通讯 | |------|------|--------|-------------| | **ModbusRTUPlugin** | "ModbusRTU" | 01-10, custom | Yes (ModbusRTUProvider) | | **SerialPortExecutorPlugin** | "SerialPort" | NormalString, HexString, HexByteArray | Yes (SerialPortProvider) | | **SocketPlugin** | "Socket" | MB_01-MB_10, SendHex, SendString | Yes (SocketProvider) | | **NullPlugin** | "Null" | Delay, SaveParam, DataProcess, Compare... | No | > **设计要点**: 插件 Name 属性不变 ("ModbusRTU"),保证现有脚本 XML 依赖名兼容。 ### 4. 并发控制 #### 4.1 串口互斥锁 ``` SerialPortMutex (ConcurrentDictionary) +-- COM3 -> SemaphoreSlim(1,1) <-- 同一时刻只有一个线程使用 +-- COM4 -> SemaphoreSlim(1,1) +-- ... 执行流程: Provider.ConnectAsync() -> SerialPortMutex.AcquireAsync("COM3") [获取锁] -> new SerialPort("COM3") + Open() -> 执行收发 ... -> Provider.Disconnect() -> SerialPortMutex.Release("COM3") [释放锁] ``` 锁从 `ConnectAsync` 持有到 `Disconnect`,覆盖整个通讯会话。多工位共用同一 COM 口时,后续工位自动排队等待。 #### 4.2 Socket 流同步 ```csharp // SocketProvider 内部 private static ConcurrentDictionary _endpointLocks; SendAndReceiveAsync() { var lk = _endpointLocks.GetOrAdd("192.168.1.1:502", _ => new SemaphoreSlim(1,1)); await lk.WaitAsync(); // 获取端点锁 try { /* 使用 TcpClient 流收发 */ } finally { lk.Release(); } // 释放 } ``` ### 5. 健壮性设计 | 维度 | 措施 | |------|------| | **Provider 生命周期** | StepExecutionService 每次步骤执行后在 finally 中调用 Disconnect/Dispose | | **插件加载防泄漏** | AssemblyLoadContext.Resolving 静态一次性注册, 避免重复 += | | **空 Provider 处理** | Null 插件 / 无设备步骤中 provider 为 null, 插件内部判空 | | **可扩展性** | 新增通讯方式: 实现 ICommunicationProvider -> Factory 注册一行 | --- ## 目录结构 ``` PVT-WPF/ +-- PVT.Wpf/ # 主程序 (.NET 9.0 WPF) | +-- Models/ # 数据模型 (StepItem, DeviceConfig, TestProgram) | +-- ViewModels/ # MVVM 视图模型 | +-- Views/ # XAML 视图 | +-- Services/ # 核心服务 | | +-- StepExecutionService.cs # 步骤执行引擎 | | +-- PluginLoader.cs # 插件加载器 | | +-- ScriptXmlService.cs # 脚本 XML 读写 | | +-- ResultPoolService.cs # 结果缓冲池 | | +-- LogService.cs # 日志服务 (NLog) | | +-- StationConfigService.cs # 工位配置服务 | +-- Infrastructure/ # 基础设施 (ObservableObject, RelayCommand) +-- PVT.Communication/ # 通讯库 (新建) | +-- SerialPortProvider.cs # 串口通讯封装 | +-- SocketProvider.cs # TCP 通讯封装 | +-- ModbusRTUProvider.cs # Modbus RTU 协议 | +-- CommunicationProviderFactory.cs # Provider 工厂 | +-- ModbusUtils.cs # CRC16 / Hex 工具 +-- Plugins/ # 插件项目 | +-- PVT.PluginContracts/ # 接口契约 (插件与主程序共享) | +-- PVT.Plugin.ModbusRTU/ # ModbusRTU 插件 | +-- PVT.Plugin.SerialPort/ # 串口插件 | +-- PVT.Plugin.Socket/ # Socket/ModbusTCP 插件 | +-- PVT.Plugin.Null/ # Null 内部操作插件 | +-- PVT.Plugin.Template/ # 插件开发模板 +-- PVT.Wpf.sln ``` --- ## 快速开始 ### 运行 ```bash cd PVT-WPF dotnet run --project PVT.Wpf ``` ### 新建脚本 1. 启动程序 -> 程序调试页 -> 新建 2. 编辑测试项: 选择设备 -> DLL -> 操作 -> 填写参数 3. 保存脚本 (XML) 4. 点击"启动"执行 ### 绑定工位 1. 工位配置页 -> 创建工位 -> 配置设备 2. 程序调试页 -> 打开脚本 -> "填入工位" 3. 多工位页 -> 查看实时状态 --- ## 插件开发 ### 开发新插件 参考 `PVT.Plugin.Template/README.md`, 复制模板 -> 改名 -> 实现接口 -> 编译 -> DLL 放入 `Dlls/` 目录 -> 重启程序自动发现。 ### 新增通讯方式 1. 在 `PVT.Communication` 中实现 `ICommunicationProvider` 2. 在 `CommunicationProviderFactory.Create()` 中注册新类型 3. 所有现有插件即可使用新通讯方式 --- ## 指令格式参考 ### DataProcess 速查 ``` ${key}|SUBSTR|start|length # 字符串截取 (1-based) ${key}|REPLACE|old|new # 字符串替换 ${key}|HEX|S16 # HEX->有符号16位 ${key}|HEX|U16 # HEX->无符号16位 ${key}|HEX|DEC # HEX->十进制 ${key}|HEX|TOHEX # 十进制->HEX ${key}|ABS # 绝对值 ${key}|INT # 整数部分 ${key}|LEN # 字符串长度 ${key}|-1|+2|*3|/4|%5 # 数值运算链 (默认) ``` ### 正则提取 ``` 返回: "SN:ABC123, VER:2.0" 正则: ABC\d+ -> "ABC123" 正则: VER:([\d.]+) -> "2.0" ``` --- ## 日志系统 ### 分层输出 ``` Logs/ +-- Debug/ # 调试模式 | +-- 2025-06-02/ | +-- program_timestamp.log +-- Running/ # 工位运行模式 +-- Station1/ +-- runTimestamp.log ``` ### 日志级别 | 级别 | 文件 | UI 显示 | |------|------|---------| | Debug | Yes | No | | Info | Yes | Yes | | Warn | Yes | Yes | | Error | Yes | Yes | ### 插件中使用日志 ```csharp var log = deviceConfig?.LogService; log?.Info("ModbusRTU COM3: send FC=03"); log?.Warn("retry #2"); log?.Error($"exception: {ex.Message}"); ``` --- ## 构建与运行 ```bash # 全量编译 dotnet build PVT.Wpf.sln # 单独编译某插件 dotnet build Plugins/PVT.Plugin.ModbusRTU/PVT.Plugin.ModbusRTU.csproj ``` 输出目录: `PVT.Wpf/bin/Debug/net9.0-windows/` 插件目录: `PVT.Wpf/bin/Debug/net9.0-windows/Dlls/` ## 技术栈 - WPF MVVM (ObservableObject, RelayCommand) - .NET 9.0-windows - 插件 DLL 多目标 net472 + net9.0 - NLog 5.x 日志 - 串口互斥 SemaphoreSlim 锁 - TCP 连接池 + SemaphoreSlim 流同步 --新增模拟器 可以对模拟脚本中常见操作的80种步骤进行从站模拟收发测试 目前仍然在测试中 Scripts文件中包含脚本以及模拟脚本说明等 ![输入图片说明](Scripts/image.png)