用Tkinter实现一个串口调试助手

Posted cbdeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用Tkinter实现一个串口调试助手相关的知识,希望对你有一定的参考价值。

2020年元旦,武汉出现了不明原因肺炎,搞得人心惶惶,只好宅在家里。闲着也是闲着,于是把很久之前用python写的一个串口调试助手再修改一下。

简单说明一下:

  1. 以前是准备在debian系统里调试单片机用的,基于python 2.6,GUI是用自带Tkinter做的,不用安装第三方库。
  2. 修改了之后,可以跨linux、windows平台(我没有MacOS系统,只能放弃),python 2.6/2.7/3.7都简单测试了一下,暂时只能说都可以用。
  3. 只实现了基本功能,没有自动发送、发送文件、保存等功能,这些以后有时间再增加。
  4. 需要第三方pyserial库,请“pip install pyserial”自行安装。

 

先上一个截图

技术图片

下面是代码,供大家参考(我比较懒,没添加注释)。

serialassistant.py:

# -*- coding: utf-8 -*-
import sys
import serial
import serial.tools.list_ports
import threading
import signal
import binascii

import strings_zh as strings

if sys.version_info.major == 2:
    import Tkinter as tk
    import tkMessageBox as msgbox
elif sys.version_info.major == 3:
    import tkinter as tk
    from tkinter import messagebox as msgbox


WINDOWSIZE = 710x510+50+50
# Common baudrate
BAUDRATES = (1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200)
BYTESIZES = (5, 6, 7, 8)
PARITIES = {None: N, Even: E, Odd: O, Mark: M, Space: S}
STOPBITS = (1, 1.5, 2)
BYTETYPES = (1 Byte, 2 Bytes, 4 Bytes)

TIMEOUT = 0.015

font = ("宋体", 10, "normal")
font_text = ("Courier", 9, "normal")


def format_data(data):
        li = data.split( )
        result = []
        k = 0
        for each in li:
            if len(each) <= 2:
                result.append(each)

            if len(each) > 2:
                while k < len(each):
                    result.append(each[k: k + 2])
                    k = k + 2
                k = 0

        for i in range(len(result)):
            if len(result[i]) == 1:
                result[i] = 0 + result[i]

        return result


def getAvailabelSerialPort():
    available_ports = []
    coms = serial.tools.list_ports.comports()
    if coms is not None:
        for com in coms:
            if sys.platform == win32:
                available_ports.append(com.device)
            elif sys.platform == linux2:
                if com[2] != n/a:
                    available_ports.append(com[0])
    return tuple(available_ports)


if sys.platform == win32:
    PORTS = sorted(getAvailabelSerialPort(), key=lambda n: int(n[3:]))
else:
    PORTS = getAvailabelSerialPort()


class SerialAssistantGUI(object):
    def __init__(self):
        self.root = tk.Tk()
        self.root.title(%s v%s by %s % (strings.APP_NAME, strings.APP_VER,
                                          strings.AUTHOR))
        self.root.geometry(WINDOWSIZE)
        self.root.resizable(width=False, height=False)

        self.__recv_area()
        self.__send_area()
        self.__cmd_area()
        self.__opt_area()

        self.root.mainloop()

    def __recv_area(self):
        recv_lframe = tk.LabelFrame(self.root,
                                    text=strings.recv_buf_text,
                                    height=250)
        recv_lframe.pack(fill=tk.X, padx=5)

        recv_optframe = tk.Frame(recv_lframe)
        recv_txtframe = tk.Frame(recv_lframe)
        recv_optframe.pack(fill=tk.Y, side=tk.LEFT, padx=5, pady=5)
        recv_txtframe.pack(fill=tk.Y, side=tk.RIGHT, padx=5, pady=5)

        self.recv_mode = tk.IntVar()
        self.recv_mode.set(0)
        recv_radbtn1 = tk.Radiobutton(recv_optframe,
                                      text=strings.text_mode_text,
                                      font=font,
                                      variable=self.recv_mode, value=0)
        recv_radbtn2 = tk.Radiobutton(recv_optframe,
                                      text=strings.hex_mode_text,
                                      font=font,
                                      variable=self.recv_mode, value=1)
        recv_clrbtn = tk.Button(recv_optframe, width=15,
                                text=strings.clear_recv_text,
                                font=font,
                                command=self.clear_received)
        recv_radbtn1.pack(anchor=tk.W)
        recv_radbtn2.pack(anchor=tk.W)
        recv_clrbtn.pack(fill=tk.X)

        self.recv_txtarea = tk.Text(recv_txtframe, height=15, width=74,
                                    font=font_text)
        self.recv_txtarea.pack(side=tk.LEFT, fill=tk.X)
        recv_scrbar = tk.Scrollbar(recv_txtframe)
        recv_scrbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.recv_txtarea[yscrollcommand] = recv_scrbar.set
        recv_scrbar[command] = self.recv_txtarea.yview

    def __send_area(self):
        send_lframe = tk.LabelFrame(self.root,
                                    text=strings.send_buf_text,
                                    height=100)
        send_lframe.pack(fill=tk.X, padx=5)

        send_optframe = tk.Frame(send_lframe)
        send_txtframe = tk.Frame(send_lframe)
        send_optframe.pack(fill=tk.Y, side=tk.LEFT, padx=5, pady=5)
        send_txtframe.pack(fill=tk.Y, side=tk.RIGHT, padx=5, pady=5)

        self.send_mode = tk.IntVar()
        self.send_mode.set(0)
        send_radbtn1 = tk.Radiobutton(send_optframe,
                                      text=strings.text_mode_text,
                                      font=font,
                                      variable=self.send_mode, value=0)
        send_radbtn2 = tk.Radiobutton(send_optframe,
                                      text=strings.hex_mode_text,
                                      font=font,
                                      variable=self.send_mode, value=1)
        self.linebreak = tk.IntVar()
        if sys.platform == win32:
            self.linebreak.set(1)
        elif sys.platform == linux2:
            self.linebreak.set(0)
        send_chkbtn = tk.Checkbutton(send_optframe,
                                     text=strings.line_break_text,
                                     font=font,
                                     variable=self.linebreak)
        send_clrbtn = tk.Button(send_optframe,
                                text=strings.clear_send_text,
                                font=font,
                                width=15,
                                command=self.clear_sent)
        send_radbtn1.pack(anchor=tk.W)
        send_radbtn2.pack(anchor=tk.W)
        send_chkbtn.pack(anchor=tk.W)
        send_clrbtn.pack(fill=tk.X)

        self.send_txtarea = tk.Text(send_txtframe, height=7, width=74,
                                    font=font_text)
        self.send_txtarea.pack(side=tk.LEFT, fill=tk.X)
        send_scrbar = tk.Scrollbar(send_txtframe)
        send_scrbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.send_txtarea[yscrollcommand] = send_scrbar.set
        send_scrbar[command] = self.send_txtarea.yview

    def __cmd_area(self):
        cmd_frame = tk.Frame(self.root)
        cmd_frame.pack(fill=tk.X, padx=5, pady=5)
        cmd_btn = tk.Button(cmd_frame,
                            text=strings.send_btn_text,
                            font=font,
                            command=self.send_data)
        cmd_btn.pack(side=tk.LEFT)

    def __opt_area(self):
        opt_frame = tk.Frame(self.root)
        opt_frame.pack(fill=tk.X, padx=5, pady=5)

        # Serial port setting
        port_label = tk.Label(opt_frame,
                              text=strings.port_lbl_text,
                              font=font)

        self.port_var = tk.StringVar()
        self.port_var.set(strings.select_port)

        if len(PORTS) == 0:
            port_opmenu = tk.OptionMenu(opt_frame, self.port_var, ‘‘)
        else:
            port_opmenu = tk.OptionMenu(opt_frame, self.port_var, *PORTS)
        port_opmenu.config(anchor=tk.W,
                           width=10,
                           font=font)
        port_label.pack(side=tk.LEFT)
        port_opmenu.pack(side=tk.LEFT)

        # Baudrate setting
        brt_label = tk.Label(opt_frame,
                             text=strings.baud_lbl_text,
                             font=font)
        self.brt_var = tk.StringVar()
        self.brt_var.set(BAUDRATES[3])
        brt_opmenu = tk.OptionMenu(opt_frame, self.brt_var, *BAUDRATES)
        brt_opmenu.config(anchor=tk.W,
                          width=3,
                          font=font)
        brt_label.pack(side=tk.LEFT)
        brt_opmenu.pack(side=tk.LEFT)

        # Bytesize setting
        size_label = tk.Label(opt_frame,
                              text=strings.size_lbl_text,
                              font=font)
        self.size_var = tk.StringVar()
        self.size_var.set(BYTESIZES[3])
        size_opmenu = tk.OptionMenu(opt_frame, self.size_var, *BYTESIZES)
        size_opmenu.config(anchor=tk.W,
                           width=3,
                           font=font)
        size_label.pack(side=tk.LEFT)
        size_opmenu.pack(side=tk.LEFT)

        # Parity setting
        parity_label = tk.Label(opt_frame,
                                text=strings.parity_lbl_text,
                                font=font)
        self.parity_var = tk.StringVar()
        self.parity_var.set(None)
        parity_opmenu = tk.OptionMenu(opt_frame, self.parity_var,
                                      *PARITIES)
        parity_opmenu.config(anchor=tk.W,
                             width=3,
                             font=font)
        parity_label.pack(side=tk.LEFT)
        parity_opmenu.pack(side=tk.LEFT)

        # Stopbit setting
        stop_label = tk.Label(opt_frame,
                              text=strings.stopbit_lbl_text,
                              font=font)
        self.stop_var = tk.StringVar()
        self.stop_var.set(STOPBITS[0])
        stop_opmenu = tk.OptionMenu(opt_frame, self.stop_var, *STOPBITS)
        stop_opmenu.config(anchor=tk.W,
                           width=3,
                           font=font)
        stop_label.pack(side=tk.LEFT)
        stop_opmenu.pack(side=tk.LEFT)

        # Set buttons
        control_frame = tk.Frame(self.root, width=300)
        status_frame = tk.Frame(self.root, width=300)
        control_frame.pack(side=tk.LEFT)
        status_frame.pack(side=tk.RIGHT)

        start_btn = tk.Button(control_frame,
                              text=strings.start_port_text,
                              width=12,
                              command=self.start_port)
        close_btn = tk.Button(control_frame,
                              text=strings.close_port_text,
                              width=12,
                              command=self.close_port)
        start_btn.pack(side=tk.LEFT, padx=5)
        close_btn.pack(side=tk.LEFT, padx=5)
        self.state_lbl = tk.Label(control_frame, text=‘‘)
        self.state_lbl.pack(side=tk.LEFT, padx=5)

        # Status frame widgets
        send_cnt_label = tk.Label(status_frame,
                                  text=strings.send_lbl_text,
                                  font=font)
        self.send_cnt = tk.StringVar()
        self.send_cnt.set(self.TX)
        send_cnt_entry = tk.Entry(status_frame,
                                  textvariable=self.send_cnt, width=10,
                                  font=font,
                                  relief=tk.SUNKEN,
                                  state=tk.DISABLED,
                                  justify=tk.RIGHT)
        send_cnt_label.pack(side=tk.LEFT)
        send_cnt_entry.pack(side=tk.LEFT)

        recv_cnt_label = tk.Label(status_frame,
                                  text=strings.recv_lbl_text,
                                  font=font)
        self.recv_cnt = tk.StringVar()
        self.recv_cnt.set(self.RX)
        recv_cnt_entry = tk.Entry(status_frame,
                                  textvariable=self.recv_cnt, width=10,
                                  font=font,
                                  relief=tk.SUNKEN,
                                  state=tk.DISABLED,
                                  justify=tk.RIGHT)
        recv_cnt_label.pack(side=tk.LEFT)
        recv_cnt_entry.pack(side=tk.LEFT)

        clr_btn = tk.Button(status_frame,
                            text=strings.clear_btn_text,
                            font=font,
                            command=self.clear_count)
        clr_btn.pack()

    def clear_received(self):
        self.recv_txtarea.delete(0.0, tk.END)

    def clear_sent(self):
        self.send_txtarea.delete(0.0, tk.END)

    def clear_count(self):
        pass

    def start_port(self):
        pass

    def send_data(self):
        pass

    def recv_data(self):
        pass


class SerialAssistant(SerialAssistantGUI):

    portisopen = 0
    TX = 0
    RX = 0

    def __init__(self):
        super(SerialAssistant, self).__init__()

    def clear_count(self):
        self.RX = 0
        self.TX = 0
        self.send_cnt.set(self.RX)
        self.recv_cnt.set(self.TX)

    def start_port(self):
        port = self.port_var.get()
        baudrate = int(self.brt_var.get())
        bytesize = int(self.size_var.get())
        parity = PARITIES[self.parity_var.get()]
        stopbits = float(self.stop_var.get())

        try:
            self.s = serial.Serial(port=port,
                                   baudrate=baudrate,
                                   bytesize=bytesize,
                                   parity=parity,
                                   stopbits=stopbits,
                                   timeout=TIMEOUT)
        except serial.SerialException as e:
            msgbox.showerror("OpenError", e)
        except serial.SerialTimeoutException as e:
            msgbox.showerror("OpenError", e)
        else:
            self.portisopen = 1
            self.state_lbl.config(background=green,
                                  text=port + strings.opening)
            self.th = threading.Thread(target=self.recv_data)
            self.th.daemon = True
            self.th.start()

    def send_data(self):
        if self.portisopen:
            data_fmt = []
            data = self.send_txtarea.get(0.0, tk.END)

            if len(data) == 1:
                return

            if self.send_mode.get():
                if data[-1] == x0a:
                    data = data[0:-1]
                data_fmt = format_data(data)
                for each in data_fmt:
                    try:
                        x = binascii.a2b_hex(each)
                    except TypeError:
                        x = x00

                    self.s.write(x)
                    self.TX += 1
            else:
                if data[-1] == 
:
                    data = data[0:-1]
                if self.linebreak.get():
                    data = data.replace(
, 
)

                self.s.write(data.encode(utf-8))
                self.TX += len(data)
            self.send_cnt.set(self.TX)
        else:
            msgbox.showerror("Error", "Port NOT open!")
            return

    def recv_data(self):
        while self.portisopen:
            data = self.s.read()
            if len(data) != 0:
                if self.recv_mode.get() == 1:
                    data = binascii.b2a_hex(data)
                    if sys.version_info.major == 3:
                        data += b 
                    elif sys.version_info.major == 2:
                        data +=  
                    self.RX += 1
                else:
                    self.RX += len(data)
            self.recv_cnt.set(self.RX)
            if data != x0d:
                self.recv_txtarea.insert(tk.END, data)
                self.recv_txtarea.see(tk.END)
        return

    def close_port(self):
        if self.portisopen:
            self.portisopen = 0
            self.s.close()
            self.state_lbl.config(background=red,
                                  text=self.port_var.get() + strings.closed)
        else:
            return

    def handler(self, signum, frame):
        self.portisopen = 0
        self.th.join()

    def __del__(self):
        self.close_port()
        signal.signal(signal.SIGINT, self.handler)


def main():
    SerialAssistant()


if __name__ == __main__:
    main()

strings_zh.py:

# -*- coding: utf-8 -*-
APP_NAME = 串口调试助手
APP_VER = 0.3
AUTHOR = cbdeng

recv_buf_text = "接收缓冲区"
text_mode_text = "文本模式"
hex_mode_text = "16进制模式"
clear_recv_text = "清空接收"
send_buf_text = "发送缓冲区"
line_break_text = "换行符:‘\\r\\n‘"
clear_send_text = "清空发送"
send_btn_text = "发送数据"
port_lbl_text = "串口:"
baud_lbl_text = "波特率:"
size_lbl_text = "数据位:"
parity_lbl_text = "校验位:"
stopbit_lbl_text = "停止位:"
send_lbl_text = "SEND"
recv_lbl_text = "RECV"
clear_btn_text = "清除"
opening = "已打开"
closed = "已关闭"
select_port = "请选择串口"
start_port_text = "打开串口"
close_port_text = "关闭串口"

以上是关于用Tkinter实现一个串口调试助手的主要内容,如果未能解决你的问题,请参考以下文章

python Tkinter GUI 串口通信 显示。

串口调试助手都能实现啥功能啊

为啥我的电脑上的串口不能用,,用串口调试助手的时候说找不到?

stm32发送数据给上位机用串口调试助手接收为什么只接收到第一个字节数据?

怎么用串口助手发送数组?

13.3.4 蓝牙程序案例: 蓝牙串口调试助手