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 : 日志回调,默认 print(GUI 可传入 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()