|
|
import tkinter as tk
|
|
|
from tkinter import ttk
|
|
|
import socket
|
|
|
import threading
|
|
|
import struct
|
|
|
import numpy as np
|
|
|
from matplotlib.figure import Figure
|
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
from protocol import DbgGlobalConfig, DebugWaveAddress
|
|
|
from .base_page import BasePage
|
|
|
from config import Cmd
|
|
|
|
|
|
|
|
|
class WaveformPage(BasePage):
|
|
|
def __init__(self, parent, tcp_client):
|
|
|
# 先初始化 WaveformPage 特有的属性
|
|
|
self.udp_socket = None
|
|
|
self.udp_thread = None
|
|
|
self.udp_running = False
|
|
|
self.udp_port = 10100
|
|
|
|
|
|
# 波形数据
|
|
|
self.waveform_data = {}
|
|
|
self.latest_timestamps = {}
|
|
|
|
|
|
# 显示配置
|
|
|
self.sample_points = 10000
|
|
|
self.sample_duration = 2.0
|
|
|
self.time_axis = np.linspace(0, self.sample_duration, self.sample_points)
|
|
|
|
|
|
# 纵轴配置
|
|
|
self.y_min = -30
|
|
|
self.y_max = 60
|
|
|
self.y_precision = 10
|
|
|
|
|
|
# 通道配置
|
|
|
self.channels = list(range(1, 9))
|
|
|
self.channel_vars = {}
|
|
|
self.channel_colors = plt.cm.Set1(np.linspace(0, 1, 8))
|
|
|
|
|
|
# 现在调用父类构造函数
|
|
|
super().__init__(parent, tcp_client, "实时波形")
|
|
|
|
|
|
tcp_client.register_callback(Cmd.SET_WAVE_ADDRESS, self.on_set_response)
|
|
|
|
|
|
def create_widgets(self):
|
|
|
"""创建界面控件"""
|
|
|
# 主框架
|
|
|
main_frame = ttk.Frame(self)
|
|
|
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
|
|
|
|
|
|
# 控制面板
|
|
|
control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding=10)
|
|
|
control_frame.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
# 通道选择
|
|
|
channel_frame = ttk.Frame(control_frame)
|
|
|
channel_frame.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
ttk.Label(channel_frame, text="通道选择:").pack(side=tk.LEFT)
|
|
|
|
|
|
# 创建8个通道的复选框
|
|
|
for i, channel in enumerate(self.channels):
|
|
|
var = tk.BooleanVar(value=True)
|
|
|
self.channel_vars[channel] = var
|
|
|
|
|
|
check = ttk.Checkbutton(channel_frame, text=f"通道{channel}",
|
|
|
variable=var, command=self.update_plot)
|
|
|
check.pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
# 控制按钮
|
|
|
button_frame = ttk.Frame(control_frame)
|
|
|
button_frame.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
ttk.Button(button_frame, text="开始接收", command=self.set_data, width=12).pack(side=tk.LEFT, padx=2)
|
|
|
# ttk.Button(button_frame, text="开始接收", command=self.start_udp, width=12).pack(side=tk.LEFT, padx=2)
|
|
|
ttk.Button(button_frame, text="停止接收", command=self.stop_udp, width=12).pack(side=tk.LEFT, padx=2)
|
|
|
ttk.Button(button_frame, text="清空数据", command=self.clear_data, width=12).pack(side=tk.LEFT, padx=2)
|
|
|
ttk.Button(button_frame, text="自动缩放", command=self.auto_scale, width=12).pack(side=tk.LEFT, padx=2)
|
|
|
|
|
|
# 状态显示
|
|
|
self.status_var = tk.StringVar(value="就绪")
|
|
|
ttk.Label(control_frame, textvariable=self.status_var).pack(side=tk.RIGHT)
|
|
|
|
|
|
# 波形显示区域
|
|
|
plot_frame = ttk.Frame(main_frame)
|
|
|
plot_frame.pack(fill=tk.BOTH, expand=True, pady=5)
|
|
|
|
|
|
# 创建 matplotlib 图形
|
|
|
self.setup_plot(plot_frame)
|
|
|
self.setup_udp()
|
|
|
|
|
|
def setup_plot(self, parent):
|
|
|
"""设置波形图"""
|
|
|
# 创建图形和坐标轴
|
|
|
self.fig = Figure(figsize=(10, 6), dpi=100)
|
|
|
self.ax = self.fig.add_subplot(111)
|
|
|
|
|
|
# 设置坐标轴标签
|
|
|
self.ax.set_xlabel('Time (us)')
|
|
|
self.ax.set_ylabel('Amplitude(mV)')
|
|
|
self.ax.set_title('Real-time Waveform Display')
|
|
|
|
|
|
# 设置坐标轴范围
|
|
|
self.ax.set_xlim(0, self.sample_duration)
|
|
|
self.ax.set_ylim(self.y_min, self.y_max)
|
|
|
|
|
|
# 设置坐标轴刻度
|
|
|
# 主刻度:每0.1us显示数字标签
|
|
|
x_major_ticks = np.arange(0, self.sample_duration + 0.1, 0.1)
|
|
|
# 次刻度:每0.05us,只显示网格线不显示数字
|
|
|
x_minor_ticks = np.arange(0, self.sample_duration + 0.05, 0.05)
|
|
|
|
|
|
# 纵轴刻度
|
|
|
y_major_ticks = np.arange(self.y_min, self.y_max + 1, self.y_precision) # 每10mV主刻度
|
|
|
y_minor_ticks = np.arange(self.y_min, self.y_max + 1, 2) # 每2mV次刻度
|
|
|
|
|
|
self.ax.set_xticks(x_major_ticks)
|
|
|
self.ax.set_xticks(x_minor_ticks, minor=True)
|
|
|
self.ax.set_yticks(y_major_ticks)
|
|
|
self.ax.set_yticks(y_minor_ticks, minor=True)
|
|
|
|
|
|
# 设置网格线
|
|
|
# 主网格线:0.1us间隔,颜色较深
|
|
|
self.ax.grid(True, which='major', alpha=0.4, linestyle='-', linewidth=0.8)
|
|
|
# 次网格线:0.05us间隔和2mV间隔,颜色较浅
|
|
|
self.ax.grid(True, which='minor', alpha=0.2, linestyle='--', linewidth=0.5)
|
|
|
|
|
|
# 创建画布
|
|
|
self.canvas = FigureCanvasTkAgg(self.fig, parent)
|
|
|
self.canvas.draw()
|
|
|
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
|
# 初始化线条对象
|
|
|
self.lines = {}
|
|
|
for channel in self.channels:
|
|
|
line, = self.ax.plot([], [],
|
|
|
color=self.channel_colors[channel - 1],
|
|
|
linewidth=1,
|
|
|
label=f'通道{channel}')
|
|
|
self.lines[channel] = line
|
|
|
|
|
|
# 添加图例
|
|
|
self.ax.legend(loc='upper right')
|
|
|
|
|
|
def setup_udp(self):
|
|
|
"""设置UDP socket"""
|
|
|
try:
|
|
|
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024 * 1024) # 1MB缓冲区
|
|
|
self.udp_socket.bind(('0.0.0.0', self.udp_port))
|
|
|
self.udp_socket.settimeout(1.0) # 设置超时以便可以检查停止标志
|
|
|
self.status_var.set(f"UDP监听端口: {self.udp_port}")
|
|
|
except Exception as e:
|
|
|
self.status_var.set(f"UDP设置失败: {e}")
|
|
|
|
|
|
def set_data(self):
|
|
|
"""设置波形接收地址"""
|
|
|
if not self.tcp_client.connected:
|
|
|
self.show_error("未连接设备")
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
addr = DebugWaveAddress()
|
|
|
|
|
|
addr.wave_ipv4 = bytes(self.get_local_ip(), encoding='utf-8')
|
|
|
addr.wave_port = 10100
|
|
|
|
|
|
print(f"wave_ipv4: {addr.wave_ipv4} wave_port:{addr.wave_port}")
|
|
|
|
|
|
print(f"发送命令: {Cmd.SET_WAVE_ADDRESS}")
|
|
|
if not self.tcp_client.connected:
|
|
|
self.show_error("未连接设备")
|
|
|
return
|
|
|
|
|
|
self.tcp_client.send_packet(Cmd.SET_WAVE_ADDRESS, addr.to_bytes())
|
|
|
|
|
|
except ValueError as e:
|
|
|
self.show_error(f"参数格式错误: {e}")
|
|
|
except Exception as e:
|
|
|
self.show_error(f"设置失败: {e}")
|
|
|
|
|
|
def start_udp(self):
|
|
|
"""开始接收UDP数据"""
|
|
|
|
|
|
if self.udp_running:
|
|
|
return
|
|
|
|
|
|
self.udp_running = True
|
|
|
self.udp_thread = threading.Thread(target=self.udp_receive_loop, daemon=True)
|
|
|
self.udp_thread.start()
|
|
|
self.status_var.set("正在接收波形数据...")
|
|
|
|
|
|
def stop_udp(self):
|
|
|
"""停止接收UDP数据"""
|
|
|
self.udp_running = False
|
|
|
if self.udp_thread and self.udp_thread.is_alive():
|
|
|
self.udp_thread.join(timeout=2.0)
|
|
|
self.status_var.set("已停止接收")
|
|
|
|
|
|
def udp_receive_loop(self):
|
|
|
"""UDP数据接收循环"""
|
|
|
buffer = b''
|
|
|
|
|
|
while self.udp_running:
|
|
|
try:
|
|
|
data, addr = self.udp_socket.recvfrom(65536) # 最大接收64KB
|
|
|
print(f"接收到来自 {addr} 的数据:")
|
|
|
print(f"原始数据: {data}")
|
|
|
print(f"数据长度: {len(data)} 字节")
|
|
|
buffer += data
|
|
|
|
|
|
# 数据头大小(前32字节)
|
|
|
data_header_size = 32
|
|
|
|
|
|
# 波形头部格式(csg_real_image_t 结构体)
|
|
|
wave_header_format = '<B B B B H H H h H H I H H H H H H 2s i f f i h f f I I h h h h 20s'
|
|
|
wave_header_size = struct.calcsize(wave_header_format)
|
|
|
|
|
|
# 总头部大小 = 数据头 + 波形头
|
|
|
total_header_size = data_header_size + wave_header_size
|
|
|
|
|
|
while len(buffer) >= total_header_size:
|
|
|
# 1. 提取数据头(前32字节)
|
|
|
data_header = buffer[:data_header_size]
|
|
|
print(f"数据头: {data_header.hex()}")
|
|
|
|
|
|
# 2. 提取波形头部(接下来的wave_header_size字节)
|
|
|
wave_header_start = data_header_size
|
|
|
wave_header_end = data_header_size + wave_header_size
|
|
|
wave_header_data = buffer[wave_header_start:wave_header_end]
|
|
|
|
|
|
# 3. 解析波形头部
|
|
|
(
|
|
|
channel_id, reserved1, channel_type, unit,
|
|
|
sample_rate, record_points, phase, pulse_peak,
|
|
|
pre_trigger, trigger_level, filter_frequency,
|
|
|
rise_time, peak_time, fall_time, pulse_width, peak_count,
|
|
|
reserved2, signal_envelope_area, signal_mean, signal_variance,
|
|
|
first_main_freq, first_main_freq_peak, spectral_peak_count,
|
|
|
spectral_mean, spectral_variance, century_second, nanosecond,
|
|
|
cycle_count, frequency, noise, sampling_time, reserved3
|
|
|
) = struct.unpack(wave_header_format, wave_header_data)
|
|
|
|
|
|
# 4. 计算波形数据大小
|
|
|
waveform_size = record_points * 2 # 每个点2字节
|
|
|
total_packet_size = total_header_size + waveform_size
|
|
|
|
|
|
if len(buffer) < total_packet_size:
|
|
|
break # 数据不完整,等待更多数据
|
|
|
|
|
|
# 5. 提取波形数据(最后的部分)
|
|
|
waveform_start = total_header_size
|
|
|
waveform_end = total_header_size + waveform_size
|
|
|
waveform_bytes = buffer[waveform_start:waveform_end]
|
|
|
|
|
|
# 6. 解析波形数据 (int16数组)
|
|
|
waveform_data = np.frombuffer(waveform_bytes, dtype=np.int16)
|
|
|
|
|
|
# 打印前100个原始值
|
|
|
print("前100个原始波形数据值:")
|
|
|
for i in range(min(100, len(waveform_data))):
|
|
|
print(f"索引 {i:3d}: {waveform_data[i]:6d}")
|
|
|
|
|
|
# 7. 直接使用接收到的mV值,转换为float32以便matplotlib处理
|
|
|
waveform_mv = waveform_data.astype(np.float32)
|
|
|
|
|
|
# 8. 限制量程范围(如果超过量程,显示最大量程)
|
|
|
# 根据当前的纵轴配置 self.y_min 和 self.y_max
|
|
|
waveform_mv = np.clip(waveform_mv, self.y_min, self.y_max)
|
|
|
|
|
|
# 9. 更新数据
|
|
|
self.waveform_data[channel_id] = waveform_mv
|
|
|
self.latest_timestamps[channel_id] = (century_second, nanosecond)
|
|
|
|
|
|
# 10. 打印调试信息
|
|
|
print(f"通道{channel_id}: {record_points}个采样点, 采样率{sample_rate}Msps")
|
|
|
print(f"时间: {century_second}.{nanosecond:09d}")
|
|
|
print(f"脉冲峰值: {pulse_peak}mV, 频率: {frequency}Hz")
|
|
|
print(f"波形数据点数: {len(waveform_data)}")
|
|
|
print(f"数据范围: {np.min(waveform_data)} ~ {np.max(waveform_data)} mV")
|
|
|
|
|
|
# 11. 更新显示
|
|
|
self.update_plot()
|
|
|
|
|
|
# 12. 从缓冲区移除已处理的数据
|
|
|
buffer = buffer[total_packet_size:]
|
|
|
|
|
|
except socket.timeout:
|
|
|
continue # 超时是正常的,继续循环
|
|
|
except Exception as e:
|
|
|
print(f"UDP接收错误: {e}")
|
|
|
import traceback
|
|
|
traceback.print_exc()
|
|
|
continue
|
|
|
|
|
|
def update_plot(self):
|
|
|
"""更新波形显示"""
|
|
|
if not hasattr(self, 'ax'):
|
|
|
return
|
|
|
|
|
|
# 清除之前的线条数据
|
|
|
for channel in self.channels:
|
|
|
self.lines[channel].set_data([], [])
|
|
|
|
|
|
# 更新选中的通道数据
|
|
|
has_data = False
|
|
|
for channel in self.channels:
|
|
|
if (self.channel_vars[channel].get() and
|
|
|
channel in self.waveform_data and
|
|
|
len(self.waveform_data[channel]) == self.sample_points):
|
|
|
waveform = self.waveform_data[channel]
|
|
|
self.lines[channel].set_data(self.time_axis, waveform)
|
|
|
has_data = True
|
|
|
|
|
|
# 如果有数据显示,更新图形
|
|
|
if has_data:
|
|
|
self.ax.relim()
|
|
|
self.ax.autoscale_view()
|
|
|
self.canvas.draw_idle()
|
|
|
|
|
|
def auto_scale(self):
|
|
|
"""自动缩放坐标轴"""
|
|
|
if hasattr(self, 'ax'):
|
|
|
self.ax.set_xlim(0, self.sample_duration)
|
|
|
self.ax.set_ylim(self.y_min, self.y_max)
|
|
|
self.canvas.draw_idle()
|
|
|
|
|
|
def clear_data(self):
|
|
|
"""清空波形数据"""
|
|
|
self.waveform_data.clear()
|
|
|
self.latest_timestamps.clear()
|
|
|
self.update_plot()
|
|
|
self.status_var.set("数据已清空")
|
|
|
|
|
|
def get_channel_info(self, channel_id):
|
|
|
"""获取通道信息字符串"""
|
|
|
if channel_id in self.latest_timestamps:
|
|
|
century_second, nanosecond = self.latest_timestamps[channel_id]
|
|
|
return f"通道{channel_id} - 时间: {century_second}.{nanosecond:09d}"
|
|
|
return f"通道{channel_id} - 无数据"
|
|
|
|
|
|
def on_close(self):
|
|
|
"""页面关闭时的清理工作"""
|
|
|
self.stop_udp()
|
|
|
if self.udp_socket:
|
|
|
self.udp_socket.close()
|
|
|
|
|
|
def on_set_response(self, header, body):
|
|
|
"""处理设置响应"""
|
|
|
self.show_info("波形地址设置成功")
|
|
|
self.start_udp()
|
|
|
|
|
|
def ip_to_int(self, ip):
|
|
|
parts = ip.split('.')
|
|
|
int_ip = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
|
|
|
return int_ip
|
|
|
|
|
|
def ipv4_to_int(self, ipv4_address):
|
|
|
|
|
|
packed_ip = socket.inet_aton(ipv4_address)
|
|
|
|
|
|
return struct.unpack("!L", packed_ip)[0]
|
|
|
|
|
|
def get_local_ip(self):
|
|
|
ip = socket.gethostbyname(socket.gethostname())
|
|
|
return ip |