# 气体流量控制与阀门开环标定 本项目基于信捷 **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 中的“关闭”或“全开”均指当前代码定义的逻辑位置,不能代替现场对阀门真实动作方向的确认。 ## 硬件与默认接线 - PLC(Easy521):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_base,Ki_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` | 电机输出 D730(0~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 ```