You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

369 lines
14 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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