Files
ReinLoopTest/ReinLoop/ind_collector.py
T
2026-07-31 11:29:10 +08:00

267 lines
10 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.
import numpy as np
import time
import pandas as pd
import datetime
import os
import warnings
# 忽略所有的 DeprecationWarning
warnings.filterwarnings("ignore", category=DeprecationWarning)
import logging
# 将 pymodbus 的日志级别提高到 ERROR,屏蔽 WARNING 及以下的信息
logging.getLogger("pymodbus").setLevel(logging.ERROR)
# ==================== 1.5 生成复合辨识序列 (三级火箭) ====================
def generate_composite_sequence(dt, n_order, t_c, levels):
"""
生成包含 闭阀、全开、多电平M序列 的终极复合辨识序列
"""
# 1. 第一段:绝对闭阀段 (占位 8 秒)
# 目的:憋气升压,暴露纯进气增益 c1
part1_duration = 4.0
part1_samples = int(part1_duration / dt)
part1_signal = np.zeros(part1_samples)
# 2. 第二段:绝对全开段 (占位 6 秒)
# 目的:极限泄压,暴露纯排气增益 c2 和机械延迟 tau
part2_duration = 5.0
part2_samples = int(part2_duration / dt)
part2_signal = np.full(part2_samples, 100.0) # 假设 100 为全开
# 3. 第三段:多电平 M 序列段
# 目的:中频动态跳变,暴露出 S 曲线非线性特征
samples_per_bit = int(t_c / dt)
part3_signal = generate_prbs(n_order=n_order, samples_per_bit=samples_per_bit, levels=levels)
# 拼接并返回完整序列
composite_signal = np.concatenate([part1_signal, part2_signal, part3_signal])
return composite_signal
# ==================== 1. 生成 M 序列信号 ====================
def generate_prbs(n_order=7, low_val=40, high_val=60, samples_per_bit=20, levels=None):
"""
生成线性反馈移位寄存器的 PRBS 信号。
n_order: 阶数 (2^n-1 长度)
low_val: 低位输出(两电平时使用)
high_val: 高位输出(两电平时使用)
samples_per_bit: 每个码元持续的控制周期数
levels: 可选的多电平列表(长度必须为 2^k),若提供则忽略 low_val/high_val
"""
length = 2 ** n_order - 1
reg = np.ones(n_order, dtype=int)
bit_seq = []
# 反馈多项式:取最高位和次高位(可根据需要修改)
for _ in range(length):
feedback = reg[-1] ^ reg[-2] # 使用最后两位,适用于任意阶数
bit_seq.append(reg[-1])
reg = np.roll(reg, 1)
reg[0] = feedback
# 若未指定 levels,则使用两电平映射
if levels is None:
raw = np.array(bit_seq)
scaled = np.where(raw == 1, high_val, low_val)
signal = np.repeat(scaled, samples_per_bit)
return signal
# 多电平模式:将二进制序列按组转换为索引
n_levels = len(levels)
group_bits = int(np.log2(n_levels))
if 2 ** group_bits != n_levels:
raise ValueError("levels 长度必须是 2 的整数次幂")
num_groups = len(bit_seq) // group_bits
bit_seq = bit_seq[:num_groups * group_bits]
indices = []
for i in range(0, len(bit_seq), group_bits):
idx = 0
for j in range(group_bits):
idx = (idx << 1) | bit_seq[i + j]
indices.append(idx)
scaled = [levels[idx] for idx in indices]
signal = np.repeat(scaled, samples_per_bit)
return signal
# ==================== 5. 实时数据采集(通过 PLC ====================
def collect_data_with_prbs(conn_mgr,
q_in_val, dt=0.05, n_order=7, t_c=1.0, levels=None,
dead_area=240, xa_full=1062.5,
save_dir=None, V_val=None,
should_stop=None, log=print, on_sample=None,
repeat=2):
"""使用复合 M 序列激励,通过 MT2-AM8 模块采集压力响应数据。
连接由调用方负责:传入已连接的 ConnectionManager。
本函数不创建客户端、不调用 exit()/input()、不画图,只跑采集、存盘并返回结果。
参数:
conn_mgr : 已连接的 ConnectionManager(需有 read_pressure() / set_motor_position()
q_in_val : 实验流量 (SLM),写入数据列并用于文件名
dt : 控制/采样周期 (秒)
n_order : M 序列阶数
t_c : 码元周期 (秒)
levels : 多电平列表(长度需为 2 的整数次幂)
dead_area : 电机死区补偿
xa_full : 阀门全开对应的电机位置上限
save_dir : CSV 保存目录,None 时存到当前目录的 ind_data/
V_val : 可选容积 (L),提供时写入文件名
should_stop : 可选回调,返回 True 时提前中止(供 GUI 停止按钮用)
log : 日志回调,默认 printGUI 可传入 self.log_message
on_sample : 可选回调 on_sample(t, u_cmd, pressure),每采样点调用(供 GUI 刷新界面)
repeat : 整段复合序列重复次数,默认 2
返回:
dict: {
't': [...], 'u': [...], 'p': [...],
'filename': str | None, 'samples': int, 'success': bool
}
"""
# 生成复合序列(闭阀 + 全开 + 多电平 M 序列)
signal = generate_composite_sequence(dt, n_order, t_c, levels)
if repeat > 1:
signal = np.tile(signal, repeat)
log(f"序列已重复 {repeat} 次")
total_samples = len(signal)
duration = total_samples * dt
log(f"复合序列总长度: {total_samples} 步, 预计耗时: {duration:.1f} 秒 ({duration/60:.1f} 分钟)")
# 数据记录
t_record = []
u_record = []
p_record = []
# 初始化压力滤波
p_filter = conn_mgr.read_pressure()
log("开始采集...")
start_time = time.perf_counter()
for step, u_cmd in enumerate(signal):
if should_stop is not None and should_stop():
log("辨识采集被手动中止")
conn_mgr.set_motor_position(0)
break
# 记录绝对时间
current_t = time.perf_counter() - start_time
# 写入阀门开度(含死区补偿)
xa = dead_area + (100 - u_cmd) * (xa_full - dead_area) / 100
conn_mgr.set_motor_position(xa)
# 读取压力
p_raw = conn_mgr.read_pressure()
alpha = 1
if p_raw is not None:
p_filter = alpha * p_raw + (1 - alpha) * p_filter
# 记录数据
t_record.append(current_t)
u_record.append(u_cmd)
p_record.append(p_filter)
if on_sample is not None:
on_sample(current_t, u_cmd, p_filter)
# 控制周期延时
elapsed = time.perf_counter() - start_time
expected = step * dt
if elapsed < expected:
time.sleep(expected - elapsed)
# 保存为 CSV
df = pd.DataFrame({'t': t_record, 'u': u_record, 'p': p_record})
df['q_in'] = q_in_val
if V_val is not None:
df['V'] = V_val
# if save_dir is None:
# save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ind_data")
# os.makedirs(save_dir, exist_ok=True)
# 生成 CSV 字节数据(不写入磁盘)
csv_buffer = df.to_csv(index=False).encode('utf-8')
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
if V_val is not None:
filename = f'identification_data_{q_in_val}SLM_{V_val}L_{timestamp}.csv'
else:
filename = f'identification_data_{q_in_val}SLM_{timestamp}.csv'
# if V_val is not None:
# filename = os.path.join(save_dir, f'identification_data_{q_in_val}SLM_{V_val}L_{timestamp}.csv')
# else:
# filename = os.path.join(save_dir, f'identification_data_{q_in_val}SLM_{timestamp}.csv')
# df.to_csv(filename, index=False)
# log(f"数据已保存至 {filename}")
return {
't': t_record,
'u': u_record,
'p': p_record,
'filename': filename,
'csv_data': csv_buffer,
'samples': len(t_record),
'success': len(t_record) > 0,
}
# ==================== 6. 主程序 ====================
def main():
"""独立运行入口:自建连接、采集、存盘。"""
from PcControl import Easy521ModbusClient, MotorModbusRTUClient
import os
from core.simulated_device import SimulatedDevice
dt = 0.1
n_order = 7 # 码元数 127
t_c = 5 # 码元周期 5 秒
levels = [10, 20, 30, 40, 50, 60, 70, 80] # 8 个电平,对应 group_bits=3
if os.environ.get("REINLOOP_SIMULATION", "").strip().lower() in {
"1", "true", "yes", "on"}:
device = SimulatedDevice()
device.connect()
q_in_val = float(input("请输入实验时的流量 (SLM): "))
try:
collect_data_with_prbs(device,
q_in_val=q_in_val, dt=dt, n_order=n_order,
t_c=t_c, levels=levels, save_dir="test_data",
repeat=2)
finally:
device.disconnect()
return
# 真实硬件路径:保留原有 PLC/电机 Modbus TCP/RTU 连接代码。
modbus_client = Easy521ModbusClient()
if not modbus_client.connect():
print("无法连接 PLC,退出")
return
modbus_client.start_control()
motor = MotorModbusRTUClient()
if not motor.connect():
print("电机连接失败,退出。")
return
time.sleep(1) # 增加短暂延时,等待驱动器接口就绪
if not motor.init():
print("电机初始化失败,退出。")
motor.disconnect()
return
q_in_val = float(input("请输入实验时的流量 (SLM): "))
try:
collect_data_with_prbs(modbus_client, motor,
q_in_val=q_in_val, dt=dt, n_order=n_order, t_c=t_c,
levels=levels, save_dir="test_data", repeat=2)
finally:
modbus_client.stop_control()
modbus_client.disconnect()
motor.disconnect()
if __name__ == "__main__":
main()