新增气体流量控制 GUI 与 Windows 可执行程序

新增 Tkinter 单窗口操作界面和后台设备状态机,支持连接与断开 PLC、在线设置目标流量、实时显示传感器数据与下发总开度,并在监测及闭环阶段统一执行安全保护。

默认将 valve_model_004.json 内置到单文件 EXE,同时支持校验并选择外部阀模型;整理标定 JSON/PNG 到 valve_model 目录并同步配置路径。补充运行层离线测试、PyInstaller 构建配置、交付文件和完整的 README 使用及重新打包说明。
This commit is contained in:
2026-09-03 17:35:40 +08:00
parent d7ed4181df
commit ead625bf2d
51 changed files with 44578 additions and 24 deletions
+70 -23
View File
@@ -1,21 +1,23 @@
# 气体流量控制与阀门开环标定
本项目基于信捷 **Easy521 PLC**(Modbus TCP)提供三个可独立运行的在线入口和两个离线工具:
本项目基于信捷 **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 曲线图。闭环程序还会持续进行滑动窗口稳态判定,并在首次达到稳态时输出一次控制性能报告。
三个命令行在线入口都会持续保存 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%` 开度,然后断开设备。该脚本不执行流量/压力越限和连续读取失败保护,只适合在具备独立安全措施的受控测试中使用。
@@ -57,18 +59,48 @@ opening = 100% -> motor_position = 800(当前打开端/机械死区边界)
项目使用 Python 3,直接依赖:
- `pymodbus`Modbus TCP/RTU 通讯;
- `matplotlib`:使用 `Agg` 后端生成 PNG,无显示器环境也可运行
- `matplotlib`命令行入口使用 `Agg` 后端生成 PNG,无显示器环境也可运行
- `tkinter`GUI 使用的 Python 标准库组件;制作 EXE 时另需 `PyInstaller`
示例安装:
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install pymodbus matplotlib
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 闭环,运行中可继续修改目标。界面显示的“下发总开度”为最终命令:
```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`
源码运行与重新打包命令为:
```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` 只检查内置模型和关键依赖,不打开界面,也不会连接真实设备。
## 闭环控制
### 运行
@@ -129,13 +161,13 @@ opening_to_motor_position()
| 模式 | 用途 | Kp / Ki / Kd | 最大开度速度 |
| --- | --- | --- | ---: |
| `FAR` | 大误差时快速接近目标 | `0.5 / 0.5 / 0.0` | `200%/s` |
| `NEAR` | 小误差时抑制振荡和稳态误差 | `0.05 / 0.01 / 0.0` | `2%/s` |
| `NEAR` | 小误差时抑制振荡和稳态误差 | `0.1 / 0.1 / 0.0` | `2%/s` |
切换阈值同时考虑绝对误差下限和目标流量比例:
```text
near_threshold = min(3 SLM, max(1 SLM, 5% * target))
far_threshold = min(12 SLM, max(4 SLM, 20% * target))
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 个采样周期
@@ -147,7 +179,7 @@ NEAR -> FAR |error| >= far_threshold,连续满足 10 个采样周期
### 前馈 + 增益调度(MFC 结构)
闭环当前默认启用“前馈反解打底 + PI 修残差”的 MFC 结构,并加载仓库根目录的 `valve_model.json`;按阀前压力缩放 Kp/Ki 的增益调度仍默认关闭。投用前必须确认阀前/阀后压力接线和阀特性表与当前硬件一致;不满足时应关闭前馈或重新辨识。
闭环当前默认启用“前馈反解打底 + PI 修残差”的 MFC 结构,并加载 `valve_model/valve_model_004.json`;按阀前压力缩放 Kp/Ki 的增益调度仍默认关闭。投用前必须确认阀前/阀后压力接线和阀特性表与当前硬件一致;不满足时应关闭前馈或重新辨识。
静态阀特性建模为:
@@ -170,7 +202,7 @@ PI 修正量 correction = PI(目标流量 - 实测流量) # 输出限幅改为
即前馈把开度送到稳态工作点附近,PI 只负责修正模型残差和动态偏差;PI 输出不再是 `0~100%` 的全行程,而是围绕前馈的修正量(限幅由 `FEEDFORWARD_CORRECTION_BAND_PCT` 控制,当前默认 ±50%)。
为避免接近目标后压力噪声持续改写前馈,同时又能响应下游负载变化,控制器维护一个 `FEEDFORWARD_UPDATE_WINDOW_S=3 s` 的流量绝对误差滑动窗口。窗口首次积满后,程序在每个控制周期都重新计算最近 3 秒的平均绝对误差,并与 `FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM=5 SLM` 比较:均值不大于阈值时冻结上一次前馈开度,只让 PI correction 逐周期更新;均值超过阈值时重新根据当前目标流量和阀前/阀后压力反解前馈。因此,下游扰动持续影响流量并使 3 秒误差均值越过阈值后,前馈会自动解除冻结。
为避免接近目标后压力噪声持续改写前馈,同时又能响应下游负载变化,控制器维护一个 `FEEDFORWARD_UPDATE_WINDOW_S=3 s` 的流量绝对误差滑动窗口。窗口首次积满后,程序在每个控制周期都重新计算最近 3 秒的平均绝对误差,并与 `FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM=2 SLM` 比较:均值不大于阈值时冻结上一次前馈开度,只让 PI correction 逐周期更新;均值超过阈值时重新根据当前目标流量和阀前/阀后压力反解前馈。因此,下游扰动持续影响流量并使 3 秒误差均值越过阈值后,前馈会自动解除冻结。
窗口未首次积满或尚无缓存前馈时,每个周期都尝试反解前馈。窗口一旦积满,其“已就绪”状态不会因采样抖动和旧样本淘汰而反复失效;但滑动均值仍会持续更新,冻结状态并不会锁死。目标改变、控制器重启或进入零流量分支会清空窗口并重新累计 3 秒。当前周期反解失败时继续复用上次有效前馈;如果从未得到有效前馈,则以 `0%` 为基值并由 correction 调整。
@@ -189,7 +221,7 @@ Kp_eff = scale · Kp_baseKi_eff = scale · Ki_base # Kd 不调度
| --- | --- | --- |
| `FEEDFORWARD_ENABLED` | `True` | 启用前馈反解打底 + PI 修残差 |
| `GAIN_SCHEDULE_ENABLED` | `False` | 启用按阀前压力缩放 Kp/Ki |
| `VALVE_MODEL_PATH` | `"valve_model.json"` | 阀特性表 JSON 路径(空串 = 关闭前馈) |
| `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,并在启动日志中提示。
@@ -298,9 +330,9 @@ python test_valve.py
前馈需要一张 `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.json
python identify_valve.py --csv "open_loop_data/*.csv" --out valve_model.json --plot
python identify_valve.py --plot # 省略 --csv 时默认合并 open_loop_data/ 下全部 CSV
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)`,并按开度访问顺序标记逼近方向(上升 / 下降 / 该文件起始步);
@@ -310,7 +342,7 @@ python identify_valve.py --plot # 省略 --csv 时默认合并 open_loop_da
非单调表可能让同一个有效面积对应多个行程,反解时选择数值更大的行程,即更靠近关闭端的交点。目标有效面积小于表中最小实测值时直接返回机械全关行程 `MOTOR_CLOSED_POSITION`;恰好等于最小值时仍返回对应实测行程。
`config.VALVE_MODEL_PATH` 指到该 JSON、确认 `PRESSURE_BEFORE_ADDR` 接线无误、并把 `FEEDFORWARD_ENABLED` 设为 `True` 后,闭环即可启用前馈。当前配置已指向 `valve_model.json` 并默认启用重新标定后应覆盖该文件或同步修改配置路径。
`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) 是纯计算离线自测,不接触硬件,可在任意环境运行:
@@ -364,19 +396,19 @@ python -m pytest -q
| `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.05` / `0.01` / `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` / `3` / `0.05` | FAR 进入 NEAR 的误差阈值下限、上限和目标比例 |
| `PID_FAR_ERROR_MIN_SLM` / `PID_FAR_ERROR_MAX_SLM` / `PID_FAR_ERROR_TARGET_RATIO` | `4` / `12` / `0.20` | NEAR 返回 FAR 的误差阈值下限、上限和目标比例 |
| `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.json"` | 阀特性表 JSON 路径;空串关闭前馈 |
| `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` | `5` / `3` | 前馈冻结/更新的流量绝对误差均值阈值与窗口长度 |
| `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 很低时增益爆炸 |
@@ -394,6 +426,8 @@ python -m pytest -q
| 文件 | 职责 |
| --- | --- |
| [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,含输出限幅和输出变化率限幅 |
@@ -403,16 +437,21 @@ python -m pytest -q
| [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)` 表,输出 `valve_model.json` |
| [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) | 闭环性能报告器与压力带判稳的离线测试 |
| [PcControl.py](PcControl.py) | Modbus 硬件客户端;本项目三个入口实际使用 `Easy521ModbusClient` |
| [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
@@ -426,11 +465,19 @@ flow_control/
├── 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
├── valve_model.json # identify_valve.py 产出的阀特性表(供前馈使用)
└── logs/ # flow_control.log / open_loop.log
```
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -0,0 +1,829 @@
('D:\\flow_control\\build\\flow_control_gui\\flow_control_gui\\PYZ-00.pyz',
[('PcControl', 'D:\\flow_control\\PcControl.py', 'PYMODULE'),
('__future__', 'D:\\Anaconda\\Lib\\__future__.py', 'PYMODULE'),
('_colorize', 'D:\\Anaconda\\Lib\\_colorize.py', 'PYMODULE'),
('_compat_pickle', 'D:\\Anaconda\\Lib\\_compat_pickle.py', 'PYMODULE'),
('_compression', 'D:\\Anaconda\\Lib\\_compression.py', 'PYMODULE'),
('_opcode_metadata', 'D:\\Anaconda\\Lib\\_opcode_metadata.py', 'PYMODULE'),
('_py_abc', 'D:\\Anaconda\\Lib\\_py_abc.py', 'PYMODULE'),
('_pydatetime', 'D:\\Anaconda\\Lib\\_pydatetime.py', 'PYMODULE'),
('_pydecimal', 'D:\\Anaconda\\Lib\\_pydecimal.py', 'PYMODULE'),
('_strptime', 'D:\\Anaconda\\Lib\\_strptime.py', 'PYMODULE'),
('_threading_local', 'D:\\Anaconda\\Lib\\_threading_local.py', 'PYMODULE'),
('aiohappyeyeballs',
'D:\\Anaconda\\Lib\\site-packages\\aiohappyeyeballs\\__init__.py',
'PYMODULE'),
('aiohappyeyeballs._staggered',
'D:\\Anaconda\\Lib\\site-packages\\aiohappyeyeballs\\_staggered.py',
'PYMODULE'),
('aiohappyeyeballs.impl',
'D:\\Anaconda\\Lib\\site-packages\\aiohappyeyeballs\\impl.py',
'PYMODULE'),
('aiohappyeyeballs.types',
'D:\\Anaconda\\Lib\\site-packages\\aiohappyeyeballs\\types.py',
'PYMODULE'),
('aiohappyeyeballs.utils',
'D:\\Anaconda\\Lib\\site-packages\\aiohappyeyeballs\\utils.py',
'PYMODULE'),
('aiohttp',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\__init__.py',
'PYMODULE'),
('aiohttp._websocket',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\_websocket\\__init__.py',
'PYMODULE'),
('aiohttp._websocket.helpers',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\_websocket\\helpers.py',
'PYMODULE'),
('aiohttp._websocket.models',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\_websocket\\models.py',
'PYMODULE'),
('aiohttp._websocket.reader',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\_websocket\\reader.py',
'PYMODULE'),
('aiohttp._websocket.reader_py',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\_websocket\\reader_py.py',
'PYMODULE'),
('aiohttp._websocket.writer',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\_websocket\\writer.py',
'PYMODULE'),
('aiohttp.abc',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\abc.py',
'PYMODULE'),
('aiohttp.base_protocol',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\base_protocol.py',
'PYMODULE'),
('aiohttp.client',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\client.py',
'PYMODULE'),
('aiohttp.client_exceptions',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\client_exceptions.py',
'PYMODULE'),
('aiohttp.client_proto',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\client_proto.py',
'PYMODULE'),
('aiohttp.client_reqrep',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\client_reqrep.py',
'PYMODULE'),
('aiohttp.client_ws',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\client_ws.py',
'PYMODULE'),
('aiohttp.compression_utils',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\compression_utils.py',
'PYMODULE'),
('aiohttp.connector',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\connector.py',
'PYMODULE'),
('aiohttp.cookiejar',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\cookiejar.py',
'PYMODULE'),
('aiohttp.formdata',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\formdata.py',
'PYMODULE'),
('aiohttp.hdrs',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\hdrs.py',
'PYMODULE'),
('aiohttp.helpers',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\helpers.py',
'PYMODULE'),
('aiohttp.http',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\http.py',
'PYMODULE'),
('aiohttp.http_exceptions',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\http_exceptions.py',
'PYMODULE'),
('aiohttp.http_parser',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\http_parser.py',
'PYMODULE'),
('aiohttp.http_websocket',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\http_websocket.py',
'PYMODULE'),
('aiohttp.http_writer',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\http_writer.py',
'PYMODULE'),
('aiohttp.log',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\log.py',
'PYMODULE'),
('aiohttp.multipart',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\multipart.py',
'PYMODULE'),
('aiohttp.payload',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\payload.py',
'PYMODULE'),
('aiohttp.payload_streamer',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\payload_streamer.py',
'PYMODULE'),
('aiohttp.resolver',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\resolver.py',
'PYMODULE'),
('aiohttp.streams',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\streams.py',
'PYMODULE'),
('aiohttp.tcp_helpers',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\tcp_helpers.py',
'PYMODULE'),
('aiohttp.tracing',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\tracing.py',
'PYMODULE'),
('aiohttp.typedefs',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\typedefs.py',
'PYMODULE'),
('aiohttp.web',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web.py',
'PYMODULE'),
('aiohttp.web_app',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_app.py',
'PYMODULE'),
('aiohttp.web_exceptions',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_exceptions.py',
'PYMODULE'),
('aiohttp.web_fileresponse',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_fileresponse.py',
'PYMODULE'),
('aiohttp.web_log',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_log.py',
'PYMODULE'),
('aiohttp.web_middlewares',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_middlewares.py',
'PYMODULE'),
('aiohttp.web_protocol',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_protocol.py',
'PYMODULE'),
('aiohttp.web_request',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_request.py',
'PYMODULE'),
('aiohttp.web_response',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_response.py',
'PYMODULE'),
('aiohttp.web_routedef',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_routedef.py',
'PYMODULE'),
('aiohttp.web_runner',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_runner.py',
'PYMODULE'),
('aiohttp.web_server',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_server.py',
'PYMODULE'),
('aiohttp.web_urldispatcher',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_urldispatcher.py',
'PYMODULE'),
('aiohttp.web_ws',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\web_ws.py',
'PYMODULE'),
('aiohttp.worker',
'D:\\Anaconda\\Lib\\site-packages\\aiohttp\\worker.py',
'PYMODULE'),
('aiosignal',
'D:\\Anaconda\\Lib\\site-packages\\aiosignal\\__init__.py',
'PYMODULE'),
('argparse', 'D:\\Anaconda\\Lib\\argparse.py', 'PYMODULE'),
('ast', 'D:\\Anaconda\\Lib\\ast.py', 'PYMODULE'),
('asyncio', 'D:\\Anaconda\\Lib\\asyncio\\__init__.py', 'PYMODULE'),
('asyncio.base_events',
'D:\\Anaconda\\Lib\\asyncio\\base_events.py',
'PYMODULE'),
('asyncio.base_futures',
'D:\\Anaconda\\Lib\\asyncio\\base_futures.py',
'PYMODULE'),
('asyncio.base_subprocess',
'D:\\Anaconda\\Lib\\asyncio\\base_subprocess.py',
'PYMODULE'),
('asyncio.base_tasks',
'D:\\Anaconda\\Lib\\asyncio\\base_tasks.py',
'PYMODULE'),
('asyncio.constants', 'D:\\Anaconda\\Lib\\asyncio\\constants.py', 'PYMODULE'),
('asyncio.coroutines',
'D:\\Anaconda\\Lib\\asyncio\\coroutines.py',
'PYMODULE'),
('asyncio.events', 'D:\\Anaconda\\Lib\\asyncio\\events.py', 'PYMODULE'),
('asyncio.exceptions',
'D:\\Anaconda\\Lib\\asyncio\\exceptions.py',
'PYMODULE'),
('asyncio.format_helpers',
'D:\\Anaconda\\Lib\\asyncio\\format_helpers.py',
'PYMODULE'),
('asyncio.futures', 'D:\\Anaconda\\Lib\\asyncio\\futures.py', 'PYMODULE'),
('asyncio.locks', 'D:\\Anaconda\\Lib\\asyncio\\locks.py', 'PYMODULE'),
('asyncio.log', 'D:\\Anaconda\\Lib\\asyncio\\log.py', 'PYMODULE'),
('asyncio.mixins', 'D:\\Anaconda\\Lib\\asyncio\\mixins.py', 'PYMODULE'),
('asyncio.proactor_events',
'D:\\Anaconda\\Lib\\asyncio\\proactor_events.py',
'PYMODULE'),
('asyncio.protocols', 'D:\\Anaconda\\Lib\\asyncio\\protocols.py', 'PYMODULE'),
('asyncio.queues', 'D:\\Anaconda\\Lib\\asyncio\\queues.py', 'PYMODULE'),
('asyncio.runners', 'D:\\Anaconda\\Lib\\asyncio\\runners.py', 'PYMODULE'),
('asyncio.selector_events',
'D:\\Anaconda\\Lib\\asyncio\\selector_events.py',
'PYMODULE'),
('asyncio.sslproto', 'D:\\Anaconda\\Lib\\asyncio\\sslproto.py', 'PYMODULE'),
('asyncio.staggered', 'D:\\Anaconda\\Lib\\asyncio\\staggered.py', 'PYMODULE'),
('asyncio.streams', 'D:\\Anaconda\\Lib\\asyncio\\streams.py', 'PYMODULE'),
('asyncio.subprocess',
'D:\\Anaconda\\Lib\\asyncio\\subprocess.py',
'PYMODULE'),
('asyncio.taskgroups',
'D:\\Anaconda\\Lib\\asyncio\\taskgroups.py',
'PYMODULE'),
('asyncio.tasks', 'D:\\Anaconda\\Lib\\asyncio\\tasks.py', 'PYMODULE'),
('asyncio.threads', 'D:\\Anaconda\\Lib\\asyncio\\threads.py', 'PYMODULE'),
('asyncio.timeouts', 'D:\\Anaconda\\Lib\\asyncio\\timeouts.py', 'PYMODULE'),
('asyncio.transports',
'D:\\Anaconda\\Lib\\asyncio\\transports.py',
'PYMODULE'),
('asyncio.trsock', 'D:\\Anaconda\\Lib\\asyncio\\trsock.py', 'PYMODULE'),
('asyncio.unix_events',
'D:\\Anaconda\\Lib\\asyncio\\unix_events.py',
'PYMODULE'),
('asyncio.windows_events',
'D:\\Anaconda\\Lib\\asyncio\\windows_events.py',
'PYMODULE'),
('asyncio.windows_utils',
'D:\\Anaconda\\Lib\\asyncio\\windows_utils.py',
'PYMODULE'),
('attr', 'D:\\Anaconda\\Lib\\site-packages\\attr\\__init__.py', 'PYMODULE'),
('attr._cmp', 'D:\\Anaconda\\Lib\\site-packages\\attr\\_cmp.py', 'PYMODULE'),
('attr._compat',
'D:\\Anaconda\\Lib\\site-packages\\attr\\_compat.py',
'PYMODULE'),
('attr._config',
'D:\\Anaconda\\Lib\\site-packages\\attr\\_config.py',
'PYMODULE'),
('attr._funcs',
'D:\\Anaconda\\Lib\\site-packages\\attr\\_funcs.py',
'PYMODULE'),
('attr._make',
'D:\\Anaconda\\Lib\\site-packages\\attr\\_make.py',
'PYMODULE'),
('attr._next_gen',
'D:\\Anaconda\\Lib\\site-packages\\attr\\_next_gen.py',
'PYMODULE'),
('attr._version_info',
'D:\\Anaconda\\Lib\\site-packages\\attr\\_version_info.py',
'PYMODULE'),
('attr.converters',
'D:\\Anaconda\\Lib\\site-packages\\attr\\converters.py',
'PYMODULE'),
('attr.exceptions',
'D:\\Anaconda\\Lib\\site-packages\\attr\\exceptions.py',
'PYMODULE'),
('attr.filters',
'D:\\Anaconda\\Lib\\site-packages\\attr\\filters.py',
'PYMODULE'),
('attr.setters',
'D:\\Anaconda\\Lib\\site-packages\\attr\\setters.py',
'PYMODULE'),
('attr.validators',
'D:\\Anaconda\\Lib\\site-packages\\attr\\validators.py',
'PYMODULE'),
('base64', 'D:\\Anaconda\\Lib\\base64.py', 'PYMODULE'),
('bisect', 'D:\\Anaconda\\Lib\\bisect.py', 'PYMODULE'),
('brotli', 'D:\\Anaconda\\Lib\\site-packages\\brotli.py', 'PYMODULE'),
('bz2', 'D:\\Anaconda\\Lib\\bz2.py', 'PYMODULE'),
('calendar', 'D:\\Anaconda\\Lib\\calendar.py', 'PYMODULE'),
('concurrent', 'D:\\Anaconda\\Lib\\concurrent\\__init__.py', 'PYMODULE'),
('concurrent.futures',
'D:\\Anaconda\\Lib\\concurrent\\futures\\__init__.py',
'PYMODULE'),
('concurrent.futures._base',
'D:\\Anaconda\\Lib\\concurrent\\futures\\_base.py',
'PYMODULE'),
('concurrent.futures.process',
'D:\\Anaconda\\Lib\\concurrent\\futures\\process.py',
'PYMODULE'),
('concurrent.futures.thread',
'D:\\Anaconda\\Lib\\concurrent\\futures\\thread.py',
'PYMODULE'),
('config', 'D:\\flow_control\\config.py', 'PYMODULE'),
('contextlib', 'D:\\Anaconda\\Lib\\contextlib.py', 'PYMODULE'),
('contextvars', 'D:\\Anaconda\\Lib\\contextvars.py', 'PYMODULE'),
('control_runtime', 'D:\\flow_control\\control_runtime.py', 'PYMODULE'),
('controllers', 'D:\\flow_control\\controllers.py', 'PYMODULE'),
('copy', 'D:\\Anaconda\\Lib\\copy.py', 'PYMODULE'),
('csv', 'D:\\Anaconda\\Lib\\csv.py', 'PYMODULE'),
('ctypes', 'D:\\Anaconda\\Lib\\ctypes\\__init__.py', 'PYMODULE'),
('ctypes._endian', 'D:\\Anaconda\\Lib\\ctypes\\_endian.py', 'PYMODULE'),
('ctypes.wintypes', 'D:\\Anaconda\\Lib\\ctypes\\wintypes.py', 'PYMODULE'),
('dataclasses', 'D:\\Anaconda\\Lib\\dataclasses.py', 'PYMODULE'),
('datetime', 'D:\\Anaconda\\Lib\\datetime.py', 'PYMODULE'),
('decimal', 'D:\\Anaconda\\Lib\\decimal.py', 'PYMODULE'),
('dis', 'D:\\Anaconda\\Lib\\dis.py', 'PYMODULE'),
('email', 'D:\\Anaconda\\Lib\\email\\__init__.py', 'PYMODULE'),
('email._encoded_words',
'D:\\Anaconda\\Lib\\email\\_encoded_words.py',
'PYMODULE'),
('email._header_value_parser',
'D:\\Anaconda\\Lib\\email\\_header_value_parser.py',
'PYMODULE'),
('email._parseaddr', 'D:\\Anaconda\\Lib\\email\\_parseaddr.py', 'PYMODULE'),
('email._policybase', 'D:\\Anaconda\\Lib\\email\\_policybase.py', 'PYMODULE'),
('email.base64mime', 'D:\\Anaconda\\Lib\\email\\base64mime.py', 'PYMODULE'),
('email.charset', 'D:\\Anaconda\\Lib\\email\\charset.py', 'PYMODULE'),
('email.contentmanager',
'D:\\Anaconda\\Lib\\email\\contentmanager.py',
'PYMODULE'),
('email.encoders', 'D:\\Anaconda\\Lib\\email\\encoders.py', 'PYMODULE'),
('email.errors', 'D:\\Anaconda\\Lib\\email\\errors.py', 'PYMODULE'),
('email.feedparser', 'D:\\Anaconda\\Lib\\email\\feedparser.py', 'PYMODULE'),
('email.generator', 'D:\\Anaconda\\Lib\\email\\generator.py', 'PYMODULE'),
('email.header', 'D:\\Anaconda\\Lib\\email\\header.py', 'PYMODULE'),
('email.headerregistry',
'D:\\Anaconda\\Lib\\email\\headerregistry.py',
'PYMODULE'),
('email.iterators', 'D:\\Anaconda\\Lib\\email\\iterators.py', 'PYMODULE'),
('email.message', 'D:\\Anaconda\\Lib\\email\\message.py', 'PYMODULE'),
('email.parser', 'D:\\Anaconda\\Lib\\email\\parser.py', 'PYMODULE'),
('email.policy', 'D:\\Anaconda\\Lib\\email\\policy.py', 'PYMODULE'),
('email.quoprimime', 'D:\\Anaconda\\Lib\\email\\quoprimime.py', 'PYMODULE'),
('email.utils', 'D:\\Anaconda\\Lib\\email\\utils.py', 'PYMODULE'),
('flow_control', 'D:\\flow_control\\flow_control.py', 'PYMODULE'),
('fnmatch', 'D:\\Anaconda\\Lib\\fnmatch.py', 'PYMODULE'),
('fractions', 'D:\\Anaconda\\Lib\\fractions.py', 'PYMODULE'),
('frozenlist',
'D:\\Anaconda\\Lib\\site-packages\\frozenlist\\__init__.py',
'PYMODULE'),
('ftplib', 'D:\\Anaconda\\Lib\\ftplib.py', 'PYMODULE'),
('getopt', 'D:\\Anaconda\\Lib\\getopt.py', 'PYMODULE'),
('getpass', 'D:\\Anaconda\\Lib\\getpass.py', 'PYMODULE'),
('gettext', 'D:\\Anaconda\\Lib\\gettext.py', 'PYMODULE'),
('glob', 'D:\\Anaconda\\Lib\\glob.py', 'PYMODULE'),
('gzip', 'D:\\Anaconda\\Lib\\gzip.py', 'PYMODULE'),
('hashlib', 'D:\\Anaconda\\Lib\\hashlib.py', 'PYMODULE'),
('hmac', 'D:\\Anaconda\\Lib\\hmac.py', 'PYMODULE'),
('html', 'D:\\Anaconda\\Lib\\html\\__init__.py', 'PYMODULE'),
('html.entities', 'D:\\Anaconda\\Lib\\html\\entities.py', 'PYMODULE'),
('http', 'D:\\Anaconda\\Lib\\http\\__init__.py', 'PYMODULE'),
('http.client', 'D:\\Anaconda\\Lib\\http\\client.py', 'PYMODULE'),
('http.cookiejar', 'D:\\Anaconda\\Lib\\http\\cookiejar.py', 'PYMODULE'),
('http.cookies', 'D:\\Anaconda\\Lib\\http\\cookies.py', 'PYMODULE'),
('idna', 'D:\\Anaconda\\Lib\\site-packages\\idna\\__init__.py', 'PYMODULE'),
('idna.core', 'D:\\Anaconda\\Lib\\site-packages\\idna\\core.py', 'PYMODULE'),
('idna.idnadata',
'D:\\Anaconda\\Lib\\site-packages\\idna\\idnadata.py',
'PYMODULE'),
('idna.intranges',
'D:\\Anaconda\\Lib\\site-packages\\idna\\intranges.py',
'PYMODULE'),
('idna.package_data',
'D:\\Anaconda\\Lib\\site-packages\\idna\\package_data.py',
'PYMODULE'),
('idna.uts46data',
'D:\\Anaconda\\Lib\\site-packages\\idna\\uts46data.py',
'PYMODULE'),
('importlib', 'D:\\Anaconda\\Lib\\importlib\\__init__.py', 'PYMODULE'),
('importlib._abc', 'D:\\Anaconda\\Lib\\importlib\\_abc.py', 'PYMODULE'),
('importlib._bootstrap',
'D:\\Anaconda\\Lib\\importlib\\_bootstrap.py',
'PYMODULE'),
('importlib._bootstrap_external',
'D:\\Anaconda\\Lib\\importlib\\_bootstrap_external.py',
'PYMODULE'),
('importlib.abc', 'D:\\Anaconda\\Lib\\importlib\\abc.py', 'PYMODULE'),
('importlib.machinery',
'D:\\Anaconda\\Lib\\importlib\\machinery.py',
'PYMODULE'),
('importlib.metadata',
'D:\\Anaconda\\Lib\\importlib\\metadata\\__init__.py',
'PYMODULE'),
('importlib.metadata._adapters',
'D:\\Anaconda\\Lib\\importlib\\metadata\\_adapters.py',
'PYMODULE'),
('importlib.metadata._collections',
'D:\\Anaconda\\Lib\\importlib\\metadata\\_collections.py',
'PYMODULE'),
('importlib.metadata._functools',
'D:\\Anaconda\\Lib\\importlib\\metadata\\_functools.py',
'PYMODULE'),
('importlib.metadata._itertools',
'D:\\Anaconda\\Lib\\importlib\\metadata\\_itertools.py',
'PYMODULE'),
('importlib.metadata._meta',
'D:\\Anaconda\\Lib\\importlib\\metadata\\_meta.py',
'PYMODULE'),
('importlib.metadata._text',
'D:\\Anaconda\\Lib\\importlib\\metadata\\_text.py',
'PYMODULE'),
('importlib.readers', 'D:\\Anaconda\\Lib\\importlib\\readers.py', 'PYMODULE'),
('importlib.resources',
'D:\\Anaconda\\Lib\\importlib\\resources\\__init__.py',
'PYMODULE'),
('importlib.resources._adapters',
'D:\\Anaconda\\Lib\\importlib\\resources\\_adapters.py',
'PYMODULE'),
('importlib.resources._common',
'D:\\Anaconda\\Lib\\importlib\\resources\\_common.py',
'PYMODULE'),
('importlib.resources._functional',
'D:\\Anaconda\\Lib\\importlib\\resources\\_functional.py',
'PYMODULE'),
('importlib.resources._itertools',
'D:\\Anaconda\\Lib\\importlib\\resources\\_itertools.py',
'PYMODULE'),
('importlib.resources.abc',
'D:\\Anaconda\\Lib\\importlib\\resources\\abc.py',
'PYMODULE'),
('importlib.resources.readers',
'D:\\Anaconda\\Lib\\importlib\\resources\\readers.py',
'PYMODULE'),
('importlib.util', 'D:\\Anaconda\\Lib\\importlib\\util.py', 'PYMODULE'),
('inspect', 'D:\\Anaconda\\Lib\\inspect.py', 'PYMODULE'),
('ipaddress', 'D:\\Anaconda\\Lib\\ipaddress.py', 'PYMODULE'),
('json', 'D:\\Anaconda\\Lib\\json\\__init__.py', 'PYMODULE'),
('json.decoder', 'D:\\Anaconda\\Lib\\json\\decoder.py', 'PYMODULE'),
('json.encoder', 'D:\\Anaconda\\Lib\\json\\encoder.py', 'PYMODULE'),
('json.scanner', 'D:\\Anaconda\\Lib\\json\\scanner.py', 'PYMODULE'),
('logging', 'D:\\Anaconda\\Lib\\logging\\__init__.py', 'PYMODULE'),
('logging.handlers', 'D:\\Anaconda\\Lib\\logging\\handlers.py', 'PYMODULE'),
('lzma', 'D:\\Anaconda\\Lib\\lzma.py', 'PYMODULE'),
('mimetypes', 'D:\\Anaconda\\Lib\\mimetypes.py', 'PYMODULE'),
('multidict',
'D:\\Anaconda\\Lib\\site-packages\\multidict\\__init__.py',
'PYMODULE'),
('multidict._abc',
'D:\\Anaconda\\Lib\\site-packages\\multidict\\_abc.py',
'PYMODULE'),
('multidict._compat',
'D:\\Anaconda\\Lib\\site-packages\\multidict\\_compat.py',
'PYMODULE'),
('multidict._multidict_py',
'D:\\Anaconda\\Lib\\site-packages\\multidict\\_multidict_py.py',
'PYMODULE'),
('multiprocessing',
'D:\\Anaconda\\Lib\\multiprocessing\\__init__.py',
'PYMODULE'),
('multiprocessing.connection',
'D:\\Anaconda\\Lib\\multiprocessing\\connection.py',
'PYMODULE'),
('multiprocessing.context',
'D:\\Anaconda\\Lib\\multiprocessing\\context.py',
'PYMODULE'),
('multiprocessing.dummy',
'D:\\Anaconda\\Lib\\multiprocessing\\dummy\\__init__.py',
'PYMODULE'),
('multiprocessing.dummy.connection',
'D:\\Anaconda\\Lib\\multiprocessing\\dummy\\connection.py',
'PYMODULE'),
('multiprocessing.forkserver',
'D:\\Anaconda\\Lib\\multiprocessing\\forkserver.py',
'PYMODULE'),
('multiprocessing.heap',
'D:\\Anaconda\\Lib\\multiprocessing\\heap.py',
'PYMODULE'),
('multiprocessing.managers',
'D:\\Anaconda\\Lib\\multiprocessing\\managers.py',
'PYMODULE'),
('multiprocessing.pool',
'D:\\Anaconda\\Lib\\multiprocessing\\pool.py',
'PYMODULE'),
('multiprocessing.popen_fork',
'D:\\Anaconda\\Lib\\multiprocessing\\popen_fork.py',
'PYMODULE'),
('multiprocessing.popen_forkserver',
'D:\\Anaconda\\Lib\\multiprocessing\\popen_forkserver.py',
'PYMODULE'),
('multiprocessing.popen_spawn_posix',
'D:\\Anaconda\\Lib\\multiprocessing\\popen_spawn_posix.py',
'PYMODULE'),
('multiprocessing.popen_spawn_win32',
'D:\\Anaconda\\Lib\\multiprocessing\\popen_spawn_win32.py',
'PYMODULE'),
('multiprocessing.process',
'D:\\Anaconda\\Lib\\multiprocessing\\process.py',
'PYMODULE'),
('multiprocessing.queues',
'D:\\Anaconda\\Lib\\multiprocessing\\queues.py',
'PYMODULE'),
('multiprocessing.reduction',
'D:\\Anaconda\\Lib\\multiprocessing\\reduction.py',
'PYMODULE'),
('multiprocessing.resource_sharer',
'D:\\Anaconda\\Lib\\multiprocessing\\resource_sharer.py',
'PYMODULE'),
('multiprocessing.resource_tracker',
'D:\\Anaconda\\Lib\\multiprocessing\\resource_tracker.py',
'PYMODULE'),
('multiprocessing.shared_memory',
'D:\\Anaconda\\Lib\\multiprocessing\\shared_memory.py',
'PYMODULE'),
('multiprocessing.sharedctypes',
'D:\\Anaconda\\Lib\\multiprocessing\\sharedctypes.py',
'PYMODULE'),
('multiprocessing.spawn',
'D:\\Anaconda\\Lib\\multiprocessing\\spawn.py',
'PYMODULE'),
('multiprocessing.synchronize',
'D:\\Anaconda\\Lib\\multiprocessing\\synchronize.py',
'PYMODULE'),
('multiprocessing.util',
'D:\\Anaconda\\Lib\\multiprocessing\\util.py',
'PYMODULE'),
('netrc', 'D:\\Anaconda\\Lib\\netrc.py', 'PYMODULE'),
('nturl2path', 'D:\\Anaconda\\Lib\\nturl2path.py', 'PYMODULE'),
('numbers', 'D:\\Anaconda\\Lib\\numbers.py', 'PYMODULE'),
('opcode', 'D:\\Anaconda\\Lib\\opcode.py', 'PYMODULE'),
('pathlib', 'D:\\Anaconda\\Lib\\pathlib\\__init__.py', 'PYMODULE'),
('pathlib._abc', 'D:\\Anaconda\\Lib\\pathlib\\_abc.py', 'PYMODULE'),
('pathlib._local', 'D:\\Anaconda\\Lib\\pathlib\\_local.py', 'PYMODULE'),
('pickle', 'D:\\Anaconda\\Lib\\pickle.py', 'PYMODULE'),
('pkgutil', 'D:\\Anaconda\\Lib\\pkgutil.py', 'PYMODULE'),
('platform', 'D:\\Anaconda\\Lib\\platform.py', 'PYMODULE'),
('pprint', 'D:\\Anaconda\\Lib\\pprint.py', 'PYMODULE'),
('propcache',
'D:\\Anaconda\\Lib\\site-packages\\propcache\\__init__.py',
'PYMODULE'),
('propcache._helpers',
'D:\\Anaconda\\Lib\\site-packages\\propcache\\_helpers.py',
'PYMODULE'),
('propcache._helpers_py',
'D:\\Anaconda\\Lib\\site-packages\\propcache\\_helpers_py.py',
'PYMODULE'),
('propcache.api',
'D:\\Anaconda\\Lib\\site-packages\\propcache\\api.py',
'PYMODULE'),
('py_compile', 'D:\\Anaconda\\Lib\\py_compile.py', 'PYMODULE'),
('pymodbus',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\__init__.py',
'PYMODULE'),
('pymodbus.bit_read_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\bit_read_message.py',
'PYMODULE'),
('pymodbus.bit_write_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\bit_write_message.py',
'PYMODULE'),
('pymodbus.client',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\__init__.py',
'PYMODULE'),
('pymodbus.client.base',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\base.py',
'PYMODULE'),
('pymodbus.client.mixin',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\mixin.py',
'PYMODULE'),
('pymodbus.client.serial',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\serial.py',
'PYMODULE'),
('pymodbus.client.tcp',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\tcp.py',
'PYMODULE'),
('pymodbus.client.tls',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\tls.py',
'PYMODULE'),
('pymodbus.client.udp',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\client\\udp.py',
'PYMODULE'),
('pymodbus.constants',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\constants.py',
'PYMODULE'),
('pymodbus.datastore',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\datastore\\__init__.py',
'PYMODULE'),
('pymodbus.datastore.context',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\datastore\\context.py',
'PYMODULE'),
('pymodbus.datastore.remote',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\datastore\\remote.py',
'PYMODULE'),
('pymodbus.datastore.simulator',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\datastore\\simulator.py',
'PYMODULE'),
('pymodbus.datastore.store',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\datastore\\store.py',
'PYMODULE'),
('pymodbus.device',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\device.py',
'PYMODULE'),
('pymodbus.diag_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\diag_message.py',
'PYMODULE'),
('pymodbus.events',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\events.py',
'PYMODULE'),
('pymodbus.exceptions',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\exceptions.py',
'PYMODULE'),
('pymodbus.factory',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\factory.py',
'PYMODULE'),
('pymodbus.file_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\file_message.py',
'PYMODULE'),
('pymodbus.framer',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\__init__.py',
'PYMODULE'),
('pymodbus.framer.ascii_framer',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\ascii_framer.py',
'PYMODULE'),
('pymodbus.framer.base',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\base.py',
'PYMODULE'),
('pymodbus.framer.binary_framer',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\binary_framer.py',
'PYMODULE'),
('pymodbus.framer.rtu_framer',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\rtu_framer.py',
'PYMODULE'),
('pymodbus.framer.socket_framer',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\socket_framer.py',
'PYMODULE'),
('pymodbus.framer.tls_framer',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\framer\\tls_framer.py',
'PYMODULE'),
('pymodbus.logging',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\logging.py',
'PYMODULE'),
('pymodbus.mei_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\mei_message.py',
'PYMODULE'),
('pymodbus.message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\__init__.py',
'PYMODULE'),
('pymodbus.message.ascii',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\ascii.py',
'PYMODULE'),
('pymodbus.message.base',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\base.py',
'PYMODULE'),
('pymodbus.message.message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\message.py',
'PYMODULE'),
('pymodbus.message.raw',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\raw.py',
'PYMODULE'),
('pymodbus.message.rtu',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\rtu.py',
'PYMODULE'),
('pymodbus.message.socket',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\socket.py',
'PYMODULE'),
('pymodbus.message.tls',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\message\\tls.py',
'PYMODULE'),
('pymodbus.other_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\other_message.py',
'PYMODULE'),
('pymodbus.payload',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\payload.py',
'PYMODULE'),
('pymodbus.pdu',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\pdu.py',
'PYMODULE'),
('pymodbus.register_read_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\register_read_message.py',
'PYMODULE'),
('pymodbus.register_write_message',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\register_write_message.py',
'PYMODULE'),
('pymodbus.server',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\server\\__init__.py',
'PYMODULE'),
('pymodbus.server.async_io',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\server\\async_io.py',
'PYMODULE'),
('pymodbus.server.simulator',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\server\\simulator\\__init__.py',
'PYMODULE'),
('pymodbus.server.simulator.custom_actions',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\server\\simulator\\custom_actions.py',
'PYMODULE'),
('pymodbus.server.simulator.http_server',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\server\\simulator\\http_server.py',
'PYMODULE'),
('pymodbus.server.simulator.main',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\server\\simulator\\main.py',
'PYMODULE'),
('pymodbus.transaction',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\transaction.py',
'PYMODULE'),
('pymodbus.transport',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\transport\\__init__.py',
'PYMODULE'),
('pymodbus.transport.serialtransport',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\transport\\serialtransport.py',
'PYMODULE'),
('pymodbus.transport.transport',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\transport\\transport.py',
'PYMODULE'),
('pymodbus.utilities',
'D:\\Anaconda\\Lib\\site-packages\\pymodbus\\utilities.py',
'PYMODULE'),
('queue', 'D:\\Anaconda\\Lib\\queue.py', 'PYMODULE'),
('quopri', 'D:\\Anaconda\\Lib\\quopri.py', 'PYMODULE'),
('random', 'D:\\Anaconda\\Lib\\random.py', 'PYMODULE'),
('runpy', 'D:\\Anaconda\\Lib\\runpy.py', 'PYMODULE'),
('secrets', 'D:\\Anaconda\\Lib\\secrets.py', 'PYMODULE'),
('selectors', 'D:\\Anaconda\\Lib\\selectors.py', 'PYMODULE'),
('serial',
'D:\\Anaconda\\Lib\\site-packages\\serial\\__init__.py',
'PYMODULE'),
('serial.serialcli',
'D:\\Anaconda\\Lib\\site-packages\\serial\\serialcli.py',
'PYMODULE'),
('serial.serialjava',
'D:\\Anaconda\\Lib\\site-packages\\serial\\serialjava.py',
'PYMODULE'),
('serial.serialposix',
'D:\\Anaconda\\Lib\\site-packages\\serial\\serialposix.py',
'PYMODULE'),
('serial.serialutil',
'D:\\Anaconda\\Lib\\site-packages\\serial\\serialutil.py',
'PYMODULE'),
('serial.serialwin32',
'D:\\Anaconda\\Lib\\site-packages\\serial\\serialwin32.py',
'PYMODULE'),
('serial.win32',
'D:\\Anaconda\\Lib\\site-packages\\serial\\win32.py',
'PYMODULE'),
('shutil', 'D:\\Anaconda\\Lib\\shutil.py', 'PYMODULE'),
('signal', 'D:\\Anaconda\\Lib\\signal.py', 'PYMODULE'),
('smtplib', 'D:\\Anaconda\\Lib\\smtplib.py', 'PYMODULE'),
('socket', 'D:\\Anaconda\\Lib\\socket.py', 'PYMODULE'),
('ssl', 'D:\\Anaconda\\Lib\\ssl.py', 'PYMODULE'),
('statistics', 'D:\\Anaconda\\Lib\\statistics.py', 'PYMODULE'),
('string', 'D:\\Anaconda\\Lib\\string.py', 'PYMODULE'),
('stringprep', 'D:\\Anaconda\\Lib\\stringprep.py', 'PYMODULE'),
('subprocess', 'D:\\Anaconda\\Lib\\subprocess.py', 'PYMODULE'),
('tarfile', 'D:\\Anaconda\\Lib\\tarfile.py', 'PYMODULE'),
('tempfile', 'D:\\Anaconda\\Lib\\tempfile.py', 'PYMODULE'),
('textwrap', 'D:\\Anaconda\\Lib\\textwrap.py', 'PYMODULE'),
('threading', 'D:\\Anaconda\\Lib\\threading.py', 'PYMODULE'),
('tkinter', 'D:\\Anaconda\\Lib\\tkinter\\__init__.py', 'PYMODULE'),
('tkinter.commondialog',
'D:\\Anaconda\\Lib\\tkinter\\commondialog.py',
'PYMODULE'),
('tkinter.constants', 'D:\\Anaconda\\Lib\\tkinter\\constants.py', 'PYMODULE'),
('tkinter.dialog', 'D:\\Anaconda\\Lib\\tkinter\\dialog.py', 'PYMODULE'),
('tkinter.filedialog',
'D:\\Anaconda\\Lib\\tkinter\\filedialog.py',
'PYMODULE'),
('tkinter.messagebox',
'D:\\Anaconda\\Lib\\tkinter\\messagebox.py',
'PYMODULE'),
('tkinter.simpledialog',
'D:\\Anaconda\\Lib\\tkinter\\simpledialog.py',
'PYMODULE'),
('tkinter.ttk', 'D:\\Anaconda\\Lib\\tkinter\\ttk.py', 'PYMODULE'),
('token', 'D:\\Anaconda\\Lib\\token.py', 'PYMODULE'),
('tokenize', 'D:\\Anaconda\\Lib\\tokenize.py', 'PYMODULE'),
('tracemalloc', 'D:\\Anaconda\\Lib\\tracemalloc.py', 'PYMODULE'),
('typing', 'D:\\Anaconda\\Lib\\typing.py', 'PYMODULE'),
('urllib', 'D:\\Anaconda\\Lib\\urllib\\__init__.py', 'PYMODULE'),
('urllib.error', 'D:\\Anaconda\\Lib\\urllib\\error.py', 'PYMODULE'),
('urllib.parse', 'D:\\Anaconda\\Lib\\urllib\\parse.py', 'PYMODULE'),
('urllib.request', 'D:\\Anaconda\\Lib\\urllib\\request.py', 'PYMODULE'),
('urllib.response', 'D:\\Anaconda\\Lib\\urllib\\response.py', 'PYMODULE'),
('uuid', 'D:\\Anaconda\\Lib\\uuid.py', 'PYMODULE'),
('valve_model', 'D:\\flow_control\\valve_model.py', 'PYMODULE'),
('win32con',
'D:\\Anaconda\\Lib\\site-packages\\win32\\lib\\win32con.py',
'PYMODULE'),
('win32evtlogutil',
'D:\\Anaconda\\Lib\\site-packages\\win32\\lib\\win32evtlogutil.py',
'PYMODULE'),
('winerror',
'D:\\Anaconda\\Lib\\site-packages\\win32\\lib\\winerror.py',
'PYMODULE'),
('xml', 'D:\\Anaconda\\Lib\\xml\\__init__.py', 'PYMODULE'),
('xml.parsers', 'D:\\Anaconda\\Lib\\xml\\parsers\\__init__.py', 'PYMODULE'),
('xml.parsers.expat',
'D:\\Anaconda\\Lib\\xml\\parsers\\expat.py',
'PYMODULE'),
('xml.sax', 'D:\\Anaconda\\Lib\\xml\\sax\\__init__.py', 'PYMODULE'),
('xml.sax._exceptions',
'D:\\Anaconda\\Lib\\xml\\sax\\_exceptions.py',
'PYMODULE'),
('xml.sax.expatreader',
'D:\\Anaconda\\Lib\\xml\\sax\\expatreader.py',
'PYMODULE'),
('xml.sax.handler', 'D:\\Anaconda\\Lib\\xml\\sax\\handler.py', 'PYMODULE'),
('xml.sax.saxutils', 'D:\\Anaconda\\Lib\\xml\\sax\\saxutils.py', 'PYMODULE'),
('xml.sax.xmlreader',
'D:\\Anaconda\\Lib\\xml\\sax\\xmlreader.py',
'PYMODULE'),
('xmlrpc', 'D:\\Anaconda\\Lib\\xmlrpc\\__init__.py', 'PYMODULE'),
('xmlrpc.client', 'D:\\Anaconda\\Lib\\xmlrpc\\client.py', 'PYMODULE'),
('yarl', 'D:\\Anaconda\\Lib\\site-packages\\yarl\\__init__.py', 'PYMODULE'),
('yarl._parse',
'D:\\Anaconda\\Lib\\site-packages\\yarl\\_parse.py',
'PYMODULE'),
('yarl._path',
'D:\\Anaconda\\Lib\\site-packages\\yarl\\_path.py',
'PYMODULE'),
('yarl._query',
'D:\\Anaconda\\Lib\\site-packages\\yarl\\_query.py',
'PYMODULE'),
('yarl._quoters',
'D:\\Anaconda\\Lib\\site-packages\\yarl\\_quoters.py',
'PYMODULE'),
('yarl._quoting',
'D:\\Anaconda\\Lib\\site-packages\\yarl\\_quoting.py',
'PYMODULE'),
('yarl._quoting_py',
'D:\\Anaconda\\Lib\\site-packages\\yarl\\_quoting_py.py',
'PYMODULE'),
('yarl._url', 'D:\\Anaconda\\Lib\\site-packages\\yarl\\_url.py', 'PYMODULE'),
('zipfile', 'D:\\Anaconda\\Lib\\zipfile\\__init__.py', 'PYMODULE'),
('zipfile._path',
'D:\\Anaconda\\Lib\\zipfile\\_path\\__init__.py',
'PYMODULE'),
('zipfile._path.glob',
'D:\\Anaconda\\Lib\\zipfile\\_path\\glob.py',
'PYMODULE'),
('zipimport', 'D:\\Anaconda\\Lib\\zipimport.py', 'PYMODULE')])
@@ -0,0 +1,49 @@
This file lists modules PyInstaller was not able to find. This does not
necessarily mean these modules are required for running your program. Both
Python's standard library and 3rd-party Python packages often conditionally
import optional modules, some of which may be available only on certain
platforms.
Types of import:
* top-level: imported at the top-level - look at these first
* conditional: imported within an if-statement
* delayed: imported within a function
* optional: imported within a try-except-statement
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional), netrc (delayed, conditional), getpass (delayed, optional)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional)
missing module named 'collections.abc' - imported by traceback (top-level), inspect (top-level), logging (top-level), typing (top-level), importlib.resources.readers (top-level), selectors (top-level), tracemalloc (top-level), http.client (top-level), asyncio.base_events (top-level), asyncio.coroutines (top-level), multidict._abc (top-level), attr._compat (top-level), yarl._query (top-level), yarl._url (top-level), propcache._helpers_py (top-level), yarl._path (top-level), aiohttp.web (top-level), aiohttp.abc (top-level), frozenlist (top-level), aiohttp.payload (conditional), aiohttp.web_response (top-level)
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
missing module named fcntl - imported by subprocess (optional), serial.serialposix (top-level)
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
missing module named _scproxy - imported by urllib.request (conditional)
missing module named termios - imported by serial.serialposix (top-level), getpass (optional)
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
missing module named resource - imported by posix (top-level)
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
missing module named pyimod02_importers - imported by D:\Anaconda\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
missing module named 'System.IO' - imported by serial.serialcli (top-level)
missing module named System - imported by serial.serialcli (top-level)
missing module named async_timeout - imported by aiohttp.helpers (conditional), aiohttp.web_ws (conditional), aiohttp.client_ws (conditional)
missing module named brotlicffi - imported by aiohttp.compression_utils (optional)
missing module named aiodns - imported by aiohttp.resolver (optional)
missing module named vms_lib - imported by platform (delayed, optional)
missing module named 'java.lang' - imported by platform (delayed, optional)
missing module named java - imported by platform (delayed)
missing module named annotationlib - imported by attr._compat (conditional)
missing module named uvloop - imported by aiohttp.worker (delayed)
missing module named 'gunicorn.workers' - imported by aiohttp.worker (top-level)
missing module named gunicorn - imported by aiohttp.worker (top-level)
missing module named _suggestions - imported by traceback (delayed, optional)
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+1 -1
View File
@@ -122,7 +122,7 @@ INITIAL_OPENING_PCT = 100.0
# 阀前压力 P1(前馈缩放 / 判阻塞 / 增益调度用),地址见上 PLC 段 PRESSURE_BEFORE_ADDR。
# 阀特性表 JSONidentify_valve.py 产出)。空字符串表示关闭前馈。
VALVE_MODEL_PATH = "valve_model_004.json"
VALVE_MODEL_PATH = "valve_model/valve_model_004.json"
# 前馈反解打底 + PI 修残差(市面 MFC 结构)。
FEEDFORWARD_ENABLED = True
+613
View File
@@ -0,0 +1,613 @@
"""桌面 GUI 使用的设备与闭环运行层。
所有 Modbus 访问都在 :class:`ControlWorker` 的后台线程中执行。GUI 主线程只
发送命令并消费事件,避免网络超时阻塞界面。
"""
from __future__ import annotations
from dataclasses import dataclass
import json
import logging
from logging.handlers import RotatingFileHandler
import math
from pathlib import Path
from queue import Empty, Queue
import sys
import threading
import time
import config
from controllers import IncrementalPID
from flow_control import FlowControlFault, FlowControlLoop
from valve_model import ValveModel
APP_NAME = "FlowControl"
BUILTIN_MODEL_NAME = "valve_model_004.json"
MONITOR_PERIOD_S = 0.25
@dataclass(frozen=True)
class DeviceSettings:
"""PLC 连接参数。"""
host: str
port: int
slave_id: int
@dataclass(frozen=True)
class ModelInfo:
"""已经校验并加载的阀模型。"""
model: ValveModel
path: Path
display_name: str
point_count: int
is_builtin: bool
is_monotonic: bool
def application_directory() -> Path:
"""返回日志等可写文件的程序目录。"""
if getattr(sys, "frozen", False):
return Path(sys.executable).resolve().parent
return Path(__file__).resolve().parent
def resource_path(name: str) -> Path:
"""同时支持源码运行和 PyInstaller 单文件资源。"""
bundle_root = getattr(sys, "_MEIPASS", None)
base = Path(bundle_root) if bundle_root else Path(__file__).resolve().parent
direct = base / name
if direct.exists():
return direct
return base / "valve_model" / name
def build_runtime_logger() -> logging.Logger:
"""创建 GUI 运行日志,默认写到 EXE 旁的 logs 目录。"""
log_dir = application_directory() / config.LOG_DIRECTORY
log_dir.mkdir(parents=True, exist_ok=True)
log_path = log_dir / config.LOG_FILE_NAME
logger = logging.getLogger("flow_control_gui")
logger.setLevel(logging.INFO)
logger.propagate = False
logger.handlers.clear()
handler = RotatingFileHandler(
log_path,
maxBytes=2 * 1024 * 1024,
backupCount=3,
encoding="utf-8",
)
handler.setFormatter(
logging.Formatter(
"%(asctime)s.%(msecs)03d %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
)
logger.addHandler(handler)
return logger
def validate_device_settings(host, port, slave_id) -> DeviceSettings:
"""解析并校验 UI 输入的 PLC 连接参数。"""
host_text = str(host).strip()
if not host_text or any(char.isspace() for char in host_text):
raise ValueError("PLC 地址不能为空或包含空格")
try:
port_value = int(str(port).strip())
except (TypeError, ValueError) as exc:
raise ValueError("端口必须是整数") from exc
try:
slave_value = int(str(slave_id).strip())
except (TypeError, ValueError) as exc:
raise ValueError("站号必须是整数") from exc
if not 1 <= port_value <= 65535:
raise ValueError("端口必须位于 165535")
if not 0 <= slave_value <= 247:
raise ValueError("站号必须位于 0247")
return DeviceSettings(host_text, port_value, slave_value)
def validate_target(target) -> float:
"""解析并校验目标流量。"""
try:
value = float(str(target).strip())
except (TypeError, ValueError) as exc:
raise ValueError("目标流量必须是数字") from exc
if not math.isfinite(value):
raise ValueError("目标流量必须是有限数值")
if not config.TARGET_FLOW_MIN_SLM <= value <= config.TARGET_FLOW_MAX_SLM:
raise ValueError(
f"目标流量必须位于 {config.TARGET_FLOW_MIN_SLM:g}"
f"{config.TARGET_FLOW_MAX_SLM:g} SLM"
)
return value
def load_model(model_path: str | Path | None = None) -> ModelInfo:
"""加载内置或外部阀模型,并检查其是否匹配当前行程配置。"""
is_builtin = model_path is None
path = resource_path(BUILTIN_MODEL_NAME) if is_builtin else Path(model_path)
path = path.expanduser().resolve()
if not path.is_file():
raise ValueError(f"阀模型文件不存在:{path}")
try:
with path.open("r", encoding="utf-8") as file:
raw = json.load(file)
except (OSError, json.JSONDecodeError) as exc:
raise ValueError(f"无法读取阀模型:{exc}") from exc
try:
declared_open = float(raw["motor_open"])
declared_closed = float(raw["motor_closed"])
area_table = raw["area_table"]
except (KeyError, TypeError, ValueError) as exc:
raise ValueError("阀模型缺少有效的 motor_open、motor_closed 或 area_table") from exc
if not math.isclose(declared_open, config.MOTOR_OPEN_POSITION, abs_tol=1e-9):
raise ValueError(
f"模型打开端行程为 {declared_open:g},当前配置要求 "
f"{config.MOTOR_OPEN_POSITION:g}"
)
if not math.isclose(declared_closed, config.MOTOR_CLOSED_POSITION, abs_tol=1e-9):
raise ValueError(
f"模型关闭端行程为 {declared_closed:g},当前配置要求 "
f"{config.MOTOR_CLOSED_POSITION:g}"
)
if not isinstance(area_table, list) or len(area_table) < 2:
raise ValueError("阀模型 area_table 至少需要两个标定点")
try:
model = ValveModel.load(
str(path),
config.MOTOR_OPEN_POSITION,
config.MOTOR_CLOSED_POSITION,
)
except (OSError, KeyError, TypeError, ValueError) as exc:
raise ValueError(f"阀模型内容无效:{exc}") from exc
for stroke, _area in model.area_table:
if not config.MOTOR_OPEN_POSITION <= stroke <= config.MOTOR_CLOSED_POSITION:
raise ValueError(
f"模型行程 {stroke:g} 超出当前有效范围 "
f"{config.MOTOR_OPEN_POSITION:g}{config.MOTOR_CLOSED_POSITION:g}"
)
prefix = "内置" if is_builtin else "外部"
return ModelInfo(
model=model,
path=path,
display_name=f"{prefix} {path.name}{len(model.area_table)} 点)",
point_count=len(model.area_table),
is_builtin=is_builtin,
is_monotonic=model.is_monotonic,
)
def build_control_loop(hardware, valve_model, logger) -> FlowControlLoop:
"""按 config 构造闭环控制器,不引入命令行记录和绘图依赖。"""
pid = IncrementalPID(
kp=config.PID_FAR_KP,
ki=config.PID_FAR_KI,
kd=config.PID_FAR_KD,
dt=config.CONTROL_PERIOD_S,
out_min=config.OPENING_MIN_PCT,
out_max=config.OPENING_MAX_PCT,
output_rate_limit=config.MAX_OPENING_RATE_FAR_PCT_S,
)
return FlowControlLoop(
hardware=hardware,
pid=pid,
period_s=config.CONTROL_PERIOD_S,
flow_channel=config.FLOW_AFTER_ADDR,
pressure_channel=config.PRESSURE_AFTER_ADDR,
pressure_before_channel=config.PRESSURE_BEFORE_ADDR,
motor_channel=config.MOTOR_OUTPUT_ADDR,
motor_open_position=config.MOTOR_OPEN_POSITION,
motor_closed_position=config.MOTOR_CLOSED_POSITION,
target_min_slm=config.TARGET_FLOW_MIN_SLM,
target_max_slm=config.TARGET_FLOW_MAX_SLM,
zero_flow_threshold_slm=config.ZERO_FLOW_THRESHOLD_SLM,
flow_valid_min_slm=config.FLOW_VALID_MIN_SLM,
flow_valid_max_slm=config.FLOW_VALID_MAX_SLM,
max_pressure_kpa=config.MAX_PRESSURE_KPA,
max_control_dt_s=config.MAX_CONTROL_DT_S,
max_consecutive_flow_failures=config.MAX_CONSECUTIVE_FLOW_FAILURES,
max_consecutive_pressure_failures=config.MAX_CONSECUTIVE_PRESSURE_FAILURES,
pid_far_gains=(config.PID_FAR_KP, config.PID_FAR_KI, config.PID_FAR_KD),
pid_near_gains=(
config.PID_NEAR_KP,
config.PID_NEAR_KI,
config.PID_NEAR_KD,
),
opening_rate_far_pct_s=config.MAX_OPENING_RATE_FAR_PCT_S,
opening_rate_near_pct_s=config.MAX_OPENING_RATE_NEAR_PCT_S,
pid_near_error_min_slm=config.PID_NEAR_ERROR_MIN_SLM,
pid_near_error_max_slm=config.PID_NEAR_ERROR_MAX_SLM,
pid_near_error_target_ratio=config.PID_NEAR_ERROR_TARGET_RATIO,
pid_far_error_min_slm=config.PID_FAR_ERROR_MIN_SLM,
pid_far_error_max_slm=config.PID_FAR_ERROR_MAX_SLM,
pid_far_error_target_ratio=config.PID_FAR_ERROR_TARGET_RATIO,
pid_mode_switch_confirm_cycles=config.PID_MODE_SWITCH_CONFIRM_CYCLES,
valve_model=valve_model,
feedforward_enabled=config.FEEDFORWARD_ENABLED,
feedforward_correction_band_pct=config.FEEDFORWARD_CORRECTION_BAND_PCT,
feedforward_update_error_threshold_slm=(
config.FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM
),
feedforward_update_window_s=config.FEEDFORWARD_UPDATE_WINDOW_S,
gain_schedule_enabled=config.GAIN_SCHEDULE_ENABLED,
gain_schedule_ref_abs_kpa=config.GAIN_SCHEDULE_REF_ABS_KPA,
gain_schedule_floor_abs_kpa=config.GAIN_SCHEDULE_FLOOR_ABS_KPA,
gain_schedule_scale_change_threshold=(
config.GAIN_SCHEDULE_SCALE_CHANGE_THRESHOLD
),
logger=logger,
)
def create_hardware(settings: DeviceSettings):
"""延迟导入硬件模块,便于离线测试 GUI 运行层。"""
from PcControl import Easy521ModbusClient
return Easy521ModbusClient(
host=settings.host,
port=settings.port,
slave_id=settings.slave_id,
flow_addr=config.FLOW_AFTER_ADDR,
flow_range=config.FLOW_AFTER_RANGE_SLM,
pressure_before_addr=config.PRESSURE_BEFORE_ADDR,
pressure_before_range=config.PRESSURE_BEFORE_RANGE_KPA,
pressure_after_addr=config.PRESSURE_AFTER_ADDR,
pressure_after_range=config.PRESSURE_AFTER_RANGE_KPA,
motor_output_addr=config.MOTOR_OUTPUT_ADDR,
)
class ControlWorker:
"""拥有设备连接的后台线程,通过命令/事件队列与 GUI 通信。"""
def __init__(
self,
logger,
*,
hardware_factory=create_hardware,
model_loader=load_model,
controller_factory=build_control_loop,
):
self.logger = logger
self.hardware_factory = hardware_factory
self.model_loader = model_loader
self.controller_factory = controller_factory
self.commands = Queue()
self.events = Queue()
self.thread = threading.Thread(
target=self._run,
name="flow-control-worker",
daemon=False,
)
self._shutdown_requested = False
self._state = "DISCONNECTED"
self._hardware = None
self._controller = None
self._next_cycle = 0.0
self._monitor_failures = {
"FLOW": 0,
"PRESSURE_BEFORE": 0,
"PRESSURE_AFTER": 0,
}
@property
def state(self):
return self._state
def start(self):
self.thread.start()
def is_alive(self):
return self.thread.is_alive()
def request_connect(self, settings: DeviceSettings, model_path=None):
self.commands.put(("CONNECT", {"settings": settings, "model_path": model_path}))
def request_target(self, target):
self.commands.put(("SET_TARGET", {"target": target}))
def request_disconnect(self):
self.commands.put(("DISCONNECT", {}))
def request_shutdown(self):
self.commands.put(("SHUTDOWN", {}))
def _emit(self, kind, **payload):
payload["kind"] = kind
self.events.put(payload)
def _set_state(self, state, message):
self._state = state
self._emit("state", state=state, message=message)
def _run(self):
self._set_state("DISCONNECTED", "请选择模型并连接设备")
try:
while not self._shutdown_requested:
timeout = self._command_timeout()
command = None
try:
command = self.commands.get(timeout=timeout)
except Empty:
pass
if command is not None:
self._handle_command(*command)
if self._shutdown_requested:
break
now = time.perf_counter()
if self._state == "MONITORING" and now >= self._next_cycle:
self._monitor_once()
self._advance_deadline(now, MONITOR_PERIOD_S)
elif self._state == "CONTROLLING" and now >= self._next_cycle:
self._control_once(now)
self._advance_deadline(now, config.CONTROL_PERIOD_S)
finally:
errors = self._safe_disconnect()
if errors:
self.logger.error("程序退出时安全断开存在问题:%s", "; ".join(errors))
self._emit("shutdown_complete", errors=errors)
def _command_timeout(self):
if self._state in {"MONITORING", "CONTROLLING"}:
return max(0.0, min(0.05, self._next_cycle - time.perf_counter()))
return 0.1
def _advance_deadline(self, now, period):
self._next_cycle += period
if self._next_cycle <= now:
self._next_cycle = time.perf_counter() + period
def _handle_command(self, command, payload):
if command == "CONNECT":
self._connect(payload["settings"], payload.get("model_path"))
elif command == "SET_TARGET":
self._set_target(payload["target"])
elif command == "DISCONNECT":
self._disconnect()
elif command == "SHUTDOWN":
self._set_state("STOPPING", "正在安全退出…")
self._shutdown_requested = True
def _connect(self, settings, model_path):
if self._state not in {"DISCONNECTED", "FAULT"}:
self._emit("warning", message="当前状态不能重复连接")
return
self._set_state("CONNECTING", f"正在连接 {settings.host}:{settings.port}")
try:
config.validate_config()
model_info = self.model_loader(model_path)
hardware = self.hardware_factory(settings)
self._hardware = hardware
if not bool(hardware.connect()):
raise RuntimeError("无法连接 PLC")
if not bool(
hardware.set_motor_position(
config.MOTOR_OPEN_POSITION,
channel=config.MOTOR_OUTPUT_ADDR,
)
):
raise RuntimeError("连接后设置阀门 100% 开度失败")
self._controller = self.controller_factory(
hardware,
model_info.model,
self.logger,
)
for sensor in self._monitor_failures:
self._monitor_failures[sensor] = 0
self._next_cycle = time.perf_counter()
self.logger.info(
"PLC 已连接 %s:%d,站号=%d;模型=%s",
settings.host,
settings.port,
settings.slave_id,
model_info.display_name,
)
self._set_state("MONITORING", "设备已连接,正在监测;请设置目标流量")
self._emit(
"model_active",
display_name=model_info.display_name,
point_count=model_info.point_count,
is_monotonic=model_info.is_monotonic,
)
except Exception as exc:
errors = self._safe_disconnect()
detail = str(exc)
if errors:
detail += ";安全断开:" + "".join(errors)
self.logger.exception("连接设备失败:%s", detail)
self._state = "FAULT"
self._emit("fault", message=detail, state="FAULT")
def _set_target(self, target):
if self._state not in {"MONITORING", "CONTROLLING"} or self._controller is None:
self._emit("warning", message="设备尚未连接,无法设置目标流量")
return
try:
value = validate_target(target)
self._controller.set_target_flow(value)
if not self._controller.running:
self._controller.start(initial_opening=config.INITIAL_OPENING_PCT)
self._next_cycle = time.perf_counter()
self.logger.info("目标流量设置为 %.3f SLM", value)
self._set_state("CONTROLLING", f"闭环运行中,目标 {value:g} SLM")
self._emit("target_applied", target=value)
except Exception as exc:
self._emit("warning", message=f"目标流量设置失败:{exc}")
def _disconnect(self):
if self._state == "DISCONNECTED":
return
self._set_state("DISCONNECTING", "正在恢复 100% 开度并断开设备…")
errors = self._safe_disconnect()
if errors:
message = "".join(errors)
self.logger.error("设备已断开,但安全收尾存在问题:%s", message)
self._emit("warning", message=message)
self._set_state("DISCONNECTED", "设备已断开(收尾存在警告)")
else:
self.logger.info("设备已安全断开")
self._set_state("DISCONNECTED", "设备已断开")
def _monitor_once(self):
try:
flow = self._read_monitor_value(
"FLOW", "get_flow", config.FLOW_AFTER_ADDR
)
pressure_before = self._read_monitor_value(
"PRESSURE_BEFORE", "get_pressure", config.PRESSURE_BEFORE_ADDR
)
pressure_after = self._read_monitor_value(
"PRESSURE_AFTER", "get_pressure", config.PRESSURE_AFTER_ADDR
)
if flow is not None and not (
config.FLOW_VALID_MIN_SLM <= flow <= config.FLOW_VALID_MAX_SLM
):
raise FlowControlFault(
"FLOW_OUT_OF_RANGE",
"MONITORING",
f"流量读数 {flow:.3f} SLM 超出允许范围",
{"measured_flow_slm": flow},
)
for name, value in (
("阀前压力", pressure_before),
("阀后压力", pressure_after),
):
if (
value is not None
and config.MAX_PRESSURE_KPA is not None
and value > config.MAX_PRESSURE_KPA
):
raise FlowControlFault(
"PRESSURE_OVER_LIMIT",
"MONITORING",
f"{name} {value:.3f} kPa 超过上限 "
f"{config.MAX_PRESSURE_KPA:.3f} kPa",
{"pressure_kpa": value, "sensor": name},
)
status = (
"MONITORING"
if all(
value is not None
for value in (flow, pressure_before, pressure_after)
)
else "SENSOR_READ_FAILED"
)
self._emit(
"telemetry",
measured_flow_slm=flow,
pressure_before_kpa=pressure_before,
pressure_kpa=pressure_after,
target_flow_slm=None,
opening_pct=100.0,
motor_position=config.MOTOR_OPEN_POSITION,
feedforward_pct=None,
correction_pct=None,
pid_mode="--",
status=status,
)
except Exception as exc:
self._handle_runtime_fault(exc)
def _read_monitor_value(self, sensor, method_name, address):
try:
method = getattr(self._hardware, method_name)
value = method(address)
if value is not None:
value = float(value)
if value is None or not math.isfinite(value):
raise ValueError(f"无效读数:{value!r}")
except Exception as exc:
self._monitor_failures[sensor] += 1
count = self._monitor_failures[sensor]
limit = (
config.MAX_CONSECUTIVE_FLOW_FAILURES
if sensor == "FLOW"
else config.MAX_CONSECUTIVE_PRESSURE_FAILURES
)
self.logger.error(
"监测阶段 %s 读取失败 %d/%d%r",
sensor,
count,
limit,
exc,
)
if count >= limit:
raise FlowControlFault(
f"{sensor}_READ_FAILED",
"MONITORING",
f"{sensor} 连续读取失败 {count}",
{"failure_detail": repr(exc)},
) from exc
return None
self._monitor_failures[sensor] = 0
return value
def _control_once(self, now):
try:
result = self._controller.step(now=now)
self._emit(
"telemetry",
**result.to_dict(),
pid_mode=self._controller.pid_mode,
)
except Exception as exc:
self._handle_runtime_fault(exc)
def _handle_runtime_fault(self, exc):
if isinstance(exc, FlowControlFault):
message = str(exc)
else:
message = f"未处理的控制异常:{exc}"
self.logger.exception("控制故障:%s", message)
errors = self._safe_disconnect()
if errors:
message += ";安全断开:" + "".join(errors)
self._state = "FAULT"
self._emit("fault", message=message, state="FAULT")
def _safe_disconnect(self):
errors = []
controller = self._controller
hardware = self._hardware
self._controller = None
self._hardware = None
if controller is not None:
try:
controller.stop()
except Exception as exc:
errors.append(f"停止控制失败:{exc}")
if hardware is not None:
if bool(getattr(hardware, "connected", False)):
try:
success = hardware.set_motor_position(
config.MOTOR_OPEN_POSITION,
channel=config.MOTOR_OUTPUT_ADDR,
)
if not success:
errors.append("断开前设置阀门 100% 开度失败")
except Exception as exc:
errors.append(f"断开前设置阀门 100% 开度异常:{exc}")
try:
hardware.disconnect()
except Exception as exc:
errors.append(f"断开 PLC 失败:{exc}")
return errors
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

+42
View File
@@ -0,0 +1,42 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules("pymodbus")
a = Analysis(
["gui_main.py"],
pathex=[],
binaries=[],
datas=[("valve_model/valve_model_004.json", ".")],
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=["matplotlib", "numpy", "pytest"],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name="FlowControl",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
+568
View File
@@ -0,0 +1,568 @@
"""气体流量控制桌面程序。"""
from __future__ import annotations
import argparse
import ctypes
from datetime import datetime
from queue import Empty
import sys
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import config
from control_runtime import (
BUILTIN_MODEL_NAME,
ControlWorker,
build_runtime_logger,
load_model,
validate_device_settings,
validate_target,
)
WINDOW_TITLE = "气体流量控制"
class FlowControlApp:
"""单窗口操作界面。"""
STATE_TEXT = {
"DISCONNECTED": "未连接",
"CONNECTING": "正在连接",
"MONITORING": "已连接 / 监测中",
"CONTROLLING": "闭环运行中",
"DISCONNECTING": "正在断开",
"STOPPING": "正在退出",
"FAULT": "故障 / 已断开",
}
STATE_COLORS = {
"DISCONNECTED": ("#5f6368", "#eef0f2"),
"CONNECTING": ("#875a00", "#fff3cd"),
"MONITORING": ("#0b6b3a", "#dff4e8"),
"CONTROLLING": ("#075cad", "#deefff"),
"DISCONNECTING": ("#875a00", "#fff3cd"),
"STOPPING": ("#875a00", "#fff3cd"),
"FAULT": ("#a61b1b", "#fde5e5"),
}
def __init__(self, root):
self.root = root
self.root.title(WINDOW_TITLE)
self.root.geometry("860x720")
self.root.minsize(780, 640)
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
self.logger = build_runtime_logger()
self.worker = ControlWorker(self.logger)
self.current_state = "DISCONNECTED"
self.closing = False
self.selected_model_path = None
self.model_info = load_model()
self.host_var = tk.StringVar(value=config.PLC_HOST)
self.port_var = tk.StringVar(value=str(config.PLC_PORT))
self.slave_var = tk.StringVar(value=str(config.PLC_SLAVE_ID))
self.model_var = tk.StringVar(value=self.model_info.display_name)
self.target_var = tk.StringVar(value="")
self.active_target_var = tk.StringVar(value="--")
self.status_var = tk.StringVar(value="未连接")
self.detail_var = tk.StringVar(value="请选择模型并连接设备")
self.value_vars = {
"flow": tk.StringVar(value="--"),
"pressure_before": tk.StringVar(value="--"),
"pressure_after": tk.StringVar(value="--"),
"opening": tk.StringVar(value="--"),
"motor": tk.StringVar(value="--"),
"feedforward": tk.StringVar(value="--"),
"correction": tk.StringVar(value="--"),
"mode": tk.StringVar(value="--"),
}
self._configure_style()
self._build_ui()
self._apply_state("DISCONNECTED", "未连接")
self._append_log(
f"已加载 {self.model_info.display_name};双击程序不会自动连接设备"
)
self.worker.start()
self.root.after(80, self._poll_worker_events)
def _configure_style(self):
self.root.option_add("*Font", ("Microsoft YaHei UI", 10))
style = ttk.Style(self.root)
try:
style.theme_use("vista")
except tk.TclError:
pass
style.configure("Title.TLabel", font=("Microsoft YaHei UI", 20, "bold"))
style.configure("Section.TLabelframe.Label", font=("Microsoft YaHei UI", 11, "bold"))
style.configure("Primary.TButton", font=("Microsoft YaHei UI", 10, "bold"))
style.configure("Danger.TButton", font=("Microsoft YaHei UI", 10, "bold"))
def _build_ui(self):
outer = ttk.Frame(self.root, padding=(18, 14, 18, 14))
outer.pack(fill="both", expand=True)
ttk.Label(outer, text=WINDOW_TITLE, style="Title.TLabel").pack(anchor="w")
ttk.Label(
outer,
text="Easy521 PLC · Modbus TCP · 10 Hz 闭环控制",
foreground="#5f6368",
).pack(anchor="w", pady=(0, 10))
warning = tk.Label(
outer,
text=(
"安全提示:连接成功后及断开设备前,程序会命令阀门至 100% 开度"
f"(行程 {config.MOTOR_OPEN_POSITION:g})。"
),
bg="#fff4e5",
fg="#8a4b00",
padx=10,
pady=7,
anchor="w",
)
warning.pack(fill="x", pady=(0, 10))
connection = ttk.LabelFrame(
outer,
text="设备与模型",
padding=10,
style="Section.TLabelframe",
)
connection.pack(fill="x")
connection.columnconfigure(1, weight=1)
connection.columnconfigure(3, weight=1)
ttk.Label(connection, text="PLC 地址").grid(row=0, column=0, sticky="w")
self.host_entry = ttk.Entry(connection, textvariable=self.host_var, width=20)
self.host_entry.grid(row=0, column=1, sticky="ew", padx=(7, 14))
ttk.Label(connection, text="端口").grid(row=0, column=2, sticky="w")
self.port_entry = ttk.Entry(connection, textvariable=self.port_var, width=9)
self.port_entry.grid(row=0, column=3, sticky="w", padx=(7, 14))
ttk.Label(connection, text="站号").grid(row=0, column=4, sticky="w")
self.slave_entry = ttk.Entry(connection, textvariable=self.slave_var, width=7)
self.slave_entry.grid(row=0, column=5, sticky="w", padx=(7, 0))
ttk.Label(connection, text="A_eff 模型").grid(
row=1, column=0, sticky="w", pady=(10, 0)
)
self.model_label = ttk.Label(
connection,
textvariable=self.model_var,
foreground="#174f85",
)
self.model_label.grid(
row=1, column=1, columnspan=3, sticky="w", padx=(7, 10), pady=(10, 0)
)
self.model_browse_button = ttk.Button(
connection, text="选择 JSON…", command=self._choose_model
)
self.model_browse_button.grid(row=1, column=4, pady=(10, 0), padx=(0, 7))
self.model_builtin_button = ttk.Button(
connection, text="使用内置", command=self._use_builtin_model
)
self.model_builtin_button.grid(row=1, column=5, pady=(10, 0))
button_row = ttk.Frame(connection)
button_row.grid(row=2, column=0, columnspan=6, sticky="ew", pady=(12, 0))
self.connect_button = ttk.Button(
button_row,
text="连接设备",
command=self._connect,
style="Primary.TButton",
width=16,
)
self.connect_button.pack(side="left")
self.disconnect_button = ttk.Button(
button_row,
text="断开设备",
command=self._disconnect,
style="Danger.TButton",
width=16,
)
self.disconnect_button.pack(side="left", padx=(10, 0))
target_frame = ttk.LabelFrame(
outer,
text="目标流量",
padding=10,
style="Section.TLabelframe",
)
target_frame.pack(fill="x", pady=(10, 0))
target_frame.columnconfigure(1, weight=1)
ttk.Label(target_frame, text="目标值").grid(row=0, column=0, sticky="w")
self.target_entry = ttk.Entry(
target_frame,
textvariable=self.target_var,
width=18,
font=("Microsoft YaHei UI", 12),
)
self.target_entry.grid(row=0, column=1, sticky="w", padx=(8, 5))
self.target_entry.bind("<Return>", lambda _event: self._set_target())
ttk.Label(target_frame, text="SLM").grid(row=0, column=2, sticky="w")
self.target_button = ttk.Button(
target_frame,
text="设置目标流量",
command=self._set_target,
style="Primary.TButton",
width=17,
)
self.target_button.grid(row=0, column=3, padx=(18, 0))
ttk.Label(target_frame, text="当前目标:").grid(
row=0, column=4, padx=(24, 0)
)
ttk.Label(
target_frame,
textvariable=self.active_target_var,
font=("Microsoft YaHei UI", 11, "bold"),
foreground="#075cad",
).grid(row=0, column=5, sticky="w")
ttk.Label(
target_frame,
text=(
f"允许范围 {config.TARGET_FLOW_MIN_SLM:g}"
f"{config.TARGET_FLOW_MAX_SLM:g} SLM;首次设置即启动闭环"
),
foreground="#5f6368",
).grid(row=1, column=0, columnspan=6, sticky="w", pady=(7, 0))
telemetry = ttk.LabelFrame(
outer,
text="实时监视",
padding=10,
style="Section.TLabelframe",
)
telemetry.pack(fill="x", pady=(10, 0))
for column in range(4):
telemetry.columnconfigure(column, weight=1, uniform="card")
self._add_value_card(telemetry, 0, 0, "当前流量", "flow", "SLM")
self._add_value_card(telemetry, 0, 1, "阀前压力", "pressure_before", "kPa")
self._add_value_card(telemetry, 0, 2, "阀后压力", "pressure_after", "kPa")
self._add_value_card(
telemetry, 0, 3, "下发总开度", "opening", "%", prominent=True
)
self._add_value_card(telemetry, 1, 0, "电机行程", "motor", "")
self._add_value_card(telemetry, 1, 1, "前馈开度", "feedforward", "%")
self._add_value_card(telemetry, 1, 2, "PI 修正", "correction", "%")
self._add_value_card(telemetry, 1, 3, "PID 模式", "mode", "")
status_frame = ttk.LabelFrame(
outer,
text="运行状态",
padding=10,
style="Section.TLabelframe",
)
status_frame.pack(fill="both", expand=True, pady=(10, 0))
status_header = ttk.Frame(status_frame)
status_header.pack(fill="x")
self.status_badge = tk.Label(
status_header,
textvariable=self.status_var,
padx=10,
pady=4,
font=("Microsoft YaHei UI", 10, "bold"),
)
self.status_badge.pack(side="left")
ttk.Label(status_header, textvariable=self.detail_var).pack(
side="left", padx=(10, 0)
)
self.log_text = tk.Text(
status_frame,
height=3,
wrap="word",
state="disabled",
bg="#fafafa",
relief="solid",
borderwidth=1,
font=("Microsoft YaHei UI", 9),
)
self.log_text.pack(fill="both", expand=True, pady=(8, 0))
def _add_value_card(self, parent, row, column, title, key, unit, prominent=False):
frame = tk.Frame(
parent,
bg="#edf5ff" if prominent else "#f6f7f8",
highlightbackground="#b8c6d5" if prominent else "#d9dde1",
highlightthickness=1,
padx=10,
pady=8,
)
frame.grid(
row=row,
column=column,
sticky="nsew",
padx=(0 if column == 0 else 5, 0),
pady=(0 if row == 0 else 7, 0),
)
tk.Label(
frame,
text=title,
bg=frame["bg"],
fg="#4c545c",
font=("Microsoft YaHei UI", 9),
).pack(anchor="w")
value_row = tk.Frame(frame, bg=frame["bg"])
value_row.pack(anchor="w", pady=(3, 0))
tk.Label(
value_row,
textvariable=self.value_vars[key],
bg=frame["bg"],
fg="#075cad" if prominent else "#202124",
font=("Microsoft YaHei UI", 16 if prominent else 14, "bold"),
).pack(side="left")
if unit:
tk.Label(
value_row,
text=f" {unit}",
bg=frame["bg"],
fg="#5f6368",
font=("Microsoft YaHei UI", 9),
).pack(side="left", anchor="s", pady=(0, 2))
def _connect(self):
try:
settings = validate_device_settings(
self.host_var.get(), self.port_var.get(), self.slave_var.get()
)
# 在主线程先校验一次,避免无效模型进入连接流程。
self.model_info = load_model(self.selected_model_path)
except ValueError as exc:
messagebox.showerror("无法连接", str(exc), parent=self.root)
return
self._apply_state("CONNECTING", f"正在连接 {settings.host}:{settings.port}")
self._append_log(f"请求连接 {settings.host}:{settings.port},站号 {settings.slave_id}")
self.worker.request_connect(settings, self.selected_model_path)
def _disconnect(self):
self._apply_state("DISCONNECTING", "正在安全断开设备…")
self._append_log("请求停止控制并安全断开设备")
self.worker.request_disconnect()
def _set_target(self):
try:
value = validate_target(self.target_var.get())
except ValueError as exc:
messagebox.showerror("目标流量无效", str(exc), parent=self.root)
return
self.detail_var.set(f"正在设置目标流量 {value:g} SLM…")
self.worker.request_target(value)
def _choose_model(self):
path = filedialog.askopenfilename(
parent=self.root,
title="选择 A_eff 阀模型",
filetypes=(("JSON 阀模型", "*.json"), ("所有文件", "*.*")),
)
if not path:
return
try:
info = load_model(path)
except ValueError as exc:
messagebox.showerror("模型无效", str(exc), parent=self.root)
return
self.selected_model_path = str(info.path)
self.model_info = info
self.model_var.set(info.display_name)
monotonic_text = "单调" if info.is_monotonic else "非单调(按关闭端优先反解)"
self._append_log(f"已选择 {info.display_name}{monotonic_text}")
def _use_builtin_model(self):
try:
info = load_model()
except ValueError as exc:
messagebox.showerror("内置模型不可用", str(exc), parent=self.root)
return
self.selected_model_path = None
self.model_info = info
self.model_var.set(info.display_name)
self._append_log(f"已切换为 {info.display_name}")
def _poll_worker_events(self):
try:
while True:
event = self.worker.events.get_nowait()
self._handle_worker_event(event)
except Empty:
pass
if not self.closing:
self.root.after(80, self._poll_worker_events)
elif self.worker.is_alive():
self.root.after(80, self._poll_worker_events)
else:
self.root.destroy()
def _handle_worker_event(self, event):
kind = event["kind"]
if kind == "state":
state = event["state"]
message = event.get("message", "")
self._apply_state(state, message)
if state != "DISCONNECTED" or message != "未连接":
self._append_log(message)
elif kind == "telemetry":
self._update_telemetry(event)
elif kind == "target_applied":
target = event["target"]
self.active_target_var.set(f"{target:g} SLM")
self._append_log(f"目标流量已生效:{target:g} SLM")
elif kind == "model_active":
self._append_log(f"当前控制模型:{event['display_name']}")
elif kind == "warning":
message = event.get("message", "未知警告")
self.detail_var.set(message)
self._append_log("警告:" + message)
elif kind == "fault":
message = event.get("message", "未知故障")
self._apply_state("FAULT", message)
self._append_log("故障:" + message)
if not self.closing:
messagebox.showerror("设备或控制故障", message, parent=self.root)
elif kind == "shutdown_complete" and self.closing:
errors = event.get("errors") or []
if errors:
self._append_log("退出警告:" + "".join(errors))
def _update_telemetry(self, event):
self.value_vars["flow"].set(self._format_number(event.get("measured_flow_slm")))
self.value_vars["pressure_before"].set(
self._format_number(event.get("pressure_before_kpa"))
)
self.value_vars["pressure_after"].set(
self._format_number(event.get("pressure_kpa"))
)
self.value_vars["opening"].set(
self._format_number(event.get("opening_pct"), digits=2)
)
self.value_vars["motor"].set(
self._format_number(event.get("motor_position"), digits=1)
)
self.value_vars["feedforward"].set(
self._format_number(event.get("feedforward_pct"), digits=2)
)
correction = event.get("correction_pct")
self.value_vars["correction"].set(
"--" if correction is None else f"{float(correction):+.2f}"
)
self.value_vars["mode"].set(str(event.get("pid_mode") or "--"))
status = event.get("status", "")
if status and status not in {"OK", "MONITORING"}:
self.detail_var.set(f"采样状态:{status}")
@staticmethod
def _format_number(value, digits=2):
if value is None:
return "--"
return f"{float(value):.{digits}f}"
def _apply_state(self, state, detail):
self.current_state = state
self.status_var.set(self.STATE_TEXT.get(state, state))
self.detail_var.set(detail)
foreground, background = self.STATE_COLORS.get(
state, ("#202124", "#eef0f2")
)
if hasattr(self, "status_badge"):
self.status_badge.configure(fg=foreground, bg=background)
editable = state in {"DISCONNECTED", "FAULT"}
connected = state in {"MONITORING", "CONTROLLING"}
self._set_state(self.host_entry, "normal" if editable else "disabled")
self._set_state(self.port_entry, "normal" if editable else "disabled")
self._set_state(self.slave_entry, "normal" if editable else "disabled")
self._set_state(
self.model_browse_button, "normal" if editable else "disabled"
)
self._set_state(
self.model_builtin_button, "normal" if editable else "disabled"
)
self._set_state(self.connect_button, "normal" if editable else "disabled")
self._set_state(
self.disconnect_button, "normal" if connected else "disabled"
)
self._set_state(self.target_entry, "normal" if connected else "disabled")
self._set_state(self.target_button, "normal" if connected else "disabled")
if state in {"DISCONNECTED", "FAULT"}:
self.active_target_var.set("--")
@staticmethod
def _set_state(widget, state):
widget.configure(state=state)
def _append_log(self, message):
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.configure(state="normal")
self.log_text.insert("end", f"[{timestamp}] {message}\n")
self.log_text.see("end-1c")
self.log_text.configure(state="disabled")
def _on_close(self):
if self.closing:
return
self.closing = True
self._apply_state("STOPPING", "正在安全退出,请稍候…")
self._append_log("正在停止控制、恢复 100% 开度并关闭连接")
self.worker.request_shutdown()
def enable_dpi_awareness():
"""让 Windows 高 DPI 显示更清晰;失败时安全忽略。"""
if sys.platform != "win32":
return
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
except (AttributeError, OSError):
try:
ctypes.windll.user32.SetProcessDPIAware()
except (AttributeError, OSError):
pass
def smoke_test():
"""供打包后自动检查资源和关键依赖,不连接设备。"""
config.validate_config()
info = load_model()
if info.point_count < 2 or info.path.name != BUILTIN_MODEL_NAME:
raise RuntimeError("内置阀模型校验失败")
from PcControl import Easy521ModbusClient # noqa: F401
return 0
def parse_args(argv=None):
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument(
"--smoke-test",
action="store_true",
help="仅检查内置资源和依赖,不打开界面、不连接设备",
)
return parser.parse_args(argv)
def main(argv=None):
args = parse_args(argv)
if args.smoke_test:
return smoke_test()
enable_dpi_awareness()
root = tk.Tk()
try:
FlowControlApp(root)
except Exception as exc:
root.withdraw()
messagebox.showerror("程序启动失败", str(exc), parent=root)
root.destroy()
return 1
root.mainloop()
return 0
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
import logging
from queue import Empty
import time
import config
from control_runtime import (
ControlWorker,
load_model,
validate_device_settings,
validate_target,
)
class FakeHardware:
def __init__(self, *, connect_ok=True):
self.connect_ok = connect_ok
self.connected = False
self.disconnect_count = 0
self.motor_commands = []
def connect(self):
self.connected = self.connect_ok
return self.connect_ok
def disconnect(self):
self.connected = False
self.disconnect_count += 1
def get_flow(self, _address):
return 20.0
def get_pressure(self, address):
if address == config.PRESSURE_BEFORE_ADDR:
return 200.0
return 100.0
def set_motor_position(self, position, channel=None):
self.motor_commands.append((float(position), channel))
return True
class OverpressureHardware(FakeHardware):
def get_pressure(self, address):
if address == config.PRESSURE_BEFORE_ADDR:
return config.MAX_PRESSURE_KPA + 1.0
return 100.0
def wait_for_event(worker, predicate, timeout=2.0):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
try:
event = worker.events.get(timeout=0.1)
except Empty:
continue
if predicate(event):
return event
raise AssertionError("等待后台线程事件超时")
def make_logger():
logger = logging.getLogger(f"test-control-runtime-{time.monotonic_ns()}")
logger.addHandler(logging.NullHandler())
return logger
def test_validate_ui_values():
settings = validate_device_settings(" 192.168.1.88 ", "502", "1")
assert settings.host == "192.168.1.88"
assert settings.port == 502
assert settings.slave_id == 1
assert validate_target("50.5") == 50.5
def test_builtin_model_is_valve_model_004():
info = load_model()
assert info.is_builtin is True
assert info.path.name == "valve_model_004.json"
assert info.point_count == 80
assert info.model.motor_open == config.MOTOR_OPEN_POSITION
assert info.model.motor_closed == config.MOTOR_CLOSED_POSITION
def test_worker_connect_control_and_safe_disconnect():
hardware = FakeHardware()
worker = ControlWorker(make_logger(), hardware_factory=lambda _settings: hardware)
worker.start()
wait_for_event(
worker,
lambda event: event.get("kind") == "state"
and event.get("state") == "DISCONNECTED",
)
settings = validate_device_settings("192.168.1.88", 502, 1)
worker.request_connect(settings)
wait_for_event(
worker,
lambda event: event.get("kind") == "state"
and event.get("state") == "MONITORING",
)
assert hardware.connected is True
assert hardware.motor_commands[0] == (
config.MOTOR_OPEN_POSITION,
config.MOTOR_OUTPUT_ADDR,
)
worker.request_target(50.0)
wait_for_event(
worker,
lambda event: event.get("kind") == "target_applied",
)
telemetry = wait_for_event(
worker,
lambda event: event.get("kind") == "telemetry"
and event.get("target_flow_slm") == 50.0,
)
assert telemetry["measured_flow_slm"] == 20.0
assert 0.0 <= telemetry["opening_pct"] <= 100.0
assert telemetry["pid_mode"] in {"FAR", "NEAR"}
worker.request_disconnect()
wait_for_event(
worker,
lambda event: event.get("kind") == "state"
and event.get("state") == "DISCONNECTED",
)
assert hardware.connected is False
assert hardware.motor_commands[-1] == (
config.MOTOR_OPEN_POSITION,
config.MOTOR_OUTPUT_ADDR,
)
assert hardware.disconnect_count == 1
worker.request_shutdown()
wait_for_event(worker, lambda event: event.get("kind") == "shutdown_complete")
worker.thread.join(timeout=1.0)
assert not worker.is_alive()
def test_worker_connection_failure_reports_fault_without_hardware_access():
hardware = FakeHardware(connect_ok=False)
worker = ControlWorker(make_logger(), hardware_factory=lambda _settings: hardware)
worker.start()
wait_for_event(worker, lambda event: event.get("state") == "DISCONNECTED")
worker.request_connect(validate_device_settings("192.168.1.88", 502, 1))
fault = wait_for_event(worker, lambda event: event.get("kind") == "fault")
assert "无法连接 PLC" in fault["message"]
assert hardware.motor_commands == []
assert hardware.disconnect_count == 1
worker.request_shutdown()
wait_for_event(worker, lambda event: event.get("kind") == "shutdown_complete")
worker.thread.join(timeout=1.0)
assert not worker.is_alive()
def test_monitoring_stage_overpressure_triggers_safe_disconnect():
hardware = OverpressureHardware()
worker = ControlWorker(make_logger(), hardware_factory=lambda _settings: hardware)
worker.start()
wait_for_event(worker, lambda event: event.get("state") == "DISCONNECTED")
worker.request_connect(validate_device_settings("192.168.1.88", 502, 1))
wait_for_event(worker, lambda event: event.get("state") == "MONITORING")
fault = wait_for_event(worker, lambda event: event.get("kind") == "fault")
assert "超过上限" in fault["message"]
assert hardware.connected is False
assert hardware.motor_commands[-1][0] == config.MOTOR_OPEN_POSITION
worker.request_shutdown()
wait_for_event(worker, lambda event: event.get("kind") == "shutdown_complete")
worker.thread.join(timeout=1.0)
assert not worker.is_alive()
@@ -0,0 +1,25 @@
time_s,timestamp,flow_after_slm,opening_pct,pressure_before_kpa,pressure_after_kpa,motor_position
0.000000,2026-09-03T14:33:33.002,180.825000,100.000000,201.375000,121.100000,800.000000
1.009349,2026-09-03T14:33:34.012,180.834375,100.000000,201.581250,121.450000,800.000000
2.019912,2026-09-03T14:33:35.022,180.459375,100.000000,201.909375,121.237500,800.000000
3.029416,2026-09-03T14:33:36.031,181.378125,100.000000,202.031250,121.375000,800.000000
4.038972,2026-09-03T14:33:37.041,174.637500,0.000000,212.353125,39.475000,1000.000000
5.048765,2026-09-03T14:33:38.051,34.912500,0.000000,306.000000,-0.237500,1000.000000
6.059989,2026-09-03T14:33:39.062,8.325000,0.000000,306.000000,-0.275000,1000.000000
7.069332,2026-09-03T14:33:40.071,3.675000,0.000000,306.000000,-0.300000,1000.000000
8.078579,2026-09-03T14:33:41.081,0.900000,0.000000,306.000000,-0.312500,1000.000000
9.089545,2026-09-03T14:33:42.092,0.881250,0.000000,306.000000,-0.312500,1000.000000
10.100123,2026-09-03T14:33:43.102,0.900000,0.000000,306.000000,-0.312500,1000.000000
11.110366,2026-09-03T14:33:44.113,0.881250,0.000000,306.000000,-0.337500,1000.000000
12.122532,2026-09-03T14:33:45.125,0.881250,0.000000,306.000000,-0.337500,1000.000000
13.134354,2026-09-03T14:33:46.136,0.900000,0.000000,306.000000,-0.312500,1000.000000
14.144802,2026-09-03T14:33:47.147,0.900000,0.000000,306.000000,-0.312500,1000.000000
15.154118,2026-09-03T14:33:48.156,0.881250,0.000000,306.000000,-0.337500,1000.000000
16.164254,2026-09-03T14:33:49.166,0.862500,0.000000,306.000000,-0.312500,1000.000000
17.174010,2026-09-03T14:33:50.176,0.881250,0.000000,306.000000,-0.312500,1000.000000
18.182420,2026-09-03T14:33:51.184,0.862500,0.000000,306.000000,-0.337500,1000.000000
19.191267,2026-09-03T14:33:52.193,0.881250,0.000000,306.000000,-0.312500,1000.000000
20.201921,2026-09-03T14:33:53.204,0.881250,0.000000,306.000000,-0.312500,1000.000000
21.211323,2026-09-03T14:33:54.214,0.862500,0.000000,306.000000,-0.312500,1000.000000
22.222314,2026-09-03T14:33:55.224,0.862500,0.000000,306.000000,-0.337500,1000.000000
23.232360,2026-09-03T14:33:56.235,0.862500,0.000000,306.000000,-0.350000,1000.000000
1 time_s timestamp flow_after_slm opening_pct pressure_before_kpa pressure_after_kpa motor_position
2 0.000000 2026-09-03T14:33:33.002 180.825000 100.000000 201.375000 121.100000 800.000000
3 1.009349 2026-09-03T14:33:34.012 180.834375 100.000000 201.581250 121.450000 800.000000
4 2.019912 2026-09-03T14:33:35.022 180.459375 100.000000 201.909375 121.237500 800.000000
5 3.029416 2026-09-03T14:33:36.031 181.378125 100.000000 202.031250 121.375000 800.000000
6 4.038972 2026-09-03T14:33:37.041 174.637500 0.000000 212.353125 39.475000 1000.000000
7 5.048765 2026-09-03T14:33:38.051 34.912500 0.000000 306.000000 -0.237500 1000.000000
8 6.059989 2026-09-03T14:33:39.062 8.325000 0.000000 306.000000 -0.275000 1000.000000
9 7.069332 2026-09-03T14:33:40.071 3.675000 0.000000 306.000000 -0.300000 1000.000000
10 8.078579 2026-09-03T14:33:41.081 0.900000 0.000000 306.000000 -0.312500 1000.000000
11 9.089545 2026-09-03T14:33:42.092 0.881250 0.000000 306.000000 -0.312500 1000.000000
12 10.100123 2026-09-03T14:33:43.102 0.900000 0.000000 306.000000 -0.312500 1000.000000
13 11.110366 2026-09-03T14:33:44.113 0.881250 0.000000 306.000000 -0.337500 1000.000000
14 12.122532 2026-09-03T14:33:45.125 0.881250 0.000000 306.000000 -0.337500 1000.000000
15 13.134354 2026-09-03T14:33:46.136 0.900000 0.000000 306.000000 -0.312500 1000.000000
16 14.144802 2026-09-03T14:33:47.147 0.900000 0.000000 306.000000 -0.312500 1000.000000
17 15.154118 2026-09-03T14:33:48.156 0.881250 0.000000 306.000000 -0.337500 1000.000000
18 16.164254 2026-09-03T14:33:49.166 0.862500 0.000000 306.000000 -0.312500 1000.000000
19 17.174010 2026-09-03T14:33:50.176 0.881250 0.000000 306.000000 -0.312500 1000.000000
20 18.182420 2026-09-03T14:33:51.184 0.862500 0.000000 306.000000 -0.337500 1000.000000
21 19.191267 2026-09-03T14:33:52.193 0.881250 0.000000 306.000000 -0.312500 1000.000000
22 20.201921 2026-09-03T14:33:53.204 0.881250 0.000000 306.000000 -0.312500 1000.000000
23 21.211323 2026-09-03T14:33:54.214 0.862500 0.000000 306.000000 -0.312500 1000.000000
24 22.222314 2026-09-03T14:33:55.224 0.862500 0.000000 306.000000 -0.337500 1000.000000
25 23.232360 2026-09-03T14:33:56.235 0.862500 0.000000 306.000000 -0.350000 1000.000000
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

@@ -0,0 +1,27 @@
time_s,timestamp,flow_after_slm,opening_pct,pressure_before_kpa,pressure_after_kpa,motor_position
0.000000,2026-09-03T14:36:53.577,186.300000,100.000000,206.175000,124.550000,800.000000
1.009534,2026-09-03T14:36:54.587,186.206250,100.000000,206.559375,124.400000,800.000000
2.018970,2026-09-03T14:36:55.597,184.837500,100.000000,207.750000,124.375000,800.000000
3.029327,2026-09-03T14:36:56.607,185.653125,100.000000,206.878125,124.550000,800.000000
4.039865,2026-09-03T14:36:57.618,186.187500,100.000000,206.850000,124.875000,800.000000
5.049509,2026-09-03T14:36:58.627,185.925000,100.000000,206.812500,124.775000,800.000000
6.059955,2026-09-03T14:36:59.638,185.559375,100.000000,206.831250,124.937500,800.000000
7.068847,2026-09-03T14:37:00.647,185.756250,100.000000,206.653125,124.812500,800.000000
8.079927,2026-09-03T14:37:01.658,186.459375,100.000000,206.709375,124.812500,800.000000
9.090449,2026-09-03T14:37:02.668,76.059375,0.000000,306.000000,-0.175000,1000.000000
10.099598,2026-09-03T14:37:03.677,17.353125,0.000000,306.000000,-0.250000,1000.000000
11.109955,2026-09-03T14:37:04.688,6.412500,0.000000,306.000000,-0.275000,1000.000000
12.120598,2026-09-03T14:37:05.698,3.131250,0.000000,306.000000,-0.312500,1000.000000
13.130787,2026-09-03T14:37:06.709,0.909375,0.000000,306.000000,-0.312500,1000.000000
14.141024,2026-09-03T14:37:07.719,0.909375,0.000000,306.000000,-0.337500,1000.000000
15.151936,2026-09-03T14:37:08.730,0.909375,0.000000,306.000000,-0.312500,1000.000000
16.161696,2026-09-03T14:37:09.739,0.881250,0.000000,306.000000,-0.337500,1000.000000
17.170813,2026-09-03T14:37:10.749,0.900000,0.000000,306.000000,-0.337500,1000.000000
18.181591,2026-09-03T14:37:11.759,0.900000,0.000000,306.000000,-0.337500,1000.000000
19.192289,2026-09-03T14:37:12.770,0.900000,0.000000,306.000000,-0.337500,1000.000000
20.201665,2026-09-03T14:37:13.779,0.900000,0.000000,306.000000,-0.337500,1000.000000
21.210939,2026-09-03T14:37:14.789,0.881250,0.000000,306.000000,-0.350000,1000.000000
22.221416,2026-09-03T14:37:15.799,0.900000,0.000000,306.000000,-0.337500,1000.000000
23.231089,2026-09-03T14:37:16.809,0.881250,0.000000,306.000000,-0.312500,1000.000000
24.242616,2026-09-03T14:37:17.820,0.881250,0.000000,306.000000,-0.312500,1000.000000
25.251933,2026-09-03T14:37:18.830,0.881250,0.000000,306.000000,-0.337500,1000.000000
1 time_s timestamp flow_after_slm opening_pct pressure_before_kpa pressure_after_kpa motor_position
2 0.000000 2026-09-03T14:36:53.577 186.300000 100.000000 206.175000 124.550000 800.000000
3 1.009534 2026-09-03T14:36:54.587 186.206250 100.000000 206.559375 124.400000 800.000000
4 2.018970 2026-09-03T14:36:55.597 184.837500 100.000000 207.750000 124.375000 800.000000
5 3.029327 2026-09-03T14:36:56.607 185.653125 100.000000 206.878125 124.550000 800.000000
6 4.039865 2026-09-03T14:36:57.618 186.187500 100.000000 206.850000 124.875000 800.000000
7 5.049509 2026-09-03T14:36:58.627 185.925000 100.000000 206.812500 124.775000 800.000000
8 6.059955 2026-09-03T14:36:59.638 185.559375 100.000000 206.831250 124.937500 800.000000
9 7.068847 2026-09-03T14:37:00.647 185.756250 100.000000 206.653125 124.812500 800.000000
10 8.079927 2026-09-03T14:37:01.658 186.459375 100.000000 206.709375 124.812500 800.000000
11 9.090449 2026-09-03T14:37:02.668 76.059375 0.000000 306.000000 -0.175000 1000.000000
12 10.099598 2026-09-03T14:37:03.677 17.353125 0.000000 306.000000 -0.250000 1000.000000
13 11.109955 2026-09-03T14:37:04.688 6.412500 0.000000 306.000000 -0.275000 1000.000000
14 12.120598 2026-09-03T14:37:05.698 3.131250 0.000000 306.000000 -0.312500 1000.000000
15 13.130787 2026-09-03T14:37:06.709 0.909375 0.000000 306.000000 -0.312500 1000.000000
16 14.141024 2026-09-03T14:37:07.719 0.909375 0.000000 306.000000 -0.337500 1000.000000
17 15.151936 2026-09-03T14:37:08.730 0.909375 0.000000 306.000000 -0.312500 1000.000000
18 16.161696 2026-09-03T14:37:09.739 0.881250 0.000000 306.000000 -0.337500 1000.000000
19 17.170813 2026-09-03T14:37:10.749 0.900000 0.000000 306.000000 -0.337500 1000.000000
20 18.181591 2026-09-03T14:37:11.759 0.900000 0.000000 306.000000 -0.337500 1000.000000
21 19.192289 2026-09-03T14:37:12.770 0.900000 0.000000 306.000000 -0.337500 1000.000000
22 20.201665 2026-09-03T14:37:13.779 0.900000 0.000000 306.000000 -0.337500 1000.000000
23 21.210939 2026-09-03T14:37:14.789 0.881250 0.000000 306.000000 -0.350000 1000.000000
24 22.221416 2026-09-03T14:37:15.799 0.900000 0.000000 306.000000 -0.337500 1000.000000
25 23.231089 2026-09-03T14:37:16.809 0.881250 0.000000 306.000000 -0.312500 1000.000000
26 24.242616 2026-09-03T14:37:17.820 0.881250 0.000000 306.000000 -0.312500 1000.000000
27 25.251933 2026-09-03T14:37:18.830 0.881250 0.000000 306.000000 -0.337500 1000.000000
Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

@@ -0,0 +1,76 @@
time_s,timestamp,flow_after_slm,opening_pct,pressure_before_kpa,pressure_after_kpa,motor_position
0.000000,2026-09-03T14:37:29.223,220.031250,100.000000,254.634375,155.712500,800.000000
1.008507,2026-09-03T14:37:30.231,222.900000,100.000000,254.259375,156.812500,800.000000
2.017654,2026-09-03T14:37:31.241,223.359375,100.000000,254.587500,155.775000,800.000000
3.029588,2026-09-03T14:37:32.253,203.062500,0.000000,279.431250,33.137500,1000.000000
4.037959,2026-09-03T14:37:33.261,36.056250,0.000000,306.000000,-0.175000,1000.000000
5.048918,2026-09-03T14:37:34.272,10.575000,0.000000,306.000000,-0.212500,1000.000000
6.059525,2026-09-03T14:37:35.283,4.659375,0.000000,306.000000,-0.212500,1000.000000
7.070708,2026-09-03T14:37:36.294,0.900000,0.000000,306.000000,-0.250000,1000.000000
8.081269,2026-09-03T14:37:37.304,0.881250,0.000000,306.000000,-0.275000,1000.000000
9.092312,2026-09-03T14:37:38.316,0.900000,0.000000,306.000000,-0.312500,1000.000000
10.104664,2026-09-03T14:37:39.328,0.900000,0.000000,306.000000,-0.312500,1000.000000
11.114669,2026-09-03T14:37:40.339,0.900000,0.000000,306.000000,-0.350000,1000.000000
12.125869,2026-09-03T14:37:41.349,0.900000,0.000000,306.000000,-0.337500,1000.000000
13.136267,2026-09-03T14:37:42.360,0.881250,0.000000,293.025000,-0.350000,1000.000000
14.146329,2026-09-03T14:37:43.369,0.881250,0.000000,276.253125,-0.375000,1000.000000
15.157293,2026-09-03T14:37:44.380,0.881250,0.000000,258.778125,-0.350000,1000.000000
16.167387,2026-09-03T14:37:45.390,0.881250,0.000000,242.231250,-0.375000,1000.000000
17.177553,2026-09-03T14:37:46.401,0.900000,0.000000,226.481250,-0.375000,1000.000000
18.189422,2026-09-03T14:37:47.412,0.881250,0.000000,211.875000,-0.400000,1000.000000
19.200252,2026-09-03T14:37:48.423,0.881250,0.000000,198.150000,-0.350000,1000.000000
20.209384,2026-09-03T14:37:49.432,0.881250,0.000000,186.337500,-0.400000,1000.000000
21.219451,2026-09-03T14:37:50.443,0.881250,0.000000,175.237500,-0.400000,1000.000000
22.228531,2026-09-03T14:37:51.451,0.862500,0.000000,163.237500,-0.400000,1000.000000
23.238794,2026-09-03T14:37:52.462,0.862500,0.000000,151.903125,-0.400000,1000.000000
24.249379,2026-09-03T14:37:53.472,0.862500,0.000000,171.750000,-0.350000,1000.000000
25.259381,2026-09-03T14:37:54.482,0.862500,0.000000,215.634375,-0.350000,1000.000000
26.269218,2026-09-03T14:37:55.492,0.862500,0.000000,242.606250,-0.350000,1000.000000
27.279854,2026-09-03T14:37:56.503,0.862500,0.000000,265.800000,-0.350000,1000.000000
28.290244,2026-09-03T14:37:57.513,0.853125,0.000000,273.759375,-0.375000,1000.000000
29.301363,2026-09-03T14:37:58.525,0.862500,0.000000,275.081250,-0.375000,1000.000000
30.311467,2026-09-03T14:37:59.535,0.853125,0.000000,275.325000,-0.375000,1000.000000
31.322894,2026-09-03T14:38:00.546,0.862500,0.000000,275.437500,-0.375000,1000.000000
32.331937,2026-09-03T14:38:01.555,0.853125,0.000000,276.712500,-0.350000,1000.000000
33.343316,2026-09-03T14:38:02.566,0.853125,0.000000,280.950000,-0.350000,1000.000000
34.352015,2026-09-03T14:38:03.575,0.853125,0.000000,283.903125,-0.375000,1000.000000
35.362602,2026-09-03T14:38:04.586,0.853125,0.000000,284.100000,-0.337500,1000.000000
36.370819,2026-09-03T14:38:05.594,0.853125,0.000000,284.353125,-0.350000,1000.000000
37.380717,2026-09-03T14:38:06.604,0.862500,0.000000,285.450000,-0.350000,1000.000000
38.391094,2026-09-03T14:38:07.614,0.853125,0.000000,285.862500,-0.350000,1000.000000
39.402279,2026-09-03T14:38:08.626,0.853125,0.000000,286.903125,-0.337500,1000.000000
40.413082,2026-09-03T14:38:09.636,0.862500,0.000000,287.137500,-0.350000,1000.000000
41.423721,2026-09-03T14:38:10.647,0.862500,0.000000,288.609375,-0.337500,1000.000000
42.433300,2026-09-03T14:38:11.656,0.862500,0.000000,289.481250,-0.375000,1000.000000
43.443357,2026-09-03T14:38:12.666,0.853125,0.000000,289.331250,-0.350000,1000.000000
44.453689,2026-09-03T14:38:13.677,0.853125,0.000000,288.750000,-0.350000,1000.000000
45.463437,2026-09-03T14:38:14.686,0.853125,0.000000,288.834375,-0.375000,1000.000000
46.473149,2026-09-03T14:38:15.696,0.834375,0.000000,288.909375,-0.350000,1000.000000
47.484513,2026-09-03T14:38:16.708,0.853125,0.000000,289.228125,-0.350000,1000.000000
48.494721,2026-09-03T14:38:17.718,0.853125,0.000000,289.884375,-0.337500,1000.000000
49.505305,2026-09-03T14:38:18.728,0.853125,0.000000,290.550000,-0.375000,1000.000000
50.514115,2026-09-03T14:38:19.737,0.862500,0.000000,291.056250,-0.350000,1000.000000
51.524633,2026-09-03T14:38:20.748,0.862500,0.000000,291.187500,-0.350000,1000.000000
52.534418,2026-09-03T14:38:21.758,0.853125,0.000000,291.206250,-0.337500,1000.000000
53.545409,2026-09-03T14:38:22.769,0.853125,0.000000,290.812500,-0.337500,1000.000000
54.555086,2026-09-03T14:38:23.778,0.834375,0.000000,290.625000,-0.375000,1000.000000
55.565609,2026-09-03T14:38:24.789,0.853125,0.000000,290.175000,-0.375000,1000.000000
56.576575,2026-09-03T14:38:25.800,0.834375,0.000000,289.884375,-0.350000,1000.000000
57.586980,2026-09-03T14:38:26.810,0.834375,0.000000,289.509375,-0.350000,1000.000000
58.596223,2026-09-03T14:38:27.819,0.834375,0.000000,289.725000,-0.375000,1000.000000
59.606634,2026-09-03T14:38:28.830,0.853125,0.000000,290.278125,-0.375000,1000.000000
60.616196,2026-09-03T14:38:29.839,0.853125,0.000000,290.259375,-0.375000,1000.000000
61.625949,2026-09-03T14:38:30.849,0.853125,0.000000,290.212500,-0.375000,1000.000000
62.635355,2026-09-03T14:38:31.859,0.853125,0.000000,289.931250,-0.375000,1000.000000
63.646221,2026-09-03T14:38:32.869,0.853125,0.000000,289.556250,-0.375000,1000.000000
64.655401,2026-09-03T14:38:33.878,0.853125,0.000000,288.984375,-0.375000,1000.000000
65.665560,2026-09-03T14:38:34.888,0.853125,0.000000,288.412500,-0.350000,1000.000000
66.675832,2026-09-03T14:38:35.899,0.834375,0.000000,287.981250,-0.350000,1000.000000
67.685689,2026-09-03T14:38:36.909,0.834375,0.000000,287.634375,-0.337500,1000.000000
68.695668,2026-09-03T14:38:37.919,0.834375,0.000000,287.325000,-0.350000,1000.000000
69.705494,2026-09-03T14:38:38.928,0.834375,0.000000,286.959375,-0.350000,1000.000000
70.716592,2026-09-03T14:38:39.940,0.853125,0.000000,286.884375,-0.375000,1000.000000
71.726608,2026-09-03T14:38:40.950,0.834375,0.000000,286.612500,-0.337500,1000.000000
72.737259,2026-09-03T14:38:41.960,0.853125,0.000000,286.228125,-0.337500,1000.000000
73.747217,2026-09-03T14:38:42.970,0.834375,0.000000,286.125000,-0.337500,1000.000000
74.757698,2026-09-03T14:38:43.981,0.834375,0.000000,285.834375,-0.337500,1000.000000
1 time_s timestamp flow_after_slm opening_pct pressure_before_kpa pressure_after_kpa motor_position
2 0.000000 2026-09-03T14:37:29.223 220.031250 100.000000 254.634375 155.712500 800.000000
3 1.008507 2026-09-03T14:37:30.231 222.900000 100.000000 254.259375 156.812500 800.000000
4 2.017654 2026-09-03T14:37:31.241 223.359375 100.000000 254.587500 155.775000 800.000000
5 3.029588 2026-09-03T14:37:32.253 203.062500 0.000000 279.431250 33.137500 1000.000000
6 4.037959 2026-09-03T14:37:33.261 36.056250 0.000000 306.000000 -0.175000 1000.000000
7 5.048918 2026-09-03T14:37:34.272 10.575000 0.000000 306.000000 -0.212500 1000.000000
8 6.059525 2026-09-03T14:37:35.283 4.659375 0.000000 306.000000 -0.212500 1000.000000
9 7.070708 2026-09-03T14:37:36.294 0.900000 0.000000 306.000000 -0.250000 1000.000000
10 8.081269 2026-09-03T14:37:37.304 0.881250 0.000000 306.000000 -0.275000 1000.000000
11 9.092312 2026-09-03T14:37:38.316 0.900000 0.000000 306.000000 -0.312500 1000.000000
12 10.104664 2026-09-03T14:37:39.328 0.900000 0.000000 306.000000 -0.312500 1000.000000
13 11.114669 2026-09-03T14:37:40.339 0.900000 0.000000 306.000000 -0.350000 1000.000000
14 12.125869 2026-09-03T14:37:41.349 0.900000 0.000000 306.000000 -0.337500 1000.000000
15 13.136267 2026-09-03T14:37:42.360 0.881250 0.000000 293.025000 -0.350000 1000.000000
16 14.146329 2026-09-03T14:37:43.369 0.881250 0.000000 276.253125 -0.375000 1000.000000
17 15.157293 2026-09-03T14:37:44.380 0.881250 0.000000 258.778125 -0.350000 1000.000000
18 16.167387 2026-09-03T14:37:45.390 0.881250 0.000000 242.231250 -0.375000 1000.000000
19 17.177553 2026-09-03T14:37:46.401 0.900000 0.000000 226.481250 -0.375000 1000.000000
20 18.189422 2026-09-03T14:37:47.412 0.881250 0.000000 211.875000 -0.400000 1000.000000
21 19.200252 2026-09-03T14:37:48.423 0.881250 0.000000 198.150000 -0.350000 1000.000000
22 20.209384 2026-09-03T14:37:49.432 0.881250 0.000000 186.337500 -0.400000 1000.000000
23 21.219451 2026-09-03T14:37:50.443 0.881250 0.000000 175.237500 -0.400000 1000.000000
24 22.228531 2026-09-03T14:37:51.451 0.862500 0.000000 163.237500 -0.400000 1000.000000
25 23.238794 2026-09-03T14:37:52.462 0.862500 0.000000 151.903125 -0.400000 1000.000000
26 24.249379 2026-09-03T14:37:53.472 0.862500 0.000000 171.750000 -0.350000 1000.000000
27 25.259381 2026-09-03T14:37:54.482 0.862500 0.000000 215.634375 -0.350000 1000.000000
28 26.269218 2026-09-03T14:37:55.492 0.862500 0.000000 242.606250 -0.350000 1000.000000
29 27.279854 2026-09-03T14:37:56.503 0.862500 0.000000 265.800000 -0.350000 1000.000000
30 28.290244 2026-09-03T14:37:57.513 0.853125 0.000000 273.759375 -0.375000 1000.000000
31 29.301363 2026-09-03T14:37:58.525 0.862500 0.000000 275.081250 -0.375000 1000.000000
32 30.311467 2026-09-03T14:37:59.535 0.853125 0.000000 275.325000 -0.375000 1000.000000
33 31.322894 2026-09-03T14:38:00.546 0.862500 0.000000 275.437500 -0.375000 1000.000000
34 32.331937 2026-09-03T14:38:01.555 0.853125 0.000000 276.712500 -0.350000 1000.000000
35 33.343316 2026-09-03T14:38:02.566 0.853125 0.000000 280.950000 -0.350000 1000.000000
36 34.352015 2026-09-03T14:38:03.575 0.853125 0.000000 283.903125 -0.375000 1000.000000
37 35.362602 2026-09-03T14:38:04.586 0.853125 0.000000 284.100000 -0.337500 1000.000000
38 36.370819 2026-09-03T14:38:05.594 0.853125 0.000000 284.353125 -0.350000 1000.000000
39 37.380717 2026-09-03T14:38:06.604 0.862500 0.000000 285.450000 -0.350000 1000.000000
40 38.391094 2026-09-03T14:38:07.614 0.853125 0.000000 285.862500 -0.350000 1000.000000
41 39.402279 2026-09-03T14:38:08.626 0.853125 0.000000 286.903125 -0.337500 1000.000000
42 40.413082 2026-09-03T14:38:09.636 0.862500 0.000000 287.137500 -0.350000 1000.000000
43 41.423721 2026-09-03T14:38:10.647 0.862500 0.000000 288.609375 -0.337500 1000.000000
44 42.433300 2026-09-03T14:38:11.656 0.862500 0.000000 289.481250 -0.375000 1000.000000
45 43.443357 2026-09-03T14:38:12.666 0.853125 0.000000 289.331250 -0.350000 1000.000000
46 44.453689 2026-09-03T14:38:13.677 0.853125 0.000000 288.750000 -0.350000 1000.000000
47 45.463437 2026-09-03T14:38:14.686 0.853125 0.000000 288.834375 -0.375000 1000.000000
48 46.473149 2026-09-03T14:38:15.696 0.834375 0.000000 288.909375 -0.350000 1000.000000
49 47.484513 2026-09-03T14:38:16.708 0.853125 0.000000 289.228125 -0.350000 1000.000000
50 48.494721 2026-09-03T14:38:17.718 0.853125 0.000000 289.884375 -0.337500 1000.000000
51 49.505305 2026-09-03T14:38:18.728 0.853125 0.000000 290.550000 -0.375000 1000.000000
52 50.514115 2026-09-03T14:38:19.737 0.862500 0.000000 291.056250 -0.350000 1000.000000
53 51.524633 2026-09-03T14:38:20.748 0.862500 0.000000 291.187500 -0.350000 1000.000000
54 52.534418 2026-09-03T14:38:21.758 0.853125 0.000000 291.206250 -0.337500 1000.000000
55 53.545409 2026-09-03T14:38:22.769 0.853125 0.000000 290.812500 -0.337500 1000.000000
56 54.555086 2026-09-03T14:38:23.778 0.834375 0.000000 290.625000 -0.375000 1000.000000
57 55.565609 2026-09-03T14:38:24.789 0.853125 0.000000 290.175000 -0.375000 1000.000000
58 56.576575 2026-09-03T14:38:25.800 0.834375 0.000000 289.884375 -0.350000 1000.000000
59 57.586980 2026-09-03T14:38:26.810 0.834375 0.000000 289.509375 -0.350000 1000.000000
60 58.596223 2026-09-03T14:38:27.819 0.834375 0.000000 289.725000 -0.375000 1000.000000
61 59.606634 2026-09-03T14:38:28.830 0.853125 0.000000 290.278125 -0.375000 1000.000000
62 60.616196 2026-09-03T14:38:29.839 0.853125 0.000000 290.259375 -0.375000 1000.000000
63 61.625949 2026-09-03T14:38:30.849 0.853125 0.000000 290.212500 -0.375000 1000.000000
64 62.635355 2026-09-03T14:38:31.859 0.853125 0.000000 289.931250 -0.375000 1000.000000
65 63.646221 2026-09-03T14:38:32.869 0.853125 0.000000 289.556250 -0.375000 1000.000000
66 64.655401 2026-09-03T14:38:33.878 0.853125 0.000000 288.984375 -0.375000 1000.000000
67 65.665560 2026-09-03T14:38:34.888 0.853125 0.000000 288.412500 -0.350000 1000.000000
68 66.675832 2026-09-03T14:38:35.899 0.834375 0.000000 287.981250 -0.350000 1000.000000
69 67.685689 2026-09-03T14:38:36.909 0.834375 0.000000 287.634375 -0.337500 1000.000000
70 68.695668 2026-09-03T14:38:37.919 0.834375 0.000000 287.325000 -0.350000 1000.000000
71 69.705494 2026-09-03T14:38:38.928 0.834375 0.000000 286.959375 -0.350000 1000.000000
72 70.716592 2026-09-03T14:38:39.940 0.853125 0.000000 286.884375 -0.375000 1000.000000
73 71.726608 2026-09-03T14:38:40.950 0.834375 0.000000 286.612500 -0.337500 1000.000000
74 72.737259 2026-09-03T14:38:41.960 0.853125 0.000000 286.228125 -0.337500 1000.000000
75 73.747217 2026-09-03T14:38:42.970 0.834375 0.000000 286.125000 -0.337500 1000.000000
76 74.757698 2026-09-03T14:38:43.981 0.834375 0.000000 285.834375 -0.337500 1000.000000
Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

@@ -0,0 +1,22 @@
time_s,timestamp,flow_after_slm,opening_pct,pressure_before_kpa,pressure_after_kpa,motor_position
0.000000,2026-09-03T14:41:56.141,158.231250,100.000000,170.634375,100.837500,800.000000
1.008806,2026-09-03T14:41:57.150,157.809375,100.000000,170.728125,100.437500,800.000000
2.018271,2026-09-03T14:41:58.159,157.378125,100.000000,170.803125,100.512500,800.000000
3.028556,2026-09-03T14:41:59.170,157.003125,100.000000,170.559375,100.375000,800.000000
4.038681,2026-09-03T14:42:00.180,157.003125,100.000000,170.503125,100.400000,800.000000
5.049503,2026-09-03T14:42:01.191,157.753125,100.000000,170.850000,100.600000,800.000000
6.060871,2026-09-03T14:42:02.202,157.837500,100.000000,171.075000,100.537500,800.000000
7.072065,2026-09-03T14:42:03.213,157.950000,100.000000,171.009375,100.575000,800.000000
8.083669,2026-09-03T14:42:04.225,157.950000,100.000000,170.906250,100.600000,800.000000
9.094138,2026-09-03T14:42:05.235,84.112500,0.000000,261.731250,-0.212500,1000.000000
10.103063,2026-09-03T14:42:06.244,18.553125,0.000000,306.000000,-0.300000,1000.000000
11.112780,2026-09-03T14:42:07.254,6.487500,0.000000,306.000000,-0.312500,1000.000000
12.122113,2026-09-03T14:42:08.263,1.303125,0.000000,306.000000,-0.337500,1000.000000
13.132136,2026-09-03T14:42:09.273,0.900000,0.000000,305.325000,-0.350000,1000.000000
14.142424,2026-09-03T14:42:10.284,0.900000,0.000000,304.078125,-0.350000,1000.000000
15.151374,2026-09-03T14:42:11.293,0.900000,0.000000,303.281250,-0.337500,1000.000000
16.161177,2026-09-03T14:42:12.302,0.881250,0.000000,302.550000,-0.337500,1000.000000
17.172047,2026-09-03T14:42:13.314,0.881250,0.000000,302.184375,-0.350000,1000.000000
18.182562,2026-09-03T14:42:14.324,0.881250,0.000000,301.809375,-0.337500,1000.000000
19.193113,2026-09-03T14:42:15.334,0.881250,0.000000,301.612500,-0.337500,1000.000000
20.203147,2026-09-03T14:42:16.344,0.881250,0.000000,301.275000,-0.350000,1000.000000
1 time_s timestamp flow_after_slm opening_pct pressure_before_kpa pressure_after_kpa motor_position
2 0.000000 2026-09-03T14:41:56.141 158.231250 100.000000 170.634375 100.837500 800.000000
3 1.008806 2026-09-03T14:41:57.150 157.809375 100.000000 170.728125 100.437500 800.000000
4 2.018271 2026-09-03T14:41:58.159 157.378125 100.000000 170.803125 100.512500 800.000000
5 3.028556 2026-09-03T14:41:59.170 157.003125 100.000000 170.559375 100.375000 800.000000
6 4.038681 2026-09-03T14:42:00.180 157.003125 100.000000 170.503125 100.400000 800.000000
7 5.049503 2026-09-03T14:42:01.191 157.753125 100.000000 170.850000 100.600000 800.000000
8 6.060871 2026-09-03T14:42:02.202 157.837500 100.000000 171.075000 100.537500 800.000000
9 7.072065 2026-09-03T14:42:03.213 157.950000 100.000000 171.009375 100.575000 800.000000
10 8.083669 2026-09-03T14:42:04.225 157.950000 100.000000 170.906250 100.600000 800.000000
11 9.094138 2026-09-03T14:42:05.235 84.112500 0.000000 261.731250 -0.212500 1000.000000
12 10.103063 2026-09-03T14:42:06.244 18.553125 0.000000 306.000000 -0.300000 1000.000000
13 11.112780 2026-09-03T14:42:07.254 6.487500 0.000000 306.000000 -0.312500 1000.000000
14 12.122113 2026-09-03T14:42:08.263 1.303125 0.000000 306.000000 -0.337500 1000.000000
15 13.132136 2026-09-03T14:42:09.273 0.900000 0.000000 305.325000 -0.350000 1000.000000
16 14.142424 2026-09-03T14:42:10.284 0.900000 0.000000 304.078125 -0.350000 1000.000000
17 15.151374 2026-09-03T14:42:11.293 0.900000 0.000000 303.281250 -0.337500 1000.000000
18 16.161177 2026-09-03T14:42:12.302 0.881250 0.000000 302.550000 -0.337500 1000.000000
19 17.172047 2026-09-03T14:42:13.314 0.881250 0.000000 302.184375 -0.350000 1000.000000
20 18.182562 2026-09-03T14:42:14.324 0.881250 0.000000 301.809375 -0.337500 1000.000000
21 19.193113 2026-09-03T14:42:15.334 0.881250 0.000000 301.612500 -0.337500 1000.000000
22 20.203147 2026-09-03T14:42:16.344 0.881250 0.000000 301.275000 -0.350000 1000.000000
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
2026-09-03 16:10:35.432 INFO PLC 已连接 192.168.1.88:502,站号=1;模型=内置 valve_model_004.json80 点)
2026-09-03 16:11:54.165 INFO 目标流量设置为 60.000 SLM
2026-09-03 16:12:01.872 INFO PID 模式 FAR -> NEAR:基础 Kp=0.100 Ki=0.100 Kd=0.000,有效 Kp=0.100 Ki=0.100scale=1.000),最大开度速度=2.000%/s;原因=|误差|=0.056 SLM <= 阈值=2.000 SLM,连续 10 个周期
2026-09-03 16:12:35.171 INFO 设备已安全断开
+327
View File
@@ -0,0 +1,327 @@
{
"motor_open": 800.0,
"motor_closed": 1000.0,
"enforce_monotonic": false,
"area_table": [
[
800.0,
0.7209916054057561
],
[
802.0,
0.7125354555381634
],
[
804.0,
0.6561316030427767
],
[
808.0,
0.6598630623935171
],
[
810.0,
0.6753272742288868
],
[
812.0,
0.621104587742021
],
[
814.0,
0.6240506162781531
],
[
816.0,
0.595488483660327
],
[
820.0,
0.5924200631363882
],
[
824.0,
0.5416291218881875
],
[
826.0,
0.5394252385668694
],
[
828.0,
0.5163395000335028
],
[
830.0,
0.5386794264154983
],
[
832.0,
0.5087205083171691
],
[
836.0,
0.46458618805923835
],
[
838.0,
0.47364585752172367
],
[
840.0,
0.46827932318875193
],
[
844.0,
0.43243164500883
],
[
848.0,
0.3984505111170211
],
[
850.0,
0.413907876348738
],
[
852.0,
0.3813184002409792
],
[
856.0,
0.3620510591370111
],
[
860.0,
0.35385962516692854
],
[
862.0,
0.3447135247536444
],
[
864.0,
0.3156364126476435
],
[
868.0,
0.29675945681605137
],
[
870.0,
0.306501316808223
],
[
872.0,
0.27050112885120703
],
[
874.0,
0.27959628186642943
],
[
876.0,
0.25531630079673473
],
[
880.0,
0.2535114488327249
],
[
884.0,
0.225092732619715
],
[
886.0,
0.22841807147673635
],
[
888.0,
0.2086511681620107
],
[
890.0,
0.2218855517263943
],
[
892.0,
0.20075819923793375
],
[
896.0,
0.18918524712906334
],
[
898.0,
0.19215080166026982
],
[
900.0,
0.18807050761212185
],
[
904.0,
0.17245083700454789
],
[
908.0,
0.16153923372120163
],
[
910.0,
0.16985605651603905
],
[
912.0,
0.15429304713019132
],
[
916.0,
0.151574549449086
],
[
920.0,
0.1530920502767755
],
[
922.0,
0.14979075485074195
],
[
924.0,
0.1428763734264717
],
[
928.0,
0.14159844506284675
],
[
930.0,
0.1481317007247288
],
[
932.0,
0.13731846151118046
],
[
934.0,
0.13994004337287919
],
[
936.0,
0.1344586476808378
],
[
940.0,
0.13892390786199926
],
[
944.0,
0.13143952256792465
],
[
946.0,
0.13338917913072393
],
[
948.0,
0.12960342436338804
],
[
950.0,
0.14035651644159308
],
[
952.0,
0.13085895406830145
],
[
956.0,
0.12884698464592212
],
[
958.0,
0.1340123387779701
],
[
960.0,
0.1353515916092655
],
[
964.0,
0.12802772183117378
],
[
968.0,
0.12244258908969449
],
[
970.0,
0.12014602858505499
],
[
972.0,
0.09119982507825447
],
[
973.0,
0.09502834500740251
],
[
976.0,
0.06089048620458142
],
[
979.0,
0.010700147302522556
],
[
980.0,
0.002096375744184619
],
[
982.0,
0.0020885117831348173
],
[
984.0,
0.0020668385196096484
],
[
985.0,
0.002069140121524581
],
[
988.0,
0.002072106769105725
],
[
990.0,
0.0021514223899834284
],
[
991.0,
0.002063386116737249
],
[
992.0,
0.002091005339716443
],
[
994.0,
0.00207971813405268
],
[
996.0,
0.0020990609464187075
],
[
997.0,
0.0020639615172159823
],
[
1000.0,
0.0020914299290714883
]
]
}
+73
View File
@@ -0,0 +1,73 @@
气体流量控制程序使用说明
========================
一、交付文件
1. FlowControl.exe
双击运行的 Windows 单文件程序,不需要安装 Python。
2. valve_model_004.json
内置模型的外置备份和自定义模型格式参考。程序默认使用 EXE 内部自带的
valve_model_004.json,外置文件被移动或删除不会影响默认模型。
3. 使用说明.txt
本文件。
二、重要安全说明
本程序会直接控制真实阀门。首次连接前必须确认 PLC IP、端口、站号、寄存器
地址、传感器量程、阀门动作方向和管路允许压力,并准备独立的机械或电气急停。
根据当前工程的安全策略:
- 连接 PLC 成功后,程序会先命令阀门至 100% 开度(电机行程 800);
- 点击“断开设备”、关闭窗口或发生控制故障时,程序会再次尝试命令阀门至
100% 开度,然后断开 PLC
- “100% 开度”是当前软件定义的逻辑位置,现场必须再次确认真实动作方向。
三、正常操作
1. 双击 FlowControl.exe。程序启动后不会自动连接设备。
2. 核对 PLC 地址、端口和站号。默认值为 192.168.1.88、502、1。
3. 默认模型显示为“内置 valve_model_004.json80 点)”。
4. 点击“连接设备”。连接成功后程序先设置 100% 开度,并进入传感器监测。
5. 输入 0~300 SLM 的目标值,点击“设置目标流量”或按回车。首次设置即启动
10 Hz 闭环;运行中可以继续修改目标。
6. 观察当前流量、阀前压力、阀后压力、下发总开度、电机行程、前馈开度、
PI 修正量和 FAR/NEAR 模式。
7. 结束时点击“断开设备”,等待状态变为“设备已断开”后再关闭程序。
四、选择其他 A_eff 模型
只能在未连接或故障已断开的状态下切换模型。点击“选择 JSON…”后选择由本工程
辨识工具生成的模型。程序会检查 JSON 结构、有效数值、标定点数量以及行程范围
是否与当前 800~1000 配置一致。无效模型不会用于控制。
点击“使用内置”可随时恢复 valve_model_004.json。该模型允许非单调特性;同一
A_eff 存在多个反解行程时,沿用工程算法选择更接近关闭端的较大行程。
五、状态与故障
- 已连接 / 监测中:设备已连接,阀门保持 100% 开度,尚未启动闭环。
- 闭环运行中:目标流量已经生效,程序以 10 Hz 读取传感器并下发总开度。
- 故障 / 已断开:出现流量越界、压力超限、连续读取失败、写入失败、控制周期
严重超时或通信异常;程序已停止闭环并执行安全断开。
运行日志保存在 FlowControl.exe 同目录下的 logs\flow_control.log。遇到问题时请
保留该日志。日志最多约 2 MB,自动保留 3 份历史文件。
六、当前默认硬件参数
- 阀后流量:D700,量程 0300 SLM
- 阀后压力:D710,量程 0400 kPa
- 阀前压力:D720,量程 0300 kPa
- 电机命令:D730,行程 0~1000 映射到模拟量 032000
- 有效控制行程:800(100% 开度)~10000% 开度)
- 压力保护上限:500 kPa
首次真实设备投用必须由现场人员确认上述参数。