Files
flow_control/README.md
T
louis c1a4a2a229 完善流量控制 GUI 的开环扫点与手动操作流程
新增可配置开环扫点、多 CSV 前馈表拟合及结果文件生成;补充闭环停止、手动开度安全互锁和 PID 未启用状态显示,并同步更新自动化测试、使用文档、打包配置与 Windows 可执行程序。
2026-09-04 16:47:16 +08:00

498 lines
36 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.
# 气体流量控制与阀门开环标定
本项目基于信捷 **Easy521 PLC**(Modbus TCP)提供一个图形闭环入口、三个可独立运行的命令行在线入口和两个离线工具:
- [gui_main.py](gui_main.py):Windows 单窗口操作界面,支持连接/断开设备、设置目标流量、实时监视三路传感器与下发总开度,以及选择外部 `A_eff` 模型;
- [main.py](main.py):以 10 Hz 周期运行单回路气体流量闭环控制,支持增量式 PID,并可选“前馈反解打底 + PI 修残差”与按阀前压力缩放增益的增益调度;
- [open_loop.py](open_loop.py):随机扫描阀门开度,并在末尾追加闭阀端精细行程扫描;采集阀前/阀后的压力与阀后流量,用于开环标定、动态特性观察和临界压力比分析。
- [test_valve.py](test_valve.py):在 Windows 终端中手动切换阀门开度,同时观察并记录阀后流量、阀前/阀后压力和电机行程。
- [identify_valve.py](identify_valve.py):离线从开环扫点 CSV 拟合阀特性表,产出供前馈使用的 `valve_model.json`
- [test_valve_model.py](test_valve_model.py):阀特性模型与前馈反解的离线自测,不接触硬件。
三个命令行在线入口都会持续保存 CSV,并在运行结束时为有效采样数据生成 PNG 曲线图。命令行闭环程序还会持续进行滑动窗口稳态判定,并在首次达到稳态时输出一次控制性能报告。GUI 保持轻量,只写轮转运行日志,不生成 CSV、PNG 或性能报告。
## 重要安全说明
> 本项目会直接控制真实阀门。首次运行前必须核对 IP、站号、寄存器地址、传感器量程、阀门行程方向、压力上限和执行机构的断电/失驱位置,并准备独立的机械或电气急停手段。
四个在线入口当前的阀门初始化与退出策略如下:
- **图形界面 `gui_main.py` / `FlowControl.exe`**:启动后不会自动连接;连接成功后先主动设置 `100%` 开度,再进入传感器监测,首次设置目标流量时启动闭环。点击断开、关闭窗口或发生故障时,会再次尝试设置 `100%` 开度,然后断开 PLC
- **闭环 `main.py`**:连接成功后先主动设置 `100%` 开度,再开始初始采样、性能报告和控制计时;故障、正常退出或 `Ctrl+C` 时再次尝试设置 `100%` 开度,然后断开 PLC。
- **开环 `open_loop.py`**:连接成功后先主动设置 `100%` 开度,再创建记录器并开始实验计时;实验结束、异常或 `Ctrl+C` 时再次尝试设置 `100%` 开度,然后断开设备。
- **手动测试 `test_valve.py`**:连接成功后先主动设置 `100%` 开度,再创建记录器并开始采样;退出时再次尝试设置 `100%` 开度,然后断开设备。该脚本不执行流量/压力越限和连续读取失败保护,只适合在具备独立安全措施的受控测试中使用。
因此,README 中的“关闭”或“全开”均指当前代码定义的逻辑位置,不能代替现场对阀门真实动作方向的确认。
## 硬件与默认接线
- PLCEasy521):Modbus TCP,默认 `192.168.1.88:502`,站号 `1`
- D 寄存器为直接 Modbus 地址:读用功能码 `03`(保持寄存器),写用功能码 `06`
- 模拟量标称原始范围:`0~32000`,线性对应各量程满量程;读取时先把 Modbus 的 `0~65535` 解码为 16 位有符号值,因此传感器负偏差会换算为负物理量,并由上层有效范围检查处理;
- 默认流量量程:`300 SLM`;阀后压力量程:`400 kPa`;阀前压力量程:`300 kPa`
- 电机行程范围:`0~1000`,且行程与阀门开度反向作用。
默认寄存器地址与量程如下:
| 用途 | 地址 | 量程 | 配置位置 |
| --- | ---: | ---: | --- |
| 阀后流量 | `D700` | `300 SLM` | `config.FLOW_AFTER_ADDR` |
| 阀后压力 | `D710` | `400 kPa` | `config.PRESSURE_AFTER_ADDR` |
| 阀前压力(前馈/增益调度/开环共用) | `D720` | `300 kPa` | `config.PRESSURE_BEFORE_ADDR` / `open_loop.PRESSURE_BEFORE_CHANNEL` |
| 电机位置指令(0~10V | `D730` | 行程 `0~1000` | `config.MOTOR_OUTPUT_ADDR` |
> 控流阀前的流量传感器已删除,`open_loop.FLOW_BEFORE_CHANNEL` 现为 `None`:开环 CSV 的 `flow_before_slm` 列与 PNG 中的 “Flow before valve” 曲线将保持为空。
开度与行程的默认映射为:
```text
opening = 0% -> motor_position = 1000(关闭)
opening = 100% -> motor_position = 800(当前打开端/机械死区边界)
```
虽然硬件层行程坐标仍为 `0~1000`,当前控制映射只使用 `800~1000`。这是近期根据阀门有效调节区间设置的打开端边界;逻辑 `100%` 不再对应物理行程 `0`
所有闭环通道、量程和行程端值集中在 [config.py](config.py);开环新增的阀前通道和实验参数位于 [open_loop.py](open_loop.py) 顶部。
## 环境与依赖
项目使用 Python 3,直接依赖:
- `pymodbus`Modbus TCP/RTU 通讯;
- `matplotlib`:命令行入口使用 `Agg` 后端生成 PNG,无显示器环境也可运行;
- `tkinter`GUI 使用的 Python 标准库组件;制作 EXE 时另需 `PyInstaller`
示例安装:
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install pymodbus matplotlib pyinstaller
```
仓库当前没有锁定依赖版本。若 `PcControl.py` 导入 `pymodbus.payload` 或调用 `slave=` 参数时报错,请使用与现有 [PcControl.py](PcControl.py) API 兼容的 `pymodbus` 版本。
## 图形界面与 Windows 可执行程序
交付目录为 `可执行程序/`,包含以下三个文件:
- `FlowControl.exe`:Windows 64 位单文件程序,无需安装 Python,默认内置 `valve_model_004.json`
- `valve_model_004.json`:内置模型的外置备份和自定义模型格式参考,删除该外置副本不会影响内置模型;
- `使用说明.txt`:现场操作、安全策略和故障说明。
双击 `FlowControl.exe` 后,核对 PLC 地址、端口和站号,再点击“连接设备”。连接成功后程序保持 `100%` 开度并以 `0.25 s` 周期监测阀后流量、阀前压力和阀后压力;输入 `0~300 SLM` 目标并点击“设置目标流量”时启动 10 Hz 闭环。闭环运行中该输入框和按钮保持可用,可直接输入新目标并再次点击以切换目标;其右侧的独立“停止流量控制”按钮只在闭环运行时可用,点击后程序停止闭环、立即下发 `100%` 总开度并返回监测状态。界面显示的“下发总开度”为最终命令:
```text
总开度 = clamp(前馈开度 + PI 修正量, 0, 100)
```
GUI 的 Modbus 读写与控制周期全部在单一后台线程中串行执行,避免通信超时阻塞界面。监测和闭环阶段都会执行流量/压力越限保护;连续读取失败、写入失败、控制周期严重超时或其他通信异常会停止闭环,尝试恢复 `100%` 开度并断开设备。运行日志写到 EXE 同目录的 `logs/flow_control.log`,单文件最大约 2 MB,保留 3 份历史文件。
默认模型只能在未连接状态下切换。点击“选择 JSON…”可加载其他模型,程序会在连接前检查 JSON 结构、有限数值、标定点数量、`800~1000` 行程范围以及 `motor_open` / `motor_closed` 与当前配置是否一致;无效模型不会进入控制。点击“使用内置”可恢复 `valve_model_004.json`
连接设备后可点击模型选择按钮左侧的“开环扫点”。弹窗可设置起止开度、开度间隔、随机种子、采样周期、稳态窗口、相对/绝对标准差阈值、单点最长等待时间,以及是否追加闭阀端精细行程扫描。程序按随机顺序直接下发各开度,每个点在阀后流量稳定或达到最长等待时间后切换;扫点期间继续显示流量、压力、总开度和电机行程。若当前正在闭环控制,开始前会确认并停止闭环;扫点启动后按钮变为“停止扫点”,点击即可提前停止并生成已有数据的结果图。正常完成、用户停止、断开、退出或故障时都会保留已经采集的数据,并尝试把阀门恢复到 `100%` 开度。
开环数据写到程序所在目录的 `open_loop_data/`:采样过程中逐行落盘 CSV,结束时生成一张包含压力、流量和绝对压力比三组子图的 PNG。扫点结束后设备保持连接并回到监测状态;安全越限或通信故障仍会执行安全断开。
“目标流量”区域下方提供手动开度输入和“设置开度”按钮,允许范围为 `0~100%`。手动开度只允许在设备已连接、闭环和开环扫点均停止时下发;闭环运行中点击该按钮只会提示先点击“停止流量控制”,关闭提示后不会停止闭环,也不会发送任何开度命令。监测卡片会显示手动总开度和换算后的电机行程;因为 PID 没有参与手动下发,PID 模式显示为“未启用”。刚连接、停止闭环后以及其他仅监测状态也统一显示“未启用”。
“拟合前馈表”是离线工具,不会在每次扫点结束时自动生成 JSON:点击后可一次选择多份开环 CSV,再指定输出 JSON。程序以每个扫描步末尾 `20` 个样本求稳态均值,将多次实验中相同行程的数据合并平均,按 `A_eff = Q / (P1_abs × F(r))` 反解并生成可由“选择 JSON…”直接加载的阀模型;同时在同目录生成一张同名诊断 PNG。拟合在独立线程运行,只能在未连接、故障已断开或已连接监测状态启动,闭环及扫点过程中禁用。
源码运行与重新打包命令为:
```powershell
python gui_main.py
python gui_main.py --smoke-test
python -m PyInstaller --noconfirm --clean --distpath "可执行程序" --workpath "build/flow_control_gui" flow_control_gui.spec
Copy-Item valve_model/valve_model_004.json 可执行程序/valve_model_004.json -Force
```
`--smoke-test` 只检查内置模型和关键依赖,不打开界面,也不会连接真实设备。
`gui_main.py` 是 GUI 入口而不是单文件源码:直接运行时仍会导入本工程的 `control_runtime.py``config.py``controllers.py``flow_control.py``PcControl.py``open_loop.py``valve_model.py`,并需要 `pymodbus``matplotlib` 和系统可用的 `tkinter`。PyInstaller 会分析这些导入,把 Python 解释器、项目模块、第三方库、Tcl/Tk 运行文件和内置 JSON 收集进单文件 EXE。双击 EXE 时,PyInstaller 先把内部文件释放到临时目录,再启动程序,所以交付后的 `FlowControl.exe` 不依赖工作区源码或现场安装 Python;它仍然依赖 Windows、可写的程序/临时目录、正常网络以及可访问的 PLC。
打包使用的 `build/flow_control_gui/` 是 PyInstaller 的中间工作目录,包含模块分析、归档、链接缓存和缺失模块告警,不是交付文件,也不是 EXE 的运行依赖;删除后可由上面的打包命令重新生成。`可执行程序/` 才是最终交付目录。
窗口版 EXE 的 `--smoke-test` 正常通过时不会显示窗口或输出文本,应通过进程退出码判断。PowerShell 中可执行 `$p = Start-Process .\可执行程序\FlowControl.exe -ArgumentList '--smoke-test' -Wait -PassThru; $p.ExitCode``0` 为通过,非 `0` 为失败。当前检查范围包括配置、内置模型、Modbus 类,以及实际创建一张临时 PNG 来验证开环绘图库;它不连接真实设备,因此不能替代 PLC 联机测试。
## 闭环控制
### 运行
先在 [config.py](config.py) 中完成现场参数核对,再执行:
```powershell
python main.py --target 50
```
`--target` 的单位为 SLM,必须位于 `TARGET_FLOW_MIN_SLM~TARGET_FLOW_MAX_SLM`(当前为 `0~300`)范围内。按 `Ctrl+C` 停止;默认 `AUTO_STOP_ON_STEADY=False`,所以达到稳态后仍会继续控制。如需首次达到稳态后自动结束,将该配置改为 `True`
### 运行中切换目标流量
闭环运行过程中无需重启即可切换目标流量:在终端按 `S``TARGET_SWITCH_KEY`)进入输入模式,屏幕会提示当前目标并等待输入,输入数字后按回车生效,按 `Esc` 取消。输入模式期间:
- 控制循环照常运行,原目标流量继续生效,直到回车确认新值;
- 终端输出整体静默(状态行、性能报告均不打印),避免与输入回显混在一起;
- 逐周期 CSV 与 `logs/flow_control.log` 照常写入,不丢数据。
新目标生效后会重置稳态判定(`PerformanceReporter.reset()`),因此切换后的目标仍会重新判稳并输出一次性能报告。目标值需位于 `TARGET_FLOW_MIN_SLM~TARGET_FLOW_MAX_SLM` 范围内,非法或超范围输入会被拒绝并提示,不影响控制。
> 该热键仅在真实控制台(Windows 的 `msvcrt`)下可用;`Ctrl+C` 无论是否处于输入模式都会停止控制。
启动流程为:
```text
校验配置 -> 连接 PLC -> 主动设置 100% 开度(不计时)-> 读取一次初始流量
-> PID 输出初始化为 INITIAL_OPENING_PCT
-> 进入周期控制
```
单个控制周期为:
```text
读取流量/压力
|
v
IncrementalPID.update()
error = target - measurement
|
v
开度百分比 0~100%(输出限幅 + 变化率限幅)
|
v
opening_to_motor_position()
|
v
反向行程 1000~800 -> PLC D730
```
增量 PID 的输出直接定义为阀门开度,因此控制器本身不包含电机行程或死区映射。目标流量不高于 `ZERO_FLOW_THRESHOLD_SLM`(当前 `0.5 SLM`)时,程序跳过常规 PID 更新并直接命令 `0%` 开度。
### FAR/NEAR 两档 PID
闭环启动时进入 `FAR` 模式。两档模式分别使用不同的 PID 参数和开度变化速度:
| 模式 | 用途 | Kp / Ki / Kd | 最大开度速度 |
| --- | --- | --- | ---: |
| `FAR` | 大误差时快速接近目标 | `0.5 / 0.5 / 0.0` | `200%/s` |
| `NEAR` | 小误差时抑制振荡和稳态误差 | `0.1 / 0.1 / 0.0` | `2%/s` |
切换阈值同时考虑绝对误差下限和目标流量比例:
```text
near_threshold = min(2 SLM, max(1 SLM, 5% * target))
far_threshold = min(8 SLM, max(4 SLM, 20% * target))
FAR -> NEAR|error| <= near_threshold,连续满足 10 个采样周期
NEAR -> FAR |error| >= far_threshold,连续满足 10 个采样周期
```
两个阈值构成滞回区间,可避免误差位于边界附近时逐周期反复切换。按当前 `0.1 s` 控制周期,连续 10 个采样周期约为 1 秒;期间只要切换条件有一个周期不成立,确认计数便清零。只有模式真正改变时才更新 PID 参数和输出变化率,切换原因会写入日志。
运行中修改目标后,模式确认计数会清零。如果尚无有效流量,或新目标相对最后有效流量的误差大于 `near_threshold`,控制器立即回到 `FAR`;若误差仍处于 NEAR 阈值内,则保持当前模式。PID 的累计输出和误差历史不会因修改目标而整体重置,性能报告器则会重置并从新目标重新计时、判稳和统计超调。
### 前馈 + 增益调度(MFC 结构)
闭环当前默认启用“前馈反解打底 + PI 修残差”的 MFC 结构,并加载 `valve_model/valve_model_004.json`;按阀前压力缩放 Kp/Ki 的增益调度仍默认关闭。投用前必须确认阀前/阀后压力接线和阀特性表与当前硬件一致;不满足时应关闭前馈或重新辨识。
静态阀特性建模为:
```text
Q_ss = A_eff(x) · P1_abs · F(r)
F(r) 为压比函数,r = P2_abs / P1_abs
阻塞流(r <= 0.528)时 F = 1
亚声速(0.528 < r < 1)时按 ISO 6358 椭圆平滑衰减;
r >= 1 时无正向流,F = 0。
```
开启前馈后,控制周期内的输出计算变为:
```text
前馈开度 opening_ff = 反解(目标流量, 阀前压 P1, 阀后压 P2)
PI 修正量 correction = PI(目标流量 - 实测流量) # 输出限幅改为对称 ±带
开度 = clamp(opening_ff + correction, 0, 100)
```
即前馈把开度送到稳态工作点附近,PI 只负责修正模型残差和动态偏差;PI 输出不再是 `0~100%` 的全行程,而是围绕前馈的修正量(限幅由 `FEEDFORWARD_CORRECTION_BAND_PCT` 控制,当前默认 ±50%)。
为避免接近目标后压力噪声持续改写前馈,同时又能响应下游负载变化,控制器维护一个 `FEEDFORWARD_UPDATE_WINDOW_S=3 s` 的流量绝对误差滑动窗口。窗口首次积满后,程序在每个控制周期都重新计算最近 3 秒的平均绝对误差,并与 `FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM=2 SLM` 比较:均值不大于阈值时冻结上一次前馈开度,只让 PI correction 逐周期更新;均值超过阈值时重新根据当前目标流量和阀前/阀后压力反解前馈。因此,下游扰动持续影响流量并使 3 秒误差均值越过阈值后,前馈会自动解除冻结。
窗口未首次积满或尚无缓存前馈时,每个周期都尝试反解前馈。窗口一旦积满,其“已就绪”状态不会因采样抖动和旧样本淘汰而反复失效;但滑动均值仍会持续更新,冻结状态并不会锁死。目标改变、控制器重启或进入零流量分支会清空窗口并重新累计 3 秒。当前周期反解失败时继续复用上次有效前馈;如果从未得到有效前馈,则以 `0%` 为基值并由 correction 调整。
增益调度把 FAR/NEAR 的 Kp、Ki 按阀前绝对压力缩放,摊平 K_x ∝ P1 的非线性:
```text
scale = GAIN_SCHEDULE_REF_ABS_KPA / max(P1_abs, GAIN_SCHEDULE_FLOOR_ABS_KPA)
Kp_eff = scale · Kp_baseKi_eff = scale · Ki_base # Kd 不调度
```
`scale` 相对变化超过 `GAIN_SCHEDULE_SCALE_CHANGE_THRESHOLD`(默认 5%)才重算并写入 PID,避免每个周期都更新增益。
相关配置的当前默认值为:
| 开关 | 默认 | 说明 |
| --- | --- | --- |
| `FEEDFORWARD_ENABLED` | `True` | 启用前馈反解打底 + PI 修残差 |
| `GAIN_SCHEDULE_ENABLED` | `False` | 启用按阀前压力缩放 Kp/Ki |
| `VALVE_MODEL_PATH` | `"valve_model/valve_model_004.json"` | 阀特性表 JSON 路径(空串 = 关闭前馈) |
| `PRESSURE_BEFORE_ADDR` | `720` | 阀前压力 P1 的寄存器地址(D720) |
前馈生效需同时满足:`FEEDFORWARD_ENABLED=True``VALVE_MODEL_PATH` 指向有效 JSON、`PRESSURE_BEFORE_ADDR``PRESSURE_AFTER_ADDR` 均已配置。任一条件缺失时自动退化为纯 PID,并在启动日志中提示。
### 运行输出
每次闭环运行会产生:
1. **终端与日志**:约每秒输出一次状态;非 `OK` 状态立即输出。日志保存到 `logs/flow_control.log`
2. **逐周期 CSV**:默认保存为 `data/flow_run_YYYYMMDD_HHMMSS.csv`,每行写入后立即刷新到磁盘。现有 `data/日期/实验说明/` 子目录是历史数据的人工归档方式,不是程序自动创建的层级。
3. **四联曲线图**:结束时保存同名 PNG,依次显示目标/实际流量、阀门开度、阀前/阀后压力和绝对压力比;启用前馈时,开度子图会叠加虚线显示前馈开度,压力比子图绘制 `0.528` 临界参考线。
4. **性能报告**:首次达到稳态时写入终端和日志;若整个运行过程未达到稳态,只输出未达到稳态的提示。
闭环 CSV 字段:
| 字段 | 含义 |
| --- | --- |
| `time_s` / `timestamp` | 相对运行时间 / Unix 时间戳 |
| `target_flow_slm` / `measured_flow_slm` | 目标流量 / 实测流量 |
| `opening_pct` / `motor_position` | 阀门开度 / 电机行程 |
| `pressure_kpa` | 阀后压力;未启用或读取失败时为空 |
| `pressure_before_kpa` | 阀前压力 P1;未启用或读取失败时为空 |
| `P_abs_ratio` | `(阀后表压 + 101.325) / (阀前表压 + 101.325)`;压力缺失或分母非法时为空 |
| `feedforward_pct` | 前馈开度;未启用前馈时为空 |
| `correction_pct` | PI 修正量(±带内);未启用前馈时为空 |
| `gain_scale` | 增益调度缩放系数;未启用调度时为 `1.0` |
| `error_slm` | `目标流量 - 实测流量` |
| `actual_dt_s` | 实际控制周期 |
| `status` | `OK` 或传感器读取重试状态 |
### 稳态判定与性能报告
判稳只使用状态为 `OK` 且流量、阀前压力、阀后压力均有效的样本。滑窗至少覆盖 `STEADY_STATE_WINDOW_S - CONTROL_PERIOD_S`,以容纳真实采样时间戳抖动,并要求下列条件同时成立:
```text
|窗口均值 - 目标流量| <= STEADY_STATE_BAND_PCT / 100 * 目标流量
阀前压力每个样本均位于阀前压力窗口均值 ± STEADY_STATE_PRESSURE_BAND_KPA
阀后压力每个样本均位于阀后压力窗口均值 ± STEADY_STATE_PRESSURE_BAND_KPA
```
当前默认使用 5 秒窗口、±1% 流量均值误差带和阀前/阀后各 `±1 kPa` 压力带。流量标准差仍会计算并写入报告,但不再作为闭环判稳条件。报告内容还包括两侧压力的窗口均值/最大偏差、判稳时当前模式的 PID 参数、目标与控制范围、调节时间、稳态误差(目标减窗口均值)、初始流量,以及按初始变化方向计算的峰值/谷值超调量。
## 开环扫点实验
[open_loop.py](open_loop.py) 不参与闭环反馈,也不会被 `main.py` 导入。默认先将 `0%~100%`、间隔 `5%` 的 21 个开度按固定随机种子 `42` 打乱并换算为行程,再在序列末尾追加 `970~1000`、间隔 `3` 的闭阀端精细行程点(末端不足一个步长时仍包含 `1000`)。精细段直接按行程下发,并反算逻辑开度用于记录。
```text
写入随机开度/精细行程 -> 每 0.1 s 采集阀前/阀后压力与阀后流量
-> 等待阀后流量稳定,或等待满 30 s
-> 切换到下一个开度
```
运行默认实验或自定义扫描范围:
```powershell
python open_loop.py
python open_loop.py --seed 7
python open_loop.py --min 0 --max 100 --step 5
```
- `--min``--max`:扫描开度上下限,必须满足 `0 <= min < max <= 100`
- `--step`:开度间隔,必须大于 `0`;不能整除区间时会额外包含 `max` 端点;
- `--seed`:打乱顺序的随机种子;相同参数和种子会得到相同顺序。
可通过 `python open_loop.py --help` 查看完整参数帮助。
开环没有目标值,其稳态条件仅依据阀后流量的滑窗总体标准差:
```text
std <= max(STEADY_STATE_STD_PCT / 100 * |窗口均值|, MIN_ABS_STD_SLM)
```
默认窗口为 5 秒,绝对标准差下限为 `0.05 SLM`,单点最长等待时间为 30 秒。阀后流量连续 5 次读取失败会中止实验;其他通道读取失败会在对应 CSV 字段中留空。
输出保存到 `open_loop_data/open_loop_YYYYMMDD_HHMMSS.csv/.png`,日志保存到 `logs/open_loop.log`。PNG 包含三张子图:阀前/阀后压力、阀前/阀后流量、阀后绝对压力与阀前绝对压力之比。由于阀前流量传感器已删除,“阀前流量”曲线与 `flow_before_slm` 列保持为空。
压力比按下式计算:
```text
P_abs_ratio = (阀后表压 + 101.325 kPa) / (阀前表压 + 101.325 kPa)
```
CSV 同时记录 `is_ratio_smaller_than_0.528`:压力比小于临界值 `0.528` 时为 `Y`,否则为 `N`;任一压力缺失或分母非法时两个压力比字段留空。图中会绘制 `0.528` 参考线,并标出各开度切换时刻。
## 手动阀门测试
[test_valve.py](test_valve.py) 用于绕过 PID,人工设置单个阀门开度并观察传感器响应。该交互依赖 Windows 的 `msvcrt`,请在真实 Windows 终端中运行:
```powershell
python test_valve.py
```
连接成功后,脚本主动写入 `100%` 开度、行程 `800`,完成后才创建记录器并开始采样。运行期间:
- 每秒读取并显示一次阀后流量、阀前/阀后压力、各传感器反算模拟量原始值、逻辑开度和映射后的电机行程;
-`S` 进入输入模式,输入 `0~100` 的新开度后按回车下发,按 `Esc` 取消;
- 输入期间暂停终端状态显示,但传感器采样和 CSV 写入继续;
-`Ctrl+C` 保存数据并退出;断开前会再次尝试下发 `100%` 开度。
数据保存为 `test_valve_data/test_valve_YYYYMMDD_HHMMSS.csv`,字段包含相对时间、ISO 时间戳、`flow_after_slm`、开度、`pressure_before_kpa``pressure_after_kpa` 和电机行程;结束时生成同名三联图,依次显示阀后流量、开度以及叠加的阀前/阀后压力。
> 此脚本不会调用 `config.validate_config()`,也没有闭环/开环程序中的流量越限、压力越限或连续读取失败保护。运行前应人工完成配置核对,并确保测试人员能够立即切断气源或执行急停。
## 阀特性辨识与离线自测
前馈需要一张 `A_eff(x)` 阀特性表。先用 [open_loop.py](open_loop.py) 扫点得到开环 CSV(时间戳命名、从不覆盖),再用 [identify_valve.py](identify_valve.py) 离线拟合。`--csv` 支持多个路径与 glob 模式,可把多次实验合并重拟合,数据越多表越稳:
```powershell
python identify_valve.py --csv open_loop_data/open_loop_YYYYMMDD_HHMMSS.csv --out valve_model/valve_model.json
python identify_valve.py --csv "open_loop_data/*.csv" --out valve_model/valve_model.json --plot
python identify_valve.py --plot --out valve_model/valve_model.json # 默认合并 open_loop_data/ 下全部 CSV
```
- 每个 CSV 按 `step_index` 分组,每组取末尾 `--tail` 个采样点平均,得到稳态 `(行程 x, 流量 Q, 阀前压 P1, 阀后压 P2)`,并按开度访问顺序标记逼近方向(上升 / 下降 / 该文件起始步);
- 对每个稳态点反解 `A_eff = Q / (P1_abs · F(r))`;同一行程 x 的多个点(来自不同次实验)先取平均再构表;
- 按行程升序构表。`IDENTIFY_VALVE_MONOTONIC_CHECK_ENABLED=False`(当前默认)时保留所有按行程平均后的点,并在 JSON 记录 `"enforce_monotonic": false`;设为 `True` 时检查单调性,整体非单调则只保留最长单调连续段并警告其余点被丢弃;
- 用 [valve_model.py](valve_model.py) 存成 JSON,并打印合并文件数、稳态点数与阻塞/亚声速点统计等摘要;加 `--plot` 额外生成诊断图,实测点按逼近方向着色:同一行程下上升/下降两团点明显分离说明阀门迟滞显著,前馈表应考虑分方向处理;完全重叠则迟滞可忽略。
非单调表可能让同一个有效面积对应多个行程,反解时选择数值更大的行程,即更靠近关闭端的交点。目标有效面积小于表中最小实测值时直接返回机械全关行程 `MOTOR_CLOSED_POSITION`;恰好等于最小值时仍返回对应实测行程。
`config.VALVE_MODEL_PATH` 指到该 JSON、确认 `PRESSURE_BEFORE_ADDR` 接线无误、并把 `FEEDFORWARD_ENABLED` 设为 `True` 后,闭环即可启用前馈。当前配置已指向 `valve_model/valve_model_004.json` 并默认启用;重新标定后应覆盖该文件或同步修改配置路径。
[test_valve_model.py](test_valve_model.py) 是纯计算离线自测,不接触硬件,可在任意环境运行:
```powershell
python test_valve_model.py
```
它校验压比函数 `F(r)`、绝对压力换算、`A_eff` 插值与前馈反解互逆、单调/非单调表反解、闭阀端精细扫描点生成,以及 `identify_valve` 端到端辨识。[test_flow_control.py](test_flow_control.py) 校验前馈误差滑窗在采样抖动下保持就绪、同时持续响应新误差;[test_performance_reporter.py](test_performance_reporter.py) 另行校验闭环的流量均值与压力带判稳逻辑。可一次运行全部离线测试:
```powershell
python -m pytest -q
```
## 安全与故障处理
### 闭环
- 配置在连接硬件前由 `validate_config()` 校验;
- 流量超出 `FLOW_VALID_MIN_SLM~FLOW_VALID_MAX_SLM` 或压力高于 `MAX_PRESSURE_KPA` 时停止控制并报故障;
- 短暂的流量/压力读取失败保持上一输出,不下发新位置;连续达到各自阈值后停止控制;
- 实际控制周期超过 `MAX_CONTROL_DT_S` 时停止控制;
- PID 输出限制在 `0~100%`;FAR/NEAR 两档分别限制每秒最大开度变化;
- 电机命令写入异常或返回失败时停止控制;
- 所有退出路径都会在断开设备前尝试下发 `100%` 开度;下发失败会记录严重错误并返回非零退出码。
### 开环
- 启动和收尾均主动下发 `100%` 开度;
- 任一有效流量读数越界、任一有效压力读数超过上限时中止;
- 阀后流量连续读取失败达到阈值时中止;
- 写入开度失败时中止;
- 中止后仍会尽力恢复 `100%` 开度、断开连接并保留已经写入的 CSV。
### 手动测试
- 只校验输入开度是否为 `0~100` 的有限数值以及电机写入是否成功;
- 传感器读取失败时 CSV 对应字段留空,终端显示 `--`,不会自动中止;
- 不检查流量和压力安全阈值;
- `Ctrl+C`、异常和正常收尾都会在保存已有数据、断开连接前尝试恢复 `100%` 开度。
## 关键配置默认值
| 配置 | 当前默认值 | 说明 |
| --- | --- | --- |
| `PLC_HOST` / `PLC_PORT` / `PLC_SLAVE_ID` | `192.168.1.88` / `502` / `1` | PLC 通讯参数 |
| `FLOW_AFTER_ADDR` / `FLOW_AFTER_RANGE_SLM` | `700` / `300` | 阀后流量 D700 与量程 |
| `PRESSURE_AFTER_ADDR` / `PRESSURE_AFTER_RANGE_KPA` | `710` / `400` | 阀后压力 D710 与量程 |
| `PRESSURE_BEFORE_ADDR` / `PRESSURE_BEFORE_RANGE_KPA` | `720` / `300` | 阀前压力 D720 与量程 |
| `MOTOR_OUTPUT_ADDR` | `730` | 电机输出 D7300~10V,行程 0~1000 |
| `TARGET_FLOW_MIN_SLM` / `TARGET_FLOW_MAX_SLM` | `0` / `300` | 允许的目标范围 |
| `TARGET_SWITCH_KEY` | `s` | 运行中按此键进入"输入新目标流量"模式 |
| `CONTROL_PERIOD_S` / `MAX_CONTROL_DT_S` | `0.1` / `0.3` | 目标周期 / 超时阈值 |
| `PID_FAR_KP` / `PID_FAR_KI` / `PID_FAR_KD` | `0.5` / `0.5` / `0.0` | 大误差快速模式 PID 参数 |
| `PID_NEAR_KP` / `PID_NEAR_KI` / `PID_NEAR_KD` | `0.1` / `0.1` / `0.0` | 小误差稳定模式 PID 参数 |
| `OPENING_MIN_PCT` / `OPENING_MAX_PCT` | `0` / `100` | PID 输出开度范围 |
| `MAX_OPENING_RATE_FAR_PCT_S` / `MAX_OPENING_RATE_NEAR_PCT_S` | `200` / `2` | FAR/NEAR 每秒最大开度变化百分比 |
| `PID_NEAR_ERROR_MIN_SLM` / `PID_NEAR_ERROR_MAX_SLM` / `PID_NEAR_ERROR_TARGET_RATIO` | `1` / `2` / `0.05` | FAR 进入 NEAR 的误差阈值下限、上限和目标比例 |
| `PID_FAR_ERROR_MIN_SLM` / `PID_FAR_ERROR_MAX_SLM` / `PID_FAR_ERROR_TARGET_RATIO` | `4` / `8` / `0.20` | NEAR 返回 FAR 的误差阈值下限、上限和目标比例 |
| `PID_MODE_SWITCH_CONFIRM_CYCLES` | `10` | 模式切换条件需连续满足的采样周期数(当前约 1 秒) |
| `MOTOR_CLOSED_POSITION` / `MOTOR_OPEN_POSITION` | `1000` / `800` | 当前反向控制区间;打开端为机械死区边界 |
| `INITIAL_OPENING_PCT` | `100` | 闭环 PID 内部初始输出 |
| `PRESSURE_BEFORE_ADDR` | `720` | 阀前压力 P1 的寄存器地址(前馈/增益调度/开环共用) |
| `VALVE_MODEL_PATH` | `"valve_model/valve_model_004.json"` | 阀特性表 JSON 路径;空串关闭前馈 |
| `FEEDFORWARD_ENABLED` | `True` | 是否启用前馈反解打底 + PI 修残差 |
| `FEEDFORWARD_CORRECTION_BAND_PCT` | `50` | 前馈开启时 PI 修正量的对称限幅(±%) |
| `FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM` / `FEEDFORWARD_UPDATE_WINDOW_S` | `2` / `3` | 前馈冻结/更新的流量绝对误差均值阈值与窗口长度 |
| `GAIN_SCHEDULE_ENABLED` | `False` | 是否启用按阀前压力缩放 Kp/Ki |
| `GAIN_SCHEDULE_REF_ABS_KPA` | `501.325` | 满压调好基准增益对应的绝对压力(400 + 101.325 |
| `GAIN_SCHEDULE_FLOOR_ABS_KPA` | `60` | 绝对压力下限,防止 P1 很低时增益爆炸 |
| `GAIN_SCHEDULE_SCALE_CHANGE_THRESHOLD` | `0.05` | scale 相对变化超过此值才重算增益 |
| `FLOW_VALID_MIN_SLM` / `FLOW_VALID_MAX_SLM` | `0` / `300` | 流量安全范围 |
| `MAX_PRESSURE_KPA` | `500` | 压力安全上限,须按实际管路额定压力确认 |
| `MAX_CONSECUTIVE_FLOW_FAILURES` / `MAX_CONSECUTIVE_PRESSURE_FAILURES` | `3` / `3` | 闭环连续读取失败阈值 |
| `STEADY_STATE_WINDOW_S` / `STEADY_STATE_BAND_PCT` / `STEADY_STATE_PRESSURE_BAND_KPA` | `5` / `1` / `1` | 闭环窗口、流量均值误差带和压力稳定带 |
| `STEADY_STATE_MAX_WAIT_S` / `STEADY_STATE_STD_PCT` | `30` / `1` | 开环单点最长等待时间 / 流量稳定标准差比例 |
| `OPEN_LOOP_FINE_STROKE_START` / `OPEN_LOOP_FINE_STROKE_STEP` | `970` / `3` | 开环闭阀端精细行程扫描起点 / 步长 |
| `IDENTIFY_VALVE_MONOTONIC_CHECK_ENABLED` | `False` | 辨识时是否只保留最长单调连续段 |
| `AUTO_STOP_ON_STEADY` | `False` | 达到稳态后是否自动结束闭环 |
## 模块职责
| 文件 | 职责 |
| --- | --- |
| [gui_main.py](gui_main.py) | Tkinter 单窗口界面、用户输入校验、状态与实时数据展示 |
| [control_runtime.py](control_runtime.py) | GUI 后台工作线程、模型校验、设备状态机与安全连接/断开 |
| [main.py](main.py) | 闭环入口:参数解析、日志、控制循环、记录器、性能报告与收尾 |
| [flow_control.py](flow_control.py) | 闭环控制层、传感器读取、开度/行程映射、前馈与增益调度、故障处理 |
| [controllers.py](controllers.py) | 通用增量式 PID,含输出限幅和输出变化率限幅 |
| [config.py](config.py) | 闭环集中配置和启动前校验 |
| [data_logger.py](data_logger.py) | 闭环逐周期 CSV 与四联曲线图(含前馈/修正/增益调度/压力比列) |
| [performance_reporter.py](performance_reporter.py) | 闭环流量均值与双压力带判稳、一次性性能报告 |
| [open_loop.py](open_loop.py) | 独立开环扫点、传感器记录和压力比分析 |
| [test_valve.py](test_valve.py) | Windows 终端手动开度测试、每秒采样、CSV 与三联图 |
| [valve_model.py](valve_model.py) | 静态阀特性模型 `Q_ss = A_eff(x)·P1_abs·F(r)` 与前馈反解(纯计算) |
| [identify_valve.py](identify_valve.py) | 从一份或多份开环扫点 CSV(支持 glob 合并)拟合 `A_eff(x)` 表,输出阀模型 JSON |
| [test_valve_model.py](test_valve_model.py) | 阀特性模型与前馈反解的离线自测 |
| [test_flow_control.py](test_flow_control.py) | 前馈误差滑窗、采样抖动与复位逻辑的离线测试 |
| [test_performance_reporter.py](test_performance_reporter.py) | 闭环性能报告器与压力带判稳的离线测试 |
| [test_control_runtime.py](test_control_runtime.py) | GUI 运行层、模型加载、连接、控制与安全断开的离线测试 |
| [flow_control_gui.spec](flow_control_gui.spec) | PyInstaller 单文件 EXE 构建配置和内置模型资源声明 |
| [PcControl.py](PcControl.py) | Modbus 硬件客户端;GUI 和三个命令行入口使用 `Easy521ModbusClient` |
## 项目结构
```text
flow_control/
├── gui_main.py # 图形界面入口
├── control_runtime.py # GUI 后台设备与闭环运行层
├── flow_control_gui.spec # Windows 单文件打包配置
├── main.py
├── open_loop.py
├── test_valve.py
├── flow_control.py
├── controllers.py
├── config.py
├── data_logger.py
├── performance_reporter.py
├── valve_model.py # 静态阀特性模型与前馈反解
├── identify_valve.py # 从开环 CSV 拟合阀特性表
├── test_valve_model.py # 阀特性模型离线自测
├── test_flow_control.py # 前馈误差滑窗离线测试
├── test_performance_reporter.py # 闭环判稳逻辑离线测试
├── test_control_runtime.py # GUI 运行层离线测试
├── PcControl.py
├── README.md
├── valve_model/ # 标定 JSON 与诊断 PNG
│ ├── valve_model.json
│ ├── valve_model_001.json ... valve_model_004.json
│ └── valve_model.png ... valve_model_004.png
├── 可执行程序/
│ ├── FlowControl.exe
│ ├── valve_model_004.json
│ └── 使用说明.txt
├── data/ # 闭环 CSV / PNG;历史数据可能已人工分组
├── open_loop_data/ # 开环 CSV / PNG(首次运行时创建)
├── test_valve_data/ # 手动阀门测试 CSV / PNG
└── logs/ # flow_control.log / open_loop.log
```