Files
flow_control/config.py
T
louis cfeedcf887 完善流量控制前馈、稳态判定与阀门标定流程
- 前馈冻结改用可配置的 3 秒流量绝对误差滑窗,修复采样抖动造成的重复解冻,并保留下游扰动后的自动更新
- 支持非单调阀特性反解、最小可测面积以下直接全关,以及闭阀端精细扫点与辨识开关
- 调整双压力判稳、最长等待时间、在线入口全开收尾和闭环四联图
- 更新配置、README、阀模型及离线测试,归档本轮实验数据与诊断产物
2026-09-02 16:14:42 +08:00

278 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""气体流量闭环控制的集中配置。
首次连接真实设备前,请重点核对:PLC 寄存器地址、量程、阀门打开端行程和
关闭端行程。PID 参数是安全起步值,不是最终整定结果。
"""
import math
# ---------------------------------------------------------------------------
# MT2-AM8 通讯与量程(旧方案;代码保留但当前不再使用)
# ---------------------------------------------------------------------------
MT2AM8_HOST = "192.168.1.12"
MT2AM8_PORT = 502
MT2AM8_SLAVE_ID = 1
# MT2AM8Client 用这些量程把模拟量转换成物理量。
PRESSURE_RANGE_KPA = 400.0
FLOW_METER_RANGE_SLM = 300.0
# ---------------------------------------------------------------------------
# PLCEasy521Modbus TCP 通讯与寄存器地址
# ---------------------------------------------------------------------------
PLC_HOST = "192.168.1.88"
PLC_PORT = 502
PLC_SLAVE_ID = 1
# D 寄存器为直接 Modbus 地址:读用功能码 03(保持寄存器),写用功能码 06。
# 模拟量 0~32000 线性对应各量程满量程(映射常量在 PcControl.py)。
# 三个测量量 + 一个输出,地址与量程集中放在一起(名称区分阀前 / 阀后)。
FLOW_AFTER_ADDR = 700 # 阀后流量 D700,量程 0~300 SLM
FLOW_AFTER_RANGE_SLM = 300.0
PRESSURE_AFTER_ADDR = 710 # 阀后压力 D710,量程 0~400 kPa
PRESSURE_AFTER_RANGE_KPA = 400.0
PRESSURE_BEFORE_ADDR = 720 # 阀前压力 D720,量程 0~300 kPa
PRESSURE_BEFORE_RANGE_KPA = 300.0
MOTOR_OUTPUT_ADDR = 730 # 电机输出 D7300~10V,行程 0~1000 映射 0~32000
# ---------------------------------------------------------------------------
# 控制目标和控制周期
# ---------------------------------------------------------------------------
TARGET_FLOW_MIN_SLM = 0.0
TARGET_FLOW_MAX_SLM = 300.0
ZERO_FLOW_THRESHOLD_SLM = 0.5
TARGET_SWITCH_KEY = "s" # 运行中按此键进入"输入新目标流量"模式
CONTROL_PERIOD_S = 0.1 # 100 ms10 Hz
MAX_CONTROL_DT_S = 0.3 # 超过此值视为控制循环严重超时
STATUS_PRINT_PERIOD_S = 1.0
# ---------------------------------------------------------------------------
# PID:输出统一定义为阀门开度百分比
# ---------------------------------------------------------------------------
# 大误差时使用 FAR 参数快速接近目标;小误差时使用 NEAR 参数抑制振荡。
# 两档之间采用带滞回的状态切换,不会逐周期连续改变 PID 参数。
PID_FAR_KP = 0.5
PID_FAR_KI = 0.5
PID_FAR_KD = 0.0
PID_NEAR_KP = 0.1
PID_NEAR_KI = 0.1
PID_NEAR_KD = 0.0
OPENING_MIN_PCT = 0.0
OPENING_MAX_PCT = 100.0
MAX_OPENING_RATE_FAR_PCT_S = 200.0
MAX_OPENING_RATE_NEAR_PCT_S = 2.0
# FAR -> NEAR:绝对误差连续满足 near 阈值指定周期数后切换。
# NEAR -> FAR:绝对误差连续达到 far 阈值指定周期数后切换。
# 比例阈值会被限制在各自的 MIN~MAX 范围内。
PID_NEAR_ERROR_MIN_SLM = 1.0
PID_NEAR_ERROR_MAX_SLM = 2.0
PID_NEAR_ERROR_TARGET_RATIO = 0.05
PID_FAR_ERROR_MIN_SLM = 4.0
PID_FAR_ERROR_MAX_SLM = 8.0
PID_FAR_ERROR_TARGET_RATIO = 0.20
PID_MODE_SWITCH_CONFIRM_CYCLES = 10
# ---------------------------------------------------------------------------
# 稳态判定(用于控制性能报告)
# ---------------------------------------------------------------------------
STEADY_STATE_WINDOW_S = 5.0 # 滑动时间窗口长度,单位:秒
STEADY_STATE_MAX_WAIT_S = 30.0 # 单个开环扫点等待稳态的最长时间
STEADY_STATE_BAND_PCT = 1.0 # 滑窗均值相对目标的允许误差带(±1%)
STEADY_STATE_PRESSURE_BAND_KPA = 1.0 # 压力样本相对滑窗均值的允许误差带(±kPa)
# 仅供 open_loop.py 使用;开环没有目标流量,仍用相对标准差判断流量稳定。
STEADY_STATE_STD_PCT = 1.0
AUTO_STOP_ON_STEADY = False # 达到稳态后是否自动停止控制(False 则持续控制)
# 开环随机开度扫描结束后,追加闭阀端精细行程扫描。
OPEN_LOOP_FINE_STROKE_START = 970.0
OPEN_LOOP_FINE_STROKE_STEP = 3.0
# identify_valve.py 是否检查单调性并只保留最长单调连续段。
#True:保持原逻辑,检测非单调并只保留最长单调连续段。
#False:不丢弃非单调点,保留所有按行程平均后的数据,关闭检查后生成的 JSON 会记录"enforce_monotonic": false
#非单调表可能对应多个反解行程,此时选择数值更大的行程,即更接近关闭端的解。
IDENTIFY_VALVE_MONOTONIC_CHECK_ENABLED = False
# ---------------------------------------------------------------------------
# 阀门执行机构
# ---------------------------------------------------------------------------
# 电机行程越大,阀门开度越小。
# opening=0% -> MOTOR_CLOSED_POSITION(完全关闭)
# opening=100% -> MOTOR_OPEN_POSITION(打开端/机械死区边界)
MOTOR_CLOSED_POSITION = 1000.0
MOTOR_OPEN_POSITION = 800.0
# 控制启动时的阀门初始开度;增量 PID 从该开度开始累加 Δu。
INITIAL_OPENING_PCT = 100.0
# ---------------------------------------------------------------------------
# 前馈 + 增益调度(前馈默认开启,增益调度默认关闭)
# ---------------------------------------------------------------------------
# 阀前压力 P1(前馈缩放 / 判阻塞 / 增益调度用),地址见上 PLC 段 PRESSURE_BEFORE_ADDR。
# 阀特性表 JSONidentify_valve.py 产出)。空字符串表示关闭前馈。
VALVE_MODEL_PATH = "valve_model_004.json"
# 前馈反解打底 + PI 修残差(市面 MFC 结构)。
FEEDFORWARD_ENABLED = True
# 前馈开启时,PI 输出是“围绕前馈的修正量”,限幅为对称小范围(±%)。
FEEDFORWARD_CORRECTION_BAND_PCT = 50.0
# |目标流量-实测流量| 不超过此值时冻结前馈,只更新 PID correction。
FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM = 2.0
# 前馈冻结/更新判断所用的“流量绝对误差均值”滑动窗口长度。
FEEDFORWARD_UPDATE_WINDOW_S = 3.0
# PI 增益按阀前压力 P1 缩放(摊平 K_x ∝ P1 的非线性)。
GAIN_SCHEDULE_ENABLED = False
GAIN_SCHEDULE_REF_ABS_KPA = 501.325 # 400 + 101.325,满压时调好的基准增益对应压力
GAIN_SCHEDULE_FLOOR_ABS_KPA = 60.0 # 绝对压力下限,防止 P1 很低时增益爆炸
GAIN_SCHEDULE_SCALE_CHANGE_THRESHOLD = 0.05 # scale 相对变化超过此值才重算增益
# ---------------------------------------------------------------------------
# 安全阈值
# ---------------------------------------------------------------------------
# 物理读数允许范围;超过范围立即报故障停机(阀门不被驱动时回到全开)。
FLOW_VALID_MIN_SLM = 0.0
FLOW_VALID_MAX_SLM = 300.0
MAX_PRESSURE_KPA = 500 # 必须按实际管路额定压力确认
# 短暂读取失败时保持上一输出,不下发新位置;连续达到阈值后报故障停机。
MAX_CONSECUTIVE_FLOW_FAILURES = 3
MAX_CONSECUTIVE_PRESSURE_FAILURES = 3
# 日志目录相对于工程目录。
LOG_DIRECTORY = "logs"
LOG_FILE_NAME = "flow_control.log"
# 每次运行的逐周期数据和曲线图保存目录(相对于工程目录)。
DATA_DIRECTORY = "data"
PLOT_DPI = 160
def validate_config():
"""在接触硬件前检查明显的配置错误。"""
if CONTROL_PERIOD_S <= 0:
raise ValueError("CONTROL_PERIOD_S 必须大于 0")
if STEADY_STATE_WINDOW_S <= CONTROL_PERIOD_S:
raise ValueError("STEADY_STATE_WINDOW_S 必须大于 CONTROL_PERIOD_S")
if STEADY_STATE_MAX_WAIT_S <= 0:
raise ValueError("STEADY_STATE_MAX_WAIT_S 必须大于 0")
if (
STEADY_STATE_BAND_PCT < 0
or STEADY_STATE_PRESSURE_BAND_KPA < 0
or STEADY_STATE_STD_PCT < 0
):
raise ValueError("稳态误差带、压力带和标准差阈值不得为负数")
if MAX_CONTROL_DT_S < CONTROL_PERIOD_S:
raise ValueError("MAX_CONTROL_DT_S 不得小于 CONTROL_PERIOD_S")
if TARGET_FLOW_MIN_SLM > TARGET_FLOW_MAX_SLM:
raise ValueError("目标流量上下限配置错误")
if TARGET_FLOW_MAX_SLM > FLOW_AFTER_RANGE_SLM:
raise ValueError("目标流量上限不得超过流量计量程")
if FLOW_VALID_MIN_SLM > FLOW_VALID_MAX_SLM:
raise ValueError("流量有效范围上下限配置错误")
if FLOW_VALID_MAX_SLM > FLOW_AFTER_RANGE_SLM:
raise ValueError("流量有效范围上限不得超过流量计量程")
if not 0 <= OPENING_MIN_PCT < OPENING_MAX_PCT <= 100:
raise ValueError("阀门开度范围必须位于 0~100%,且下限小于上限")
if MOTOR_OPEN_POSITION >= MOTOR_CLOSED_POSITION:
raise ValueError("本系统行程越大开度越小,因此打开端行程必须小于关闭端行程")
if MOTOR_OPEN_POSITION < 0 or MOTOR_CLOSED_POSITION > 1000:
raise ValueError("电机行程必须位于当前采用的 0~1000 范围")
pid_gains = (
PID_FAR_KP,
PID_FAR_KI,
PID_FAR_KD,
PID_NEAR_KP,
PID_NEAR_KI,
PID_NEAR_KD,
)
if any(value < 0 for value in pid_gains):
raise ValueError("FAR/NEAR PID 参数不得为负数")
if (
MAX_OPENING_RATE_FAR_PCT_S <= 0
or MAX_OPENING_RATE_NEAR_PCT_S <= 0
):
raise ValueError("FAR/NEAR 最大开度变化速度必须大于 0")
pid_error_limits = (
PID_NEAR_ERROR_MIN_SLM,
PID_NEAR_ERROR_MAX_SLM,
PID_FAR_ERROR_MIN_SLM,
PID_FAR_ERROR_MAX_SLM,
)
if any(value < 0 for value in pid_error_limits):
raise ValueError("PID 模式切换误差上下限不得为负数")
if PID_NEAR_ERROR_MIN_SLM > PID_NEAR_ERROR_MAX_SLM:
raise ValueError("PID NEAR 误差下限不得大于上限")
if PID_FAR_ERROR_MIN_SLM > PID_FAR_ERROR_MAX_SLM:
raise ValueError("PID FAR 误差下限不得大于上限")
if (
PID_NEAR_ERROR_TARGET_RATIO < 0
or PID_FAR_ERROR_TARGET_RATIO < 0
):
raise ValueError("PID 模式切换目标比例不得为负数")
if (
PID_NEAR_ERROR_MAX_SLM >= PID_FAR_ERROR_MIN_SLM
or PID_NEAR_ERROR_TARGET_RATIO >= PID_FAR_ERROR_TARGET_RATIO
):
raise ValueError("PID NEAR 阈值范围必须整体小于 FAR 阈值范围")
if PID_MODE_SWITCH_CONFIRM_CYCLES < 1:
raise ValueError("PID_MODE_SWITCH_CONFIRM_CYCLES 必须至少为 1")
if MAX_CONSECUTIVE_FLOW_FAILURES < 1:
raise ValueError("MAX_CONSECUTIVE_FLOW_FAILURES 必须至少为 1")
if PRESSURE_AFTER_ADDR is not None and MAX_PRESSURE_KPA is None:
raise ValueError("启用压力通道时必须配置 MAX_PRESSURE_KPA")
if PRESSURE_BEFORE_ADDR is not None:
if not isinstance(PRESSURE_BEFORE_ADDR, int):
raise ValueError("PRESSURE_BEFORE_ADDR 必须为整数地址或 None")
if PRESSURE_BEFORE_ADDR < 0:
raise ValueError("PRESSURE_BEFORE_ADDR 不得为负数")
if MAX_PRESSURE_KPA is None:
raise ValueError("启用阀前压力通道时必须配置 MAX_PRESSURE_KPA")
if GAIN_SCHEDULE_REF_ABS_KPA <= 0:
raise ValueError("GAIN_SCHEDULE_REF_ABS_KPA 必须大于 0")
if GAIN_SCHEDULE_FLOOR_ABS_KPA <= 0:
raise ValueError("GAIN_SCHEDULE_FLOOR_ABS_KPA 必须大于 0")
if not 0 < FEEDFORWARD_CORRECTION_BAND_PCT <= 100:
raise ValueError("FEEDFORWARD_CORRECTION_BAND_PCT 必须位于 0~100")
if (
not isinstance(FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM, (int, float))
or not math.isfinite(FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM)
or FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM < 0
):
raise ValueError(
"FEEDFORWARD_UPDATE_ERROR_THRESHOLD_SLM 必须是非负有限数值"
)
if (
not isinstance(FEEDFORWARD_UPDATE_WINDOW_S, (int, float))
or not math.isfinite(FEEDFORWARD_UPDATE_WINDOW_S)
or FEEDFORWARD_UPDATE_WINDOW_S <= 0
):
raise ValueError("FEEDFORWARD_UPDATE_WINDOW_S 必须是正的有限数值")
if not MOTOR_OPEN_POSITION <= OPEN_LOOP_FINE_STROKE_START < MOTOR_CLOSED_POSITION:
raise ValueError(
"OPEN_LOOP_FINE_STROKE_START 必须位于电机开端和闭端行程之间"
)
if (
not isinstance(OPEN_LOOP_FINE_STROKE_STEP, (int, float))
or not math.isfinite(OPEN_LOOP_FINE_STROKE_STEP)
or OPEN_LOOP_FINE_STROKE_STEP <= 0
):
raise ValueError("OPEN_LOOP_FINE_STROKE_STEP 必须是正的有限数值")
if not isinstance(IDENTIFY_VALVE_MONOTONIC_CHECK_ENABLED, bool):
raise ValueError("IDENTIFY_VALVE_MONOTONIC_CHECK_ENABLED 必须是布尔值")
if GAIN_SCHEDULE_SCALE_CHANGE_THRESHOLD <= 0:
raise ValueError("GAIN_SCHEDULE_SCALE_CHANGE_THRESHOLD 必须大于 0")