ADD 1.调试工具增加实时波形显示;

main
wangbo 3 weeks ago
parent b29ae5333f
commit 656376fcc5

@ -178,6 +178,7 @@ typedef struct
char buf_send[1500]; // 发送缓冲区. char buf_send[1500]; // 发送缓冲区.
char buf_recv[1500]; // 接收缓冲区. char buf_recv[1500]; // 接收缓冲区.
struct sockaddr_in server; // 服务器地址. struct sockaddr_in server; // 服务器地址.
struct sockaddr_in wave_svr;
int32_t server_ip; // server ip. int32_t server_ip; // server ip.
uint16_t server_port; // server port. uint16_t server_port; // server port.
uint8_t is_connect; // 是否连接上服务器. uint8_t is_connect; // 是否连接上服务器.

@ -64,7 +64,7 @@ enum DebugCommand
DEBUG_DEVICE_STATUS = 8, /* 查询设备状态 */ DEBUG_DEVICE_STATUS = 8, /* 查询设备状态 */
DEBUG_TIME_SET = 9, /* 对时 */ DEBUG_TIME_SET = 9, /* 对时 */
DEBUG_REBOOT = 10, /* 重启 */ DEBUG_REBOOT = 10, /* 重启 */
DEBUG_SET_WAVE_ADDRESS = 11, /* 设置波形地址 */
}; };
#define BITMAP_SAVE_FILE (1 << 0) #define BITMAP_SAVE_FILE (1 << 0)
@ -182,6 +182,12 @@ typedef struct
{ {
uint16_t index; // 应答包序号. uint16_t index; // 应答包序号.
} dbg_upgrade_ack_t; } dbg_upgrade_ack_t;
typedef struct
{
char ipv4[16]; // wave IP.
uint16_t port; // wave port.
} dbg_wave_addr_t;
/* Exported macro ------------------------------------------------------------*/ /* Exported macro ------------------------------------------------------------*/

@ -304,6 +304,17 @@ void _csg_send_data(uint8_t cmd_type, uint8_t cmd, char *pkt, int32_t len)
} }
} }
void _csg_send(int fd, char *pkt, uint16_t len, struct sockaddr_in sk_server)
{
int32_t rv = 0;
rv = sendto(fd, pkt, len, 0, (struct sockaddr*)&sk_server, sizeof(sk_server));
if (rv < 0)
{
DBG(DBG_M_PD_CSG_ERR, "Sendto return %s!\r\n", safe_strerror(errno));
usleep(50);
}
}
/* 与后台连接断开 */ /* 与后台连接断开 */
void _csg_disconnect_set(const char *message) void _csg_disconnect_set(const char *message)
{ {
@ -536,7 +547,6 @@ int32_t _csg_trend_send(pd_trend_port_t *ptrend)
uint8_t port_idx; uint8_t port_idx;
uint32_t nums; uint32_t nums;
uint32_t len; uint32_t len;
int32_t rv = 0;
int i; int i;
int fd; int fd;
if (!ch) if (!ch)
@ -609,12 +619,31 @@ int32_t _csg_trend_send(pd_trend_port_t *ptrend)
image_head->nanosecond = header->nano_sec; image_head->nanosecond = header->nano_sec;
memcpy(pkt + sizeof(csg_pkt_head_t) + sizeof(csg_real_image_t), data_point, image_head->record_points*sizeof(int16_t)); memcpy(pkt + sizeof(csg_pkt_head_t) + sizeof(csg_real_image_t), data_point, image_head->record_points*sizeof(int16_t));
rv = sendto(fd, pkt, pkt_head->len, 0, (struct sockaddr*)&csg.server, sizeof(csg.server)); //rv = sendto(fd, pkt, pkt_head->len, 0, (struct sockaddr*)&csg.server, sizeof(csg.server));
if (rv < 0) //if (rv < 0)
//{
// DBG(DBG_M_PD_CSG_ERR, "Sendto return %s!\r\n", safe_strerror(errno));
// usleep(50);
//}
struct sockaddr_in sk_server;
if (csg.wave_svr.sin_addr.s_addr == 0)
{ {
DBG(DBG_M_PD_CSG_ERR, "Sendto return %s!\r\n", safe_strerror(errno)); sk_server = csg.server;
usleep(50);
} }
else
{
sk_server = csg.wave_svr;
}
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(sk_server.sin_addr), ip_str, sizeof(ip_str));
printf("IP Address: %s\n", ip_str);
// 获取端口号使用ntohs()函数转换回主机字节序
unsigned short port = ntohs(sk_server.sin_port);
printf("Port: %d\n", port);
_csg_send(fd, pkt, pkt_head->len, sk_server);
} }
else if(csg.new_data_flag[port_idx] == TRUE) else if(csg.new_data_flag[port_idx] == TRUE)
{ {
@ -1003,7 +1032,7 @@ int32_t _csg_config_set_recv(char *pkt)
pd_config.config.ch_en_mask = config->ch_en_mask; pd_config.config.ch_en_mask = config->ch_en_mask;
if(config->sync_mode < 4) if(config->sync_mode < 4)
{ {
pd_config.config.pt_B_sync_mode = config.sync_mode; pd_config.config.pt_B_sync_mode = config->sync_mode;
} }
pd_config.config.pt_internal_period = 1000000000UL / config->pt_internal_period; pd_config.config.pt_internal_period = 1000000000UL / config->pt_internal_period;
vtysh_config_save(); vtysh_config_save();

@ -382,6 +382,20 @@ int32_t _debug_pkt_status_get(char *buf, int len)
return E_NONE; return E_NONE;
} }
/* 获取运行状态报文处理. */
int32_t _debug_pkt_set_wave_address(char *buf, int len)
{
dbg_wave_addr_t *wave = (dbg_wave_addr_t*)(buf + sizeof(dbg_pkt_head_t));
bzero(&csg.wave_svr, sizeof(csg.wave_svr));
csg.wave_svr.sin_family = AF_INET;
csg.wave_svr.sin_addr.s_addr = inet_addr(wave->ipv4);
csg.wave_svr.sin_port = htons(wave->port);
_debug_pkt_common_send(buf, DEBUG_SET_WAVE_ADDRESS, 0);
return E_NONE;
}
int32_t _debug_pkt_upgrade(char *pkt, int len) int32_t _debug_pkt_upgrade(char *pkt, int len)
{ {
static int fd = -1; static int fd = -1;
@ -534,6 +548,9 @@ int32_t _debug_pkt_process(char *buf, int32_t len)
case DEBUG_UPGRADE_DATA: case DEBUG_UPGRADE_DATA:
_debug_pkt_upgrade(buf, len); _debug_pkt_upgrade(buf, len);
break; break;
case DEBUG_SET_WAVE_ADDRESS:
_debug_pkt_set_wave_address( buf, len);
break;
default: default:
DBG(DBG_M_DEBUG, "Debug not support cmd:%x\n", head->cmd); DBG(DBG_M_DEBUG, "Debug not support cmd:%x\n", head->cmd);
break; break;

@ -18,4 +18,5 @@ class Cmd:
UPGRADE_DATA = 0x07 UPGRADE_DATA = 0x07
STATE_GET = 0x08 # 获取状态命令 STATE_GET = 0x08 # 获取状态命令
TIME_SYNC = 0x09 # 对时命令 TIME_SYNC = 0x09 # 对时命令
REBOOT_DEVICE = 0x0A # 重启命令 REBOOT_DEVICE = 0x0A # 重启命令
SET_WAVE_ADDRESS = 0x0B # 设置波形接收地址

@ -7,6 +7,7 @@ from pages.global_params import GlobalParamsPage
from pages.port_params import PortParamsPage from pages.port_params import PortParamsPage
from pages.upgrade import UpgradePage from pages.upgrade import UpgradePage
from pages.status_page import StatusPage from pages.status_page import StatusPage
from pages.waveform_page import WaveformPage
from tcp_client import TCPClient from tcp_client import TCPClient
@ -61,6 +62,9 @@ class DeviceTool:
self.status_page = StatusPage(self.notebook, self.tcp_client) self.status_page = StatusPage(self.notebook, self.tcp_client)
self.notebook.add(self.status_page, text="实时状态") self.notebook.add(self.status_page, text="实时状态")
self.waveform_page = WaveformPage(self.notebook, self.tcp_client)
self.notebook.add(self.waveform_page, text="实时波形")
# 可以继续添加其他页面... # 可以继续添加其他页面...
def toggle_connection(self): def toggle_connection(self):

@ -0,0 +1,369 @@
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

@ -224,6 +224,25 @@ class DebugPktState:
raise ValueError("Data too short for state data") raise ValueError("Data too short for state data")
return cls(*struct.unpack(cls.FORMAT, data[:cls.SIZE])) return cls(*struct.unpack(cls.FORMAT, data[:cls.SIZE]))
@dataclass
class DebugWaveAddress:
wave_ipv4: bytes = bytes(16)
wave_port: int = 0
FORMAT = '<16s H'
SIZE = struct.calcsize(FORMAT)
def to_bytes(self):
return struct.pack(
self.FORMAT,
self.wave_ipv4, self.wave_port)
@classmethod
def from_bytes(cls, data):
if len(data) < cls.SIZE:
raise ValueError("Data too short for wave address data")
return cls(*struct.unpack(cls.FORMAT, data[:cls.SIZE]))
# 工具函数 # 工具函数
def int_to_ip(ip_int): def int_to_ip(ip_int):
return f"{ip_int & 0xFF}.{(ip_int >> 8) & 0xFF}.{(ip_int >> 16) & 0xFF}.{(ip_int >> 24) & 0xFF}" return f"{ip_int & 0xFF}.{(ip_int >> 8) & 0xFF}.{(ip_int >> 16) & 0xFF}.{(ip_int >> 24) & 0xFF}"
@ -238,6 +257,49 @@ def bytes_to_mac(mac_bytes):
return ':'.join(f'{b:02x}' for b in mac_bytes) return ':'.join(f'{b:02x}' for b in mac_bytes)
@dataclass
class CsgRealImage:
channel_id: int = 0 # 通道编号
reserved1: int = 0 # 保留
channel_type: int = 0 # 通道类型 (1: UHF)
unit: int = 0 # 单位 (1: dBm; 2: mV)
sample_rate: int = 0 # 采样率 (Msps)
record_points: int = 0 # 采样点数
century_second: int = 0 # 采样时间世纪秒
nanosecond: int = 0 # 纳秒
cycle_count: int = 0 # 周期数
frequency: int = 0 # 频率
noise: int = 0 # 噪声
sampling_time: int = 0 # 采样时长
reserved3: bytes = bytes(20) # 预留
FORMAT = '<B B B B H H I I h h h h 20s'
SIZE = struct.calcsize(FORMAT)
def to_bytes(self):
return struct.pack(
self.FORMAT,
self.channel_id,
self.reserved1,
self.channel_type,
self.unit,
self.sample_rate,
self.record_points,
self.century_second,
self.nanosecond,
self.cycle_count,
self.frequency,
self.noise,
self.sampling_time,
self.reserved3
)
@classmethod
def from_bytes(cls, data):
if len(data) < cls.SIZE:
raise ValueError("Data too short for real image header")
return cls(*struct.unpack(cls.FORMAT, data[:cls.SIZE]))
class Protocol: class Protocol:
TAIL_SIZE = 2 TAIL_SIZE = 2

Loading…
Cancel
Save