python写上位机并打包250824
1.python写的串口上位机软件程序
import serial
import serial.tools.list_ports
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import threading
import time
from datetime import datetime
class SerialPortAssistant:
def init(self, root):
self.root = root
self.root.title(“串口助手”)
self.root.geometry(“800x600”)
self.serial_port = Noneself.is_receiving = Falseself.receive_thread = Noneself.create_widgets()self.update_port_list()# 默认设置self.baudrate_var.set("9600")self.databits_var.set("8")self.stopbits_var.set("1")self.parity_var.set("None")self.flowcontrol_var.set("None")def create_widgets(self):# 串口设置区域settings_frame = ttk.LabelFrame(self.root, text="串口设置", padding=10)settings_frame.pack(fill=tk.X, padx=5, pady=5)# 串口号ttk.Label(settings_frame, text="串口号:").grid(row=0, column=0, sticky=tk.W)self.port_var = tk.StringVar()self.port_combobox = ttk.Combobox(settings_frame, textvariable=self.port_var, width=10)self.port_combobox.grid(row=0, column=1, sticky=tk.W)# 波特率ttk.Label(settings_frame, text="波特率:").grid(row=0, column=2, sticky=tk.W)self.baudrate_var = tk.StringVar()baudrates = ["9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600"]self.baudrate_combobox = ttk.Combobox(settings_frame, textvariable=self.baudrate_var, values=baudrates, width=10)self.baudrate_combobox.grid(row=0, column=3, sticky=tk.W)# 数据位ttk.Label(settings_frame, text="数据位:").grid(row=0, column=4, sticky=tk.W)self.databits_var = tk.StringVar()databits = ["5", "6", "7", "8"]self.databits_combobox = ttk.Combobox(settings_frame, textvariable=self.databits_var, values=databits, width=5)self.databits_combobox.grid(row=0, column=5, sticky=tk.W)# 停止位ttk.Label(settings_frame, text="停止位:").grid(row=1, column=0, sticky=tk.W)self.stopbits_var = tk.StringVar()stopbits = ["1", "1.5", "2"]self.stopbits_combobox = ttk.Combobox(settings_frame, textvariable=self.stopbits_var, values=stopbits, width=5)self.stopbits_combobox.grid(row=1, column=1, sticky=tk.W)# 校验位ttk.Label(settings_frame, text="校验位:").grid(row=1, column=2, sticky=tk.W)self.parity_var = tk.StringVar()parities = ["None", "Even", "Odd", "Mark", "Space"]self.parity_combobox = ttk.Combobox(settings_frame, textvariable=self.parity_var, values=parities, width=8)self.parity_combobox.grid(row=1, column=3, sticky=tk.W)# 流控ttk.Label(settings_frame, text="流控:").grid(row=1, column=4, sticky=tk.W)self.flowcontrol_var = tk.StringVar()flowcontrols = ["None", "Hardware (RTS/CTS)", "Software (XON/XOFF)"]self.flowcontrol_combobox = ttk.Combobox(settings_frame, textvariable=self.flowcontrol_var, values=flowcontrols, width=15)self.flowcontrol_combobox.grid(row=1, column=5, sticky=tk.W)# 刷新按钮refresh_btn = ttk.Button(settings_frame, text="刷新", command=self.update_port_list)refresh_btn.grid(row=0, column=6, padx=5)# 打开/关闭按钮self.connect_btn = ttk.Button(settings_frame, text="打开串口", command=self.toggle_connection)self.connect_btn.grid(row=1, column=6, padx=5)# 发送设置区域send_settings_frame = ttk.LabelFrame(self.root, text="发送设置", padding=10)send_settings_frame.pack(fill=tk.X, padx=5, pady=5)# 发送格式ttk.Label(send_settings_frame, text="发送格式:").grid(row=0, column=0, sticky=tk.W)self.send_format_var = tk.StringVar(value="ASCII")ttk.Radiobutton(send_settings_frame, text="ASCII", variable=self.send_format_var, value="ASCII").grid(row=0, column=1, sticky=tk.W)ttk.Radiobutton(send_settings_frame, text="Hex", variable=self.send_format_var, value="Hex").grid(row=0, column=2, sticky=tk.W)# 自动换行self.auto_newline_var = tk.IntVar(value=1)ttk.Checkbutton(send_settings_frame, text="自动添加换行", variable=self.auto_newline_var).grid(row=0, column=3, sticky=tk.W)# 定时发送ttk.Label(send_settings_frame, text="定时发送(ms):").grid(row=0, column=4, sticky=tk.W)self.timer_var = tk.StringVar()self.timer_entry = ttk.Entry(send_settings_frame, textvariable=self.timer_var, width=8)self.timer_entry.grid(row=0, column=5, sticky=tk.W)self.timer_entry.config(state=tk.DISABLED)# 接收设置区域recv_settings_frame = ttk.LabelFrame(self.root, text="接收设置", padding=10)recv_settings_frame.pack(fill=tk.X, padx=5, pady=5)# 接收格式ttk.Label(recv_settings_frame, text="接收格式:").grid(row=0, column=0, sticky=tk.W)self.recv_format_var = tk.StringVar(value="ASCII")ttk.Radiobutton(recv_settings_frame, text="ASCII", variable=self.recv_format_var, value="ASCII").grid(row=0, column=1, sticky=tk.W)ttk.Radiobutton(recv_settings_frame, text="Hex", variable=self.recv_format_var, value="Hex").grid(row=0, column=2, sticky=tk.W)# 显示设置ttk.Label(recv_settings_frame, text="显示设置:").grid(row=0, column=3, sticky=tk.W)self.display_var = tk.StringVar(value="显示时间")ttk.Checkbutton(recv_settings_frame, text="显示时间", variable=self.display_var, onvalue="显示时间", offvalue="").grid(row=0, column=4, sticky=tk.W)# 保存接收数据save_btn = ttk.Button(recv_settings_frame, text="保存接收数据", command=self.save_received_data)save_btn.grid(row=0, column=5, padx=5)# 清除接收区clear_btn = ttk.Button(recv_settings_frame, text="清除接收区", command=self.clear_received_data)clear_btn.grid(row=0, column=6, padx=5)# 发送区域send_frame = ttk.LabelFrame(self.root, text="发送区", padding=10)send_frame.pack(fill=tk.X, padx=5, pady=5)self.send_text = tk.Text(send_frame, height=5)self.send_text.pack(fill=tk.X, expand=True)# 发送按钮send_btn_frame = ttk.Frame(send_frame)send_btn_frame.pack(fill=tk.X, pady=5)self.send_btn = ttk.Button(send_btn_frame, text="发送", command=self.send_data)self.send_btn.pack(side=tk.LEFT, padx=5)self.timer_send_btn = ttk.Button(send_btn_frame, text="启动定时发送", command=self.toggle_timer_send)self.timer_send_btn.pack(side=tk.LEFT, padx=5)self.timer_send_btn.config(state=tk.DISABLED)# 接收区域recv_frame = ttk.LabelFrame(self.root, text="接收区", padding=10)recv_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)self.recv_text = scrolledtext.ScrolledText(recv_frame, height=15)self.recv_text.pack(fill=tk.BOTH, expand=True)# 状态栏self.status_var = tk.StringVar()self.status_var.set("就绪")status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN)status_bar.pack(fill=tk.X, padx=5, pady=5)def update_port_list(self):"""更新可用串口列表"""ports = serial.tools.list_ports.comports()port_list = [port.device for port in ports]self.port_combobox['values'] = port_listif port_list:self.port_combobox.current(0)def toggle_connection(self):"""打开/关闭串口"""if self.serial_port and self.serial_port.is_open:self.close_serial_port()self.connect_btn.config(text="打开串口")self.status_var.set("串口已关闭")else:if self.open_serial_port():self.connect_btn.config(text="关闭串口")self.status_var.set(f"串口 {self.port_var.get()} 已打开")self.start_receiving()def open_serial_port(self):"""打开串口"""try:port = self.port_var.get()baudrate = int(self.baudrate_var.get())bytesize = int(self.databits_var.get())stopbits_map = {"1": serial.STOPBITS_ONE, "1.5": serial.STOPBITS_ONE_POINT_FIVE, "2": serial.STOPBITS_TWO}stopbits = stopbits_map[self.stopbits_var.get()]parity_map = {"None": serial.PARITY_NONE, "Even": serial.PARITY_EVEN, "Odd": serial.PARITY_ODD, "Mark": serial.PARITY_MARK, "Space": serial.PARITY_SPACE}parity = parity_map[self.parity_var.get()]flowcontrol_map = {"None": serial.FLOWCONTROL_NONE,"Hardware (RTS/CTS)": serial.FLOWCONTROL_RTSCTS,"Software (XON/XOFF)": serial.FLOWCONTROL_SOFTWARE}flowcontrol = flowcontrol_map[self.flowcontrol_var.get()]self.serial_port = serial.Serial(port=port,baudrate=baudrate,bytesize=bytesize,parity=parity,stopbits=stopbits,xonxoff=flowcontrol == serial.FLOWCONTROL_SOFTWARE,rtscts=flowcontrol == serial.FLOWCONTROL_RTSCTS,timeout=1)# 启用定时发送按钮self.timer_send_btn.config(state=tk.NORMAL)self.timer_entry.config(state=tk.NORMAL)return Trueexcept Exception as e:messagebox.showerror("错误", f"无法打开串口:\n{str(e)}")return Falsedef close_serial_port(self):"""关闭串口"""if self.serial_port and self.serial_port.is_open:self.stop_receiving()self.stop_timer_send()self.serial_port.close()self.timer_send_btn.config(state=tk.DISABLED)self.timer_entry.config(state=tk.DISABLED)def start_receiving(self):"""开始接收数据"""self.is_receiving = Trueself.receive_thread = threading.Thread(target=self.receive_data, daemon=True)self.receive_thread.start()def stop_receiving(self):"""停止接收数据"""self.is_receiving = Falseif self.receive_thread and self.receive_thread.is_alive():self.receive_thread.join()def receive_data(self):"""接收数据线程"""while self.is_receiving and self.serial_port and self.serial_port.is_open:try:if self.serial_port.in_waiting > 0:data = self.serial_port.read(self.serial_port.in_waiting)self.display_received_data(data)except Exception as e:self.status_var.set(f"接收错误: {str(e)}")breaktime.sleep(0.01)def display_received_data(self, data):"""显示接收到的数据"""def append_text(text):self.recv_text.insert(tk.END, text)self.recv_text.see(tk.END)self.root.update()display_data = dataif self.recv_format_var.get() == "Hex":display_data = data.hex(' ').upper()if self.display_var.get() == "显示时间":current_time = datetime.now().strftime("[%H:%M:%S.%f] ")append_text(current_time)append_text(display_data.decode('latin-1') if self.recv_format_var.get() == "ASCII" else display_data + '\n')def send_data(self):"""发送数据"""if not self.serial_port or not self.serial_port.is_open:messagebox.showwarning("警告", "请先打开串口")returntry:data = self.send_text.get("1.0", tk.END).strip()if not data:returnif self.send_format_var.get() == "Hex":# 处理Hex发送data = data.replace(" ", "").replace("\n", "")if len(data) % 2 != 0:messagebox.showwarning("警告", "Hex数据长度必须为偶数")returntry:send_data = bytes.fromhex(data)except ValueError:messagebox.showwarning("警告", "无效的Hex数据")returnelse:# ASCII发送send_data = data.encode('utf-8')if self.auto_newline_var.get():send_data += b'\r\n'self.serial_port.write(send_data)self.status_var.set(f"已发送 {len(send_data)} 字节")except Exception as e:messagebox.showerror("错误", f"发送失败:\n{str(e)}")def toggle_timer_send(self):"""切换定时发送状态"""if hasattr(self, 'timer_send_active') and self.timer_send_active:self.stop_timer_send()self.timer_send_btn.config(text="启动定时发送")else:self.start_timer_send()self.timer_send_btn.config(text="停止定时发送")def start_timer_send(self):"""启动定时发送"""try:interval = int(self.timer_var.get())if interval <= 0:raise ValueError("间隔时间必须大于0")self.timer_send_active = Trueself.timer_send_task()except ValueError as e:messagebox.showwarning("警告", f"无效的定时发送间隔:\n{str(e)}")def stop_timer_send(self):"""停止定时发送"""if hasattr(self, 'timer_send_active'):self.timer_send_active = Falsedef timer_send_task(self):"""定时发送任务"""if getattr(self, 'timer_send_active', False):self.send_data()interval = int(self.timer_var.get())self.root.after(interval, self.timer_send_task)def save_received_data(self):"""保存接收到的数据"""data = self.recv_text.get("1.0", tk.END)if not data:messagebox.showwarning("警告", "没有数据可保存")returnfile_path = filedialog.asksaveasfilename(defaultextension=".txt",filetypes=[("Text files", "*.txt"), ("All files", "*.*")],title="保存接收数据")if file_path:try:with open(file_path, 'w', encoding='utf-8') as f:f.write(data)messagebox.showinfo("成功", "数据保存成功")except Exception as e:messagebox.showerror("错误", f"保存失败:\n{str(e)}")def clear_received_data(self):"""清除接收区"""self.recv_text.delete("1.0", tk.END)def on_closing(self):"""窗口关闭事件处理"""self.close_serial_port()self.root.destroy()
if name == “main”:
root = tk.Tk()
app = SerialPortAssistant(root)
root.protocol(“WM_DELETE_WINDOW”, app.on_closing)
root.mainloop()
2、python打包程序
a、pip install pyinstaller
b、打包
import PyInstaller.main
PyInstaller.main.run([
‘–name=SerialPortAssistant’,
‘–onefile’,
‘–windowed’,
‘–add-data=icon.ico;.’, # 如果有图标文件
‘serial_assistant.py’ # 你的主程序文件名
])