明白了,我们需要确保当接收到的数据超过20个字节时,超出部分在循环结束时也进行显示,而不是保留在 buffer 中。为了实现这一点,我们可以在每次累积到20个字节时立即显示数据,并且在循环结束时将剩余的任何数据也显示出来。
以下是更新后的完整代码示例
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=3, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar()
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
主要改动点
修改 check_queue 方法:
使用一个 while 循环来处理累积的数据 (buffer),直到其长度小于20个字节。
每次累积到20个字节时,调用 display_received_data 显示前20个字节,并从 buffer 中移除这20个字节。
在循环结束后,如果有剩余数据(少于20个字节),则立即显示这些数据。
具体逻辑解释
累积数据:从队列中取出所有数据并累积到 buffer 中。
处理累积数据:使用 while 循环不断检查 buffer 的长度,如果长度大于等于20,则提取前20个字节并调用 display_received_data 显示这些数据,然后从 buffer 中移除这20个字节。
剩余数据处理:在 while 循环结束后,如果有剩余数据(少于20个字节),也会调用 display_received_data 显示这些数据。
好的,我们可以在现有的应用程序中增加一个名为“读取距离”的按钮。当点击该按钮时,程序将以十六进制格式发送指定的数据(01 03 10 00 00 02 C0 CB),并显示发送和接收到的数据。
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar(value=True) # 默认勾选十六进制发送
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 创建读取距离按钮
self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED)
self.read_distance_button.grid(row=6, column=1, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_distance(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB"
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_distance_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
主要改动点
新增“读取距离”按钮:
在第6行 (row=6) 的 Checkbutton 右侧添加了一个新的按钮 read_distance_button,用于发送特定的十六进制命令。
使用 self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED) 创建按钮,并将其放置在网格布局中。
实现 read_distance 方法:
当点击“读取距离”按钮时,会调用 read_distance 方法。
该方法将特定的十六进制命令(01 03 10 00 00 02 C0 CB)转换为字节并发送到串口。
同时,它会在接收数据显示区域显示发送的命令。
更新 update_buttons_state 方法:
确保“读取距离”按钮的状态与串口的打开/关闭状态同步。当串口关闭时,按钮应禁用;当串口打开时,按钮应启用。
使用方法
运行程序:启动应用程序后,接收数据显示区域会显示在窗口的上部,其他控件则显示在窗口的下部。
选择串口和波特率:
选择要使用的串口和波特率(默认为 9600)。
如果需要刷新可用串口,请点击“刷新”按钮。
打开串口:点击“打开”按钮以打开串口。
点击“读取距离”按钮:
当串口打开后,点击“读取距离”按钮。
这将发送特定的十六进制命令(01 03 10 00 00 02 C0 CB),并在接收数据显示区域显示发送的命令。
接收到的数据也会在接收数据显示区域显示出来。
发送自定义数据:
在“发送数据”文本框中输入要发送的数据。
如果需要以十六进制格式发送数据,请确保勾选“十六进制发送”复选框。
点击“发送”按钮发送数据。
接收数据显示:
接收到的数据会在累积到20个字节时一次性显示,或者在接收到少于20个字节时立即显示。
如果勾选了“十六进制显示”,则数据将以十六进制格式显示;否则,数据将被解码为字符串并显示。
滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。
明白了,您希望将“读取温度”按钮发送的命令更改为与“读取距离”按钮相同的十六进制命令 01 03 10 00 00 02 C0 CB。下面是更新后的代码,其中“读取温度”按钮的功能已经修改为发送相同的命令。
更新后的完整代码示例
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar(value=True) # 默认勾选十六进制发送
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 创建读取距离按钮
self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED)
self.read_distance_button.grid(row=6, column=1, padx=5, pady=5, sticky='w')
# 创建读取温度按钮
self.read_temperature_button = tk.Button(root, text="读取温度", command=self.read_temperature, state=tk.DISABLED)
self.read_temperature_button.grid(row=6, column=2, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_distance(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB"
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_temperature(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB" # 使用与读取距离相同的命令
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_distance_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_temperature_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
运行程序:启动应用程序后,接收数据显示区域会显示在窗口的上部,其他控件则显示在窗口的下部。
选择串口和波特率:
选择要使用的串口和波特率(默认为 9600)。
如果需要刷新可用串口,请点击“刷新”按钮。
打开串口:点击“打开”按钮以打开串口。
点击“读取距离”或“读取温度”按钮:
当串口打开后,点击“读取距离”或“读取温度”按钮。
这两个按钮现在都发送相同的十六进制命令(01 03 10 00 00 02 C0 CB),并在接收数据显示区域显示发送的命令。
接收到的数据也会在接收数据显示区域显示出来。
发送自定义数据:
在“发送数据”文本框中输入要发送的数据。
如果需要以十六进制格式发送数据,请确保勾选“十六进制发送”复选框。
点击“发送”按钮发送数据。
接收数据显示:
接收到的数据会在累积到20个字节时一次性显示,或者在接收到少于20个字节时立即显示。
如果勾选了“十六进制显示”,则数据将以十六进制格式显示;否则,数据将被解码为字符串并显示。
滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。
为了在点击“读取距离”按钮后显示接收到的数据的第5和第6字节,并将其转换为十进制格式(单位为毫米),我们需要做以下几件事:
添加一个新的显示窗口:用于显示从接收到的数据中提取并转换后的距离值。
修改 read_distance 方法:在发送命令后,解析接收到的数据,并更新新的显示窗口。
处理接收到的数据:确保正确地解析数据,并将特定字节转换为十进制数值。
下面是更新后的完整代码示例,其中包含了一个新的标签用于显示读取的距离值:
更新后的完整代码示例
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar(value=True) # 默认勾选十六进制发送
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 创建读取距离按钮和显示窗口
self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED)
self.read_distance_button.grid(row=6, column=1, padx=5, pady=5, sticky='w')
self.distance_label = tk.Label(root, text="距离: - mm")
self.distance_label.grid(row=6, column=2, padx=5, pady=5, sticky='w')
# 创建读取温度按钮
self.read_temperature_button = tk.Button(root, text="读取温度", command=self.read_temperature, state=tk.DISABLED)
self.read_temperature_button.grid(row=7, column=1, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_distance(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB"
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 清空距离显示
self.distance_label.config(text="距离: - mm")
def read_temperature(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB" # 使用与读取距离相同的命令
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
# 解析距离数据并更新显示
if len(data) >= 6:
distance_bytes = data[4:6]
distance_mm = int.from_bytes(distance_bytes, byteorder='big')
self.distance_label.config(text=f"距离: {distance_mm} mm")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_distance_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_temperature_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
运行程序:启动应用程序后,接收数据显示区域会显示在窗口的上部,其他控件则显示在窗口的下部。
选择串口和波特率:
选择要使用的串口和波特率(默认为 9600)。
如果需要刷新可用串口,请点击“刷新”按钮。
打开串口:点击“打开”按钮以打开串口。
点击“读取距离”按钮:
当串口打开后,点击“读取距离”按钮。
该按钮会发送特定的十六进制命令(01 03 10 00 00 02 C0 CB),并在接收数据显示区域显示发送的命令。
接收到的数据会在累积到一定长度时一次性显示,或者在接收到少于20个字节时立即显示。
接收到的数据中的第5和第6字节会被解析为距离值(单位为毫米),并更新到新的显示标签中。
发送自定义数据:
在“发送数据”文本框中输入要发送的数据。
如果需要以十六进制格式发送数据,请确保勾选“十六进制发送”复选框。
点击“发送”按钮发送数据。
接收数据显示:
接收到的数据会在累积到20个字节时一次性显示,或者在接收到少于20个字节时立即显示。
如果勾选了“十六进制显示”,则数据将以十六进制格式显示;否则,数据将被解码为字符串并显示。
滚动条会自动滚动到最新数据的位置。
关闭串口:点击“关闭”按钮关闭串口。
好的,您希望在点击“读取距离”按钮后显示接收到的数据的第4和第5字节,并将其转换为十进制数值(单位为毫米)。我们需要对之前的代码进行一些调整来实现这个需求。
主要改动点
修改解析数据的位置:将原先解析第5和第6字节的地方改为解析第4和第5字节。
更新 display_received_data 方法:确保正确提取并处理新的字节位置。
下面是更新后的
完整代码示例:
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar(value=True) # 默认勾选十六进制发送
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 创建读取距离按钮和显示窗口
self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED)
self.read_distance_button.grid(row=6, column=1, padx=5, pady=5, sticky='w')
self.distance_label = tk.Label(root, text="距离: - mm")
self.distance_label.grid(row=6, column=2, padx=5, pady=5, sticky='w')
# 创建读取温度按钮
self.read_temperature_button = tk.Button(root, text="读取温度", command=self.read_temperature, state=tk.DISABLED)
self.read_temperature_button.grid(row=7, column=1, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_distance(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB"
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 清空距离显示
self.distance_label.config(text="距离: - mm")
def read_temperature(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB" # 使用与读取距离相同的命令
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
# 解析距离数据并更新显示
if len(data) >= 5:
distance_bytes = data[3:5] # 修改为第4和第5字节
distance_mm = int.from_bytes(distance_bytes, byteorder='big')
self.distance_label.config(text=f"距离: {distance_mm} mm")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_distance_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_temperature_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x400") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
为了在点击“读取温度”按钮后显示接收到的数据的第6和第7字节,并将其转换为十进制数值(单位为摄氏度),我们需要对代码进行一些调整。具体来说,我们将添加一个新的标签用于显示温度值,并在解析接收到的数据时提取并处理第6和第7字节。
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar(value=True) # 默认勾选十六进制发送
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 创建读取距离按钮和显示窗口
self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED)
self.read_distance_button.grid(row=6, column=1, padx=5, pady=5, sticky='w')
self.distance_label = tk.Label(root, text="距离: - mm")
self.distance_label.grid(row=6, column=2, padx=5, pady=5, sticky='w')
# 创建读取温度按钮和显示窗口
self.read_temperature_button = tk.Button(root, text="读取温度", command=self.read_temperature, state=tk.DISABLED)
self.read_temperature_button.grid(row=7, column=1, padx=5, pady=5, sticky='w')
self.temperature_label = tk.Label(root, text="温度: - ℃")
self.temperature_label.grid(row=7, column=2, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_distance(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB"
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 清空距离显示
self.distance_label.config(text="距离: - mm")
def read_temperature(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB" # 使用与读取距离相同的命令
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 清空温度显示
self.temperature_label.config(text="温度: - ℃")
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
# 解析距离数据并更新显示
if len(data) >= 5:
distance_bytes = data[3:5] # 修改为第4和第5字节
distance_mm = int.from_bytes(distance_bytes, byteorder='big')
self.distance_label.config(text=f"距离: {distance_mm} mm")
# 解析温度数据并更新显示
if len(data) >= 7:
temperature_bytes = data[5:7] # 修改为第6和第7字节
temperature_celsius = int.from_bytes(temperature_bytes, byteorder='big')
self.temperature_label.config(text=f"温度: {temperature_celsius} ℃")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_distance_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_temperature_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x450") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()
为了确保只有在点击“读取距离”按钮后才显示距离,以及只有在点击“读取温度”按钮后才显示温度,我们需要对代码进行一些调整。具体来说,我们将添加标志位来跟踪用户是否点击了相应的按钮,并仅在这些标志位为真时解析并显示数据。
以下是更新后的完整代码示例:
更新后的完整代码示例
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
class SerialToolApp:
def __init__(self, root):
self.root = root
self.root.title("串口工具小助手")
# 创建接收数据显示区域(包含滚动条),并放置在窗口上部
self.receive_text_frame = tk.Frame(root)
self.receive_text_frame.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
self.receive_text = tk.Text(self.receive_text_frame, height=10, width=40)
self.receive_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.receive_text_frame, orient=tk.VERTICAL, command=self.receive_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.receive_text.config(yscrollcommand=self.scrollbar.set)
# 创建串口选择下拉框
self.port_label = tk.Label(root, text="选择串口:")
self.port_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.port_combobox = ttk.Combobox(root)
self.port_combobox.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.refresh_ports()
# 创建刷新按钮
self.refresh_button = tk.Button(root, text="刷新", command=self.refresh_ports)
self.refresh_button.grid(row=1, column=2, padx=5, pady=5, sticky='w')
# 创建波特率选择下拉框,默认波特率为9600
self.baud_label = tk.Label(root, text="波特率:")
self.baud_label.grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.baud_combobox = ttk.Combobox(root, values=[9600, 19200, 38400, 57600, 115200])
self.baud_combobox.current(0) # 默认选择9600
self.baud_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='w')
# 创建打开/关闭按钮
self.open_button = tk.Button(root, text="打开", command=self.open_serial)
self.open_button.grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.close_button = tk.Button(root, text="关闭", command=self.close_serial, state=tk.DISABLED)
self.close_button.grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 创建发送数据输入框和发送按钮
self.send_label = tk.Label(root, text="发送数据:")
self.send_label.grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.send_entry = tk.Entry(root)
self.send_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制发送选择框
self.hex_send_var = tk.BooleanVar(value=True) # 默认勾选十六进制发送
self.hex_send_checkbutton = tk.Checkbutton(root, text="十六进制发送", variable=self.hex_send_var)
self.hex_send_checkbutton.grid(row=5, column=0, padx=5, pady=5, sticky='e')
# 创建发送按钮
self.send_button = tk.Button(root, text="发送", command=self.send_data, state=tk.DISABLED)
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky='w')
# 创建十六进制显示选择框
self.hex_receive_var = tk.BooleanVar()
self.hex_receive_checkbutton = tk.Checkbutton(root, text="十六进制显示", variable=self.hex_receive_var)
self.hex_receive_checkbutton.grid(row=6, column=0, padx=5, pady=5, sticky='e')
# 创建读取距离按钮和显示窗口
self.read_distance_button = tk.Button(root, text="读取距离", command=self.read_distance, state=tk.DISABLED)
self.read_distance_button.grid(row=6, column=1, padx=5, pady=5, sticky='w')
self.distance_label = tk.Label(root, text="距离: - mm")
self.distance_label.grid(row=6, column=2, padx=5, pady=5, sticky='w')
# 创建读取温度按钮和显示窗口
self.read_temperature_button = tk.Button(root, text="读取温度", command=self.read_temperature, state=tk.DISABLED)
self.read_temperature_button.grid(row=7, column=1, padx=5, pady=5, sticky='w')
self.temperature_label = tk.Label(root, text="温度: - ℃")
self.temperature_label.grid(row=7, column=2, padx=5, pady=5, sticky='w')
# 初始化串口对象和队列
self.ser = None
self.read_queue = queue.Queue()
self.read_thread = None
# 标志位
self.read_distance_flag == False
self.read_temperature_flag/XMLSchemaValidator == False
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combobox['values'] = ports
if ports:
self.port_combobox.current(0)
else:
self.port_combobox.set('')
def open_serial(self):
"""打开串口"""
try:
port = self.port_combobox.get()
baudrate = int(self.baud_combobox.get())
self.ser = serial.Serial(port, baudrate, timeout=1)
self.update_buttons_state(False)
self.receive_text.insert(tk.END, f"已打开串口 {port} @ {baudrate}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 启动读取线程
self.read_thread = threading.Thread(target=self.read_from_serial)
self.read_thread.daemon = True
self.read_thread.start()
# 开始定期检查队列中的数据
self.check_queue()
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
self.update_buttons_state(True)
self.receive_text.insert(tk.END, "已关闭串口\n")
self.receive_text.see(tk.END) # 自动滚动到底部
def send_data(self):
"""发送数据"""
if self.ser and self.ser.is_open:
data = self.send_entry.get()
if self.hex_send_var.get():
try:
# 将输入的十六进制字符串转换为字节
bytes_data = bytes.fromhex(data)
self.ser.write(bytes_data)
self.receive_text.insert(tk.END, f"发送 (HEX): {data}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
except ValueError:
messagebox.showerror("错误", "请输入有效的十六进制数据")
else:
# 发送普通字符串
data += '\n'
self.ser.write(data.encode())
self.receive_text.insert(tk.END, f"发送: {data}")
self.receive_text.see(tk.END) # 自动滚动到底部
def read_distance(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB"
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 设置读取距离标志位
self.read_distance_flag = True
self.read_temperature_flag = False
def read_temperature(self):
"""以十六进制发送特定命令并显示发送和接收到的数据"""
if self.ser and self.ser.is_open:
hex_command = "01 03 10 00 00 02 C0 CB" # 使用与读取距离相同的命令
bytes_command = bytes.fromhex(hex_command.replace(" ", ""))
self.ser.write(bytes_command)
self.receive_text.insert(tk.END, f"发送 (HEX): {hex_command}\n")
self.receive_text.see(tk.END) # 自动滚动到底部
# 设置读取温度标志位
self.read_temperature_flag = True
self.read_distance_flag = False
def read_from_serial(self):
"""从串口读取数据并放入队列"""
while self.ser and self.ser.is_open:
try:
data = self.ser.read(self.ser.in_waiting or 1)
if data:
self.read_queue.put(data)
except serial.SerialException as e:
messagebox.showerror("错误", str(e))
break
def check_queue(self):
"""检查队列中的数据并更新显示"""
buffer = b""
while not self.read_queue.empty():
data = self.read_queue.get()
buffer += data
while len(buffer) >= 20:
self.display_received_data(buffer[:20])
buffer = buffer[20:]
if buffer:
self.display_received_data(buffer)
# 定期调用此方法以持续检查队列
self.root.after(100, self.check_queue)
def display_received_data(self, data):
"""根据设置显示接收到的数据"""
if self.hex_receive_var.get():
# 十六进制显示
hex_data = ' '.join([f'{byte:02X}' for byte in data])
self.receive_text.insert(tk.END, f"接收 (HEX): {hex_data}\n")
else:
# 普通字符串显示
self.receive_text.insert(tk.END, f"接收: {data.decode('utf-8', errors='replace')}")
self.receive_text.see(tk.END) # 自动滚动到底部
# 解析距离数据并更新显示
if self.read_distance_flag and len(data) >= 5:
distance_bytes = data[3:5] # 修改为第4和第5字节
distance_mm = int.from_bytes(distance_bytes, byteorder='big')
self.distance_label.config(text=f"距离: {distance_mm} mm")
# 解析温度数据并更新显示
if self.read_temperature_flag and len(data) >= 7:
temperature_bytes = data[5:7] # 修改为第6和第7字节
temperature_celsius = int.from_bytes(temperature_bytes, byteorder='big')
self.temperature_label.config(text=f"温度: {temperature_celsius} ℃")
def update_buttons_state(self, is_open):
"""更新按钮状态"""
self.open_button.config(state=tk.NORMAL if is_open else tk.DISABLED)
self.close_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.send_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_distance_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
self.read_temperature_button.config(state=tk.DISABLED if is_open else tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("400x450") # 设置初始窗口大小
app = SerialToolApp(root)
root.mainloop()