用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] == ‘\n‘:
                    data = data[0:-1]
                if self.linebreak.get():
                    data = data.replace(‘\n‘, ‘\r\n‘)

                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 = "关闭串口"

原文地址:https://www.cnblogs.com/cbdeng/p/12129401.html

时间: 2024-10-29 13:30:27

用Tkinter实现一个串口调试助手的相关文章

新一代优秀串口助手-小蜂串口调试助手

小蜂串口调试助手Ver1.0是非常好用的串口调试工具,可以实现的功能包括发送接收16进制数.字符串,另所有拥有目前领先其它串口工具独有功能,可以在一个窗口上显示两个串口界面.无须安装,直接使用,界面简洁,清晰,操作简单.适宜单片机编程时对RS232的通讯和测试以及所有有串口工作. 在使用小蜂串口助手之前,必须先安装Microsoft.NET Framework 4.5.exe或以上版本软件,微软官方网站下载地址:http://www.microsoft.com/net/ 百度网盘下载地址: ht

串口调试助手,VB6.0开发

1.为什么要自己开发一个串口调试助手 通常我们都是:在网上直接下载一个串口助手,可执行文件,直接使用,并无法得到其源码,在此我们提供了一个VB6.0开发的串口助手: (1)让你极速掌握串口开发的要点: (2)提供源码,并且源代码中有详细注释: (3)极速开发出上位机,并与下位机(单片机)等通讯: (4)使用自己的串口助手,高大上 支持的功能: (1)支持串口设置:串口号,波特率,校验位,数据位,停止位 (2)支持字符格式发送和接收 (3)支持十六进制格式发送和接收 (4)支持将接收到的数据保存到

发布一个UDP调试助手

UDP协议适用于那种频繁通信,但是可以容忍一些丢包的应用,比如GPS的定位应用. 调试UDP助手,可以定时发送,输出文本, 记录收到时间. 1. 该工具基于IOCP网络模型,调试UDP服务使用的一个工具,可以定时的向服务端发出请求,收到时自动显示在接收区域. 2. 软件关闭时可以存储侦听端口,远程IP和端口,定时发送设定时间,发送信息,接收的设定等信息.启动时可以进行读取,更大的方便了调试. DIOCP_UDP_Tools绿色版

Delphi - 采用第三方控件TMS、SPComm开发串口调试助手

第三方控件TMS.SPComm的下载与安装 盒子上可搜索关键字进行下载,TMS是.dpk文件,SPComm.pas文件: 安装方法自行百度,不做赘述. 通过TMS控件进行界面布局 界面预览: Delphi通过SPComm连接串口.发送和接收指令 连接串口 拖一个TComm控件到主窗体上,选中控件,单击F11,完成如下配置. 这里主要是将一些布尔类型的属性设置成False,其他属性在前台连接按钮事件下动态设置. 连接代码如下,这里需要特别主意一下: 当串口参数超过COM9(即COM10.COM11

C# 串口调试助手

本方法,禁用跨进程错误(做法不太好,但是对于单片机出身的人来说,好理解,能用就行). 基本功能: 1.点串口号的下拉菜单自动当前检索设备管理器的COM 2.发送模式可选,hex和string两种 3.接收显示模式,hex和string两种 4.发送多行数据 5.发送单行,可增加自动换行(方便用于一些串口指令,很多指令都带回车,每次写回车太麻烦) 效果演示: 主代码参考: using System; using System.Collections.Generic; using System.Co

[转]web串口调试助手,浏览器控制串口设备

本文转自:https://blog.csdn.net/ldevs/article/details/39664697 打开串口时查找可用串口供选择 通过javascript调用activex控制串口收发数据,可以通过轮询每个串口状态找到指定的设备,简化用户操作 选择并打开串口 function selcomport(){if ($("#btnOpen").val() == "关闭串口") { closeCom(); $("#btnOpen").va

串口调试助手

一直想开发一款串口调试助手软件,更确切的说是需要了解串口调试助手怎样设计的. 最近做windows串口编程时搜索到一些开源的,简单记录如下: 1. https://github.com/movsb/common win32平台串口调试助手 https://blog.twofei.com/566/ 开发语言:C++ 2. https://github.com/Neutree/COMTool 跨平台串口调试助手 开发语言:python,基于QT 3. 无界面跨平台go库 https://github

BLE蓝牙4.0串口调试助手

支持HEX和文本发送接收,仅175K  无广告 无高级权限 APK下载地址:http://pan.baidu.com/s/1gdk20dP IOS版敬请期待... 版权声明:本文为博主原创文章,未经博主允许不得转载.

OSDA - 一个以MIT协议开源的串行端口调试助手

市场其实有很多开源的串口调试助手,但很大一部分没有明确的开源协议,还有一部分只能个人使用,所以编写了一个并以MIT协议授权开源. 主页: https://leven9.gitee.io/osdaweb/ (主页源码:https://gitee.com/leven9/OSDAWeb) Git存储库: https://github.com/leven99/OSDA 和 https://gitee.com/leven9/OSDA (两个存储库是一样的) 菜单栏中的配置功能有: 字节编码(即文本传输前和