update server
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
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
|
||||
|
||||
dt = 0.1
|
||||
n_order = 7 # 码元数 127
|
||||
t_c = 5 # 码元周期 5 秒
|
||||
levels = [10, 20, 30, 40, 50, 60, 70, 80] # 8 个电平,对应 group_bits=3
|
||||
|
||||
# 连接 PLC
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user