0
点赞
收藏
分享

微信扫一扫

python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端

python3+tkinter实践历程(四)——基于socket通信与tkinter的TCP串口客户端(仿CRT)

文章目录


系列文章目录

python3+tkinter实践历程(一)——基于requests与tkinter的API工具
python3+tkinter实践历程(二)——基于tkinter的日志检索工具
python3+tkinter实践历程(三)——基于requests与tkinter的模拟web登录工具
python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端

分享背景

①分享意图在于帮助新入门的朋友,提供思路,里面详细的注释多多少少能解决一些问题。欢迎大佬指点跟交流。
②2021年8月,开始陆续有需求制作一些工具,因为python语言跟tkinter工具相对简单,所以就基于这些做了好几个不同用处的工具。
③分享从完成的第一个工具开始分享,分享到最新完成的工具,对于tkinter的理解也从一开始的摸索入门,到后来逐渐熟练,完成速度也越来越快,所用到的tk的功能点也越来越多。
④这是发布前做的最后一个工具,也是最难、最复杂、要求最多、耗时最长的一个。

制作背景

① 研发部自己做一个串口服务器,硬件部解决板子问题,开发将软件写入板子。最终服务器可以用特制的线连通24个设备的串口,开启了TCP服务端口,服务器驱动将24个设备的日志实时的通过socket套接字将发送给已连接的TCP客户端。
② 本人所做的就是这个接收、处理串口服务器传过来的日志的TCP客户端。
③ 功能需求如最终功能所示,完全实现了所有预期需求。

最终功能

① 提供界面输入-TCP服务器的IP、端口
② 提供界面选项-保存日志的目录,成功连接TCP服务器后,自动将所有接收到的日志保存,按日期划分文件夹,按串口划分log文件。
③ 提供界面打印界面,自主选择串口打印,且与保存日志功能完全分离,互不干扰。
④ 打印日志的界面支持增、删、重命名。
⑤ 工具支持读取界面配置、记录历史配置
⑥ 支持给不同串口发送命令,发送命令框支持回车发送+按钮发送
⑦ 快速发送按钮支持读取配置
⑧ 提供选项-滚动条是否自动跟随新打印的日志,支持实时改变

工具截图展示

在这里插入图片描述

代码详解

# -*- coding=utf-8 -*-
import tkinter as tk
from tkinter import ttk
import tkinter.font as tf
import threading
import os
import datetime
import socket
import re
import time
import json
from tkinter.filedialog import askdirectory

# 用于连接TCP服务器、接收、发送数据的类
class SocketServer(object):
    def __init__(self, ip, port):
    	# 初始化时自动连接TCP服务器
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        print('开始连接串口服务器%s:%d' % (ip, port))
        self.sock.connect((ip, port))
        print('连接完成')

    def get_serial_data(self):
    	# 接收从服务器发送过来的日志,返回给上层
        recv_info = self.sock.recv(7168).decode("utf-8", "replace")       # 服务器传过来的是bytes类型,收到数据后编码变成str
        # print(recv_info)
        return recv_info

    def send_data(self, data):
    	# 将已经处理好的数据直接发送给服务器
        self.sock.send(data)


class Application(tk.Frame):
    def __init__(self, master=None):
    # 进行初始化,打开工具时完成的动作
        tk.Frame.__init__(self, master)
        self.grid()

        self.save_log_dir = ''		# 记录保存日志的路径
        self.is_running = ''		# 记录是否TCP交互正在进行
        self.LOG_LINE_NUM = 0		# 记录日志框中打印的日志行数
        self.see_log = True		    # 是否滚动条跟随最新打印的日志
        self.button_column = 0      # 用于记录多个tab页所用过的column属性的最大值,以便于后新增的tab页可以出现在最后面。否则在多次删减过后,出现新增tab页生成位置异常的bug
        self.top = ''               # 重命名窗口,后续变成重命名窗口界面的对象
        self.tab = {}       		# TAB集合,所有的TAB按钮与日志打印框的对象和显示的text文本都记录在这里,多tab页日志打印框的核心字典
        self.active = ''    		# 当前置顶,记录当前切换的TAB,查看、发送、删除、重命名四个的操作都基于这个调用
        self.create_widgets()		# 执行下面函数,创建图形化界面

    def create_widgets(self):
        """创建图形化界面"""
        # 每一行用一个Frame包裹,里面的控件基于Frame来创建
        # 第一行
        row1 = tk.Frame(self)
        row1.grid(row=0, column=0, sticky='w', padx=(15, 0), pady=10)

        # IP端口输入框
        server_url = tk.Label(row1, text="serial server IP and port(xx:xx)")
        server_url.grid(row=0, column=1)
        server_url = tk.StringVar()
        server_url.set("10.100.10.180:9036")         # set的作用的预置的文字(在标签内可变的文本)
        self.server_url = tk.Entry(row1, textvariable=server_url, width=20)
        self.server_url.grid(row=0, column=2, padx=2, pady=2)
        
        # 日志存放目录
        tk.Label(row1, text="", width=1).grid(row=0, column=3)
        tk.Label(row1, text="log dir").grid(row=0, column=4)
        self.dir = tk.StringVar()
        self.dir.set("")
        self.log_dir = tk.Entry(row1, textvariable=self.dir, width=35)
        self.log_dir.grid(row=0, column=5)
        tk.Button(row1, text="select directory", command=self.selectPath).grid(row=0, column=6)
        # 开始按钮--连接服务器并接收保存日志
        tk.Label(row1, text="", width=2).grid(row=0, column=7)
        start_btn = tk.Button(row1, text="start connect and save log", command=self.start_socket_conn).grid(row=0, column=8)
        # 停止按钮
        stop_btn = tk.Button(row1, text="stop conn", command=self.start_stop_task).grid(row=0, column=9, padx=(10, 0))
		# 滚动条跟随日志复选框
        self.see_log_value = tk.IntVar()
        self.see_log_value.set(1)
        tk.Checkbutton(row1, text="Follow log", variable=self.see_log_value, command=self.change_see_log).grid(row=0, column=14, padx=20)
		
		# 第二、第三行都先创建一个Frame用来固定宽度
        # 第二行 tab页  非固定行
        self.row2 = tk.Frame(self)
        self.row2.grid(row=1, column=0, sticky='w', padx=(15, 0))
        # 第二行的三个按钮: “+”、“-”、“重命名”
        # 对应增加一个TAB页、删除当前置顶的TAB页、重命名当前TAB页的功能
        # “+”绑定self.add_log_text方法来实现,“-”绑定self.del_log_text方法来实现,“重命名”绑定self.start_tab_rename方法来实现,三个函数均没有放进线程执行
        # 三个按钮的column属性分别为1000、1001、1002,目的是实现无论新增多少个TAB,这三个按钮都按顺序排在其他TAB按钮之后,具体展示可看图
        tk.Button(self.row2, text='+', relief='solid', bd=1, command=self.add_log_text, width=2).grid(row=0, column=1000, padx=2)
        tk.Button(self.row2, text='-', relief='solid', bd=1, command=self.del_log_text, width=2).grid(row=0, column=1001, padx=2)
        tk.Button(self.row2, text='重命名', relief='solid', bd=1, command=self.start_tab_rename, width=5).grid(row=0, column=1002, padx=2)
        
        # 第三行   非固定行
        # 先创建一个Frame固定第三行,然后有多少个TAB就在这个Frame里的同一位置创建多少个Frame
        # 这样需要隐藏、显示、销毁这个界面的时候,只需控制里面的Frame完成grid(显示)、grid_forget(隐藏)、destroy(销毁)即可
        self.row3 = tk.Frame(self, height=650)
        self.row3.grid(row=2, column=0, sticky='w', padx=(15, 0))
        
        # 以下从外部文件interface_data.txt读取第二第三行(TAB按钮+日志打印框)的内容
        # 调用work_for_data方法,返回字典
        interface_data = self.work_for_data('tab', 'read')
        # 如果存在数据,则使用存在的数据来创建界面,如果不存在数据,则使用初始界面
        if not interface_data:
	        # 初始界面为:{'log1': {'name_str': 'log1', 'serial_value': ''}},即一个TAB,标识符为log1,输出在界面的名称也为log1,选择的串口号为空,即不选择
            interface_data = {'log1': {'name_str': 'log1', 'serial_value': ''}}
        for name in interface_data:
                column = self.button_column + 1		# 每一次column + 1,即在下一个位置创建控件
                # tab字典以这个遍历到的name作为key来创建新字典,来记录这个name名下所有的数据
                # 解释tab字典内的每一个key的含义:
                # tab[name]['name'] :字符串容器,用来修改显示在界面上的tab名称,如图的10.108,用set()设置,get()获取
                # tab[name]['name_str'] :字符串,记录显示在界面上的tab名称,如图的10.108
                # tab[name]['c_button']:作为第二行的TAB的对象,绑定change_interface方法,来更改置顶的TAB
                # tab[name]['Frame']:作为第三行的Frame里面的Frame,控制name名下的第三行全部控件,只需对该对象使用grid(显示)、grid_forget(隐藏)、destroy(销毁)方法即可
                # tab[name]['text']: Text框(日志打印框)的对象,该TAB日志框的增删都通过该对象操作
                # tab[name]['serial']:串口选择下拉框的对象
                # tab[name]['serial']['value']:串口下拉框的所有选项
                
                self.tab[name] = dict()
                self.tab[name]['name'] = tk.StringVar()
                self.tab[name]['name'].set(interface_data[name]['name_str'])
                self.tab[name]['name_str'] = interface_data[name]['name_str']
                self.tab[name]['c_button'] = tk.Button(self.row2, textvariable=self.tab[name]['name'], relief='raised',
                                                       bd=1, command=lambda _name=name: self.change_interface(_name))
                self.tab[name]['c_button'].grid(row=0, column=column)
                self.button_column = column			# 刷新self.button_column的值,等于当前column
                self.tab[name]['Frame'] = tk.Frame(self.row3)
                self.tab[name]['Frame'].grid(row=0, column=0)
                ft = tf.Font(size=10)
                self.tab[name]['text'] = tk.Text(self.tab[name]['Frame'], width=180, height=50, font=ft, padx=5, pady=5,
                                                 relief='sunken')
                log_slide_bar = tk.Scrollbar(self.tab[name]['Frame'], command=self.tab[name]['text'].yview,
                                             orient="vertical")		# 日志框的滚动条
                log_slide_bar.grid(row=0, column=1, sticky='ns')
                self.tab[name]['text'].grid(row=0, column=0)
                self.tab[name]['text'].config(font=ft, yscrollcommand=log_slide_bar.set)
                tk.Label(self.tab[name]['Frame'], text="select serial").grid(row=0, column=2, sticky='n', padx=(0, 5))
                self.tab[name]['serial'] = ttk.Combobox(self.tab[name]['Frame'], state='readonly', width=10)
                self.tab[name]['serial'].grid(row=0, column=3, sticky='n')
                self.tab[name]['serial_value'] = interface_data[name]['serial_value']
                self.tab[name]['serial']['value'] = (
                                                'serial01', 'serial02', 'serial03', 'serial04', 'serial05', 'serial06',
                                                'serial07', 'serial08', 'serial09', 'serial10', 'serial11', 'serial12',
                                                'serial13', 'serial14', 'serial15', 'serial16', 'serial17', 'serial18',
                                                'serial19', 'serial20', 'serial21', 'serial22', 'serial23', 'serial24')
                self.tab[name]['serial'].bind('<<ComboboxSelected>>', self.start_change_serial)
                if interface_data[name]['serial_value']:
                    self.tab[name]['serial'].set(interface_data[name]['serial_value'])
        # TAB全部生成完成,选择一个TAB作为置顶,显示该TAB的第三行,同时按钮设置成不可操作。其他TAB的第三行隐藏,按钮设置成可操作
        for i in self.tab:
            if not self.active:
                self.active = i
                self.tab[i]['Frame'].grid()
                self.tab[i]['c_button'].config(state=tk.DISABLED)
                print('置顶%s界面' % i)
            else:
                self.tab[i]['Frame'].grid_forget()
                self.tab[i]['c_button'].config(state=tk.ACTIVE)

		# 第四行(命令输入框+发送按钮)
        row4 = tk.Frame(self)
        row4.grid(row=3, column=0, sticky='w', padx=(15, 0), pady=(15, 0))

        # 命令输入框
        ft = tf.Font(size=10)
        self.log_input = tk.Text(row4, width=100, height=4, font=ft)
        self.log_input.grid(row=0, column=1)
        self.log_input.bind('<Return>', self.start_send_data)       # text框绑定回车键,当收到回车键就触发start_send_data方法
        # 注意:self.start_send_data以return 'break'结尾,则回车的换行效果不会在文本框触发,如果没有,则会触发换行
        tk.Button(row4, text="send info", command=self.start_send_data).grid(row=0, column=2, padx=10)

        # 第五行 (快速发送命令按钮)
        row5 = tk.Frame(self)
        row5.grid(row=4, column=0, sticky='w', padx=(15, 0), pady=(15, 0))
		
        # 快速发送命令按钮
        # 只能从外部的quick_send_data.txt文件获取,不能在界面上添加
        quick_send_data = self.work_for_data('quick_send')		调用work_for_data方法获取文本中的信息
        if quick_send_data:
            print(quick_send_data)
            column = 0
            for data in quick_send_data:
            # 解析出文本的信息,遍历内容构建按钮,按钮绑定self.start_quick_print方法
            # data是一个字典,包含了显示在界面的按钮名称和执行的命令。
            # 将data2=data,然后把data2传入self.start_quick_print方法
                tk.Button(row5, text=data['name'], command=lambda data2=data: self.start_quick_print(data2)).grid(row=0, column=column, padx=(0, 10))
                column = column + 1

    def add_log_text(self):
        """增加多个tab页"""
        # 创建界面完成后,点击“+”按钮触发此函数,新增TAB按钮与第三行的日志框,方法与create_widgets中第二第三行的逻辑大同小异
        now_log_text_num = 0
        for ii in range(1, 1000):
            if 'log' + str(ii) not in self.tab:
                now_log_text_num = ii
                break
            else:
                now_log_text_num = 1000
        column = self.button_column + 1
        name = 'log' + str(now_log_text_num)
        self.tab[name] = dict()
        self.tab[name]['name'] = tk.StringVar()
        self.tab[name]['name'].set(name)
        self.tab[name]['name_str'] = name
        self.tab[name]['c_button'] = tk.Button(self.row2, textvariable=self.tab[name]['name'], relief='raised', bd=1, command=lambda _name=name: self.change_interface(_name))
        self.tab[name]['c_button'].grid(row=0, column=column)
        self.button_column = column
        self.tab[name]['Frame'] = tk.Frame(self.row3)
        self.tab[name]['Frame'].grid(row=0, column=0)
        ft = tf.Font(size=10)
        self.tab[name]['text'] = tk.Text(self.tab[name]['Frame'], width=180, height=50, font=ft, padx=5, pady=5, relief='sunken')
        log_slide_bar = tk.Scrollbar(self.tab[name]['Frame'], command=self.tab[name]['text'].yview,
                                     orient="vertical")
        log_slide_bar.grid(row=0, column=1, sticky='ns')
        self.tab[name]['text'].grid(row=0, column=0)
        self.tab[name]['text'].config(font=ft, yscrollcommand=log_slide_bar.set)
        tk.Label(self.tab[name]['Frame'], text="select serial").grid(row=0, column=2, sticky='n', padx=(0, 5))
        self.tab[name]['serial'] = ttk.Combobox(self.tab[name]['Frame'], state='readonly', width=10)
        self.tab[name]['serial'].grid(row=0, column=3, sticky='n')
        self.tab[name]['serial_value'] = ''
        self.tab[name]['serial']['value'] = ('serial01', 'serial02', 'serial03', 'serial04', 'serial05', 'serial06',
                                             'serial07', 'serial08', 'serial09', 'serial10', 'serial11', 'serial12',
                                             'serial13', 'serial14', 'serial15', 'serial16', 'serial17', 'serial18',
                                             'serial19', 'serial20', 'serial21', 'serial22', 'serial23', 'serial24')
        self.tab[name]['serial'].bind('<<ComboboxSelected>>', self.start_change_serial)
        self.active = name
        for i in self.tab:
            if i == self.active:
                self.tab[i]['Frame'].grid()
                self.tab[i]['c_button'].config(state=tk.DISABLED)
                print('置顶%s界面' % i)
            else:
                self.tab[i]['Frame'].grid_forget()
                self.tab[i]['c_button'].config(state=tk.ACTIVE)
        # 存放tab信息
        result = self.work_for_data('tab', 'write')
        if result:
            print('存放tab信息与interface_data.txt完成')

    def del_log_text(self):
        """删除当前置顶的tab页"""
        # 先换一个置顶的tab页面
        if len(self.tab) == 1:
            print('只有一个tab页,不可销毁')
            return
        target = self.active
        # 换一个置顶的TAB,该TAB显示,按钮不可用,其他TAB隐藏,按钮可用
        for i in self.tab:
            if i != self.active:
                self.active = i
                self.tab[i]['Frame'].grid()
                self.tab[i]['c_button'].config(state=tk.DISABLED)
                print('置顶%s界面' % i)
                break
        # 销毁目标的第三行、与TAB按钮
        self.tab[target]['Frame'].destroy()
        self.tab[target]['c_button'].destroy()
        del self.tab[target]
        # 存放tab信息
        result = self.work_for_data('tab', 'write')
        if result:
            print('存放tab信息与interface_data.txt完成')

    def tab_rename(self):
        """给tab页面重命名"""
        try:
            if self.top:		# 如果重命名窗口已经存在,就销毁这个窗口
                self.top.destroy()
                self.top = ''
            self.top = tk.Toplevel()		# 创建最上层的窗口
            self.top.title('重命名')		# 给该窗口命令
            self.top.transient(self)		# Toplevel注册成master的临时窗口
            self.top.resizable(0, 0)		# 不可改变大小
            # 居中
            width = self.winfo_screenwidth()
            height = self.winfo_screenheight()
            ww = 150
            wh = 75
            x = (width-ww)/2
            y = (height-wh)/2
            self.top.geometry("%dx%d+%d+%d" % (ww, wh, x, y))  # 自适应居中
            self.top.grid()
            # 重命名输入框
            name = tk.StringVar()
            name.set(self.tab[self.active]['name_str'])		# 把该TAB按钮原来的名字显示出来
            self.rename_entry = tk.Entry(self.top, textvariable=name)
            self.rename_entry.grid(row=0, column=0, columnspan=2, ipady=5)
            # 确定按钮		# 绑定self.control_rename_btn方法
            self.rename_sure_btn = tk.Button(self.top, text='确定', command=lambda : self.control_rename_btn('确定'))
            self.rename_sure_btn.grid(row=1, column=0, padx=10, pady=5)
            # 取消按钮
            self.rename_cancel_btn = tk.Button(self.top, text='取消', command=lambda : self.control_rename_btn('取消'))
            self.rename_cancel_btn.grid(row=1, column=1, padx=10, pady=5)
        except Exception as e:
            print('初始化重命名界面异常:%s' % e)

    def control_rename_btn(self, operate):
        """控制重命名的确定取消按钮"""
        # self.tab[self.active]['name']为该TAB按钮绑定的字符串容器,set()方法直接改变显示的名称,最后销毁重命名窗口
        if operate == '确定':
            if self.rename_entry.get():
                self.tab[self.active]['name_str'] = self.rename_entry.get()
                self.tab[self.active]['name'].set(self.rename_entry.get())
            self.top.destroy()
        if operate == '取消':
            self.top.destroy()

    def socket_conn(self):
        """与串口服务器建立TCP连接,无限循环获取数据,放进线程中执行"""
        ip_port = self.server_url.get().split(':')		# 获取TCP服务器IP和端口
        ip = ip_port[0]
        port = int(ip_port[1])
        global sock_conn			# 创建全局变量,为SocketServer类的对象
        sock_conn = SocketServer(ip, port)
        self.is_running = True		# 将 self.is_running设置为True
        while True:
            if self.is_running:			# 如果self.is_running被设置为False时,退出循环
                try:
                    recv_info = sock_conn.get_serial_data()		# 获取数据
                except Exception as e:
                    print('获取数据异常:%s' % e)
                    continue
                try:
                	# 以下为解析数据的方法,根据TCP服务器已经设定好的规则处理接收的数据,分解出日志本身与对应的串口
                    parse_rule = re.compile(r'\*<\[(\d{2})]>\*')			
                    parse_result = parse_rule.search(recv_info)
                    if parse_result:
                        log_info = recv_info.replace(parse_result.group(0), '')
                        serial = 'serial' + parse_result.group(1)
                    else:
                        print('串口标识识别失败:%s' % recv_info)
                        continue
                except Exception as e:
                    print('解析数据异常:%s,异常数据:%s' % (e, recv_info))
                    continue
                try:
                    self.save_log(log_info, serial)		# 保存该行日志
                except Exception as e:
                    print('保存日志异常:%s' % e)
                    continue
                try:
                    self.output_log(log_info, serial)	# 打印该行日志
                except Exception as e:
                    print('输出日志异常:%s' % e)
                    continue
            else:
                print('stopped')
                break

    def selectPath(self):
        """把获取到的串口目录传入Entry"""
        dir_ = askdirectory()
        self.dir.set(str(dir_))
        self.save_log_dir = self.log_dir.get()

    def save_log(self, log_info, serial_port):
        """把收到的日志分别传入不同的log文本中"""
        if not self.save_log_dir:
            # print('无传入日志目录')
            return
        # print('获取到的日志目录为:%s' % self.save_log_dir)
        now_day = datetime.datetime.now().strftime('%Y-%m-%d')
        # print('当前日期为:%s' % now_day)
        log_folder = os.path.join(self.save_log_dir, now_day)
        # print('存放今天日志目录为:%s' % log_folder)
        if not os.path.exists(log_folder):
            # print('不存在%s文件夹,进行新建' % log_folder)
            os.mkdir(log_folder)
        log_file = os.path.join(log_folder, '%s_%s.log' % (serial_port, now_day))
        log_info = log_info.rstrip('\n')
        with open(log_file, 'a+', errors='ignore', newline='') as f:
            f.write(log_info)

    def change_serial(self):
        """更改输出日志的串口"""
        # 当串口下拉框被操作时,会将当前置顶的TAB字典中的serial_value的值变成选择的串口,并且清空该TAB的日志框
        if self.tab[self.active]['serial_value'] != self.tab[self.active]['serial'].get():
            self.tab[self.active]['serial_value'] = self.tab[self.active]['serial'].get()
            self.tab[self.active]['text'].delete(0.0, 'end')
            print('更改%s的输出串口为:%s' % (self.active, self.tab[self.active]['serial_value']))
            # 存放tab信息,因为有TAB的串口发生变成,所以把当前tab字典更新在interface_data.txt文件
            result = self.work_for_data('tab', 'write')
            if result:
                print('存放tab信息与interface_data.txt完成')

    def change_interface(self, interface):
        """显示界面"""
        for i in self.tab:
            if i == interface:
                self.tab[interface]['Frame'].grid()
                self.tab[interface]['c_button'].config(state=tk.DISABLED)
            else:
                self.tab[i]['Frame'].grid_forget()
                self.tab[i]['c_button'].config(state=tk.ACTIVE)
        self.active = interface
        print('置顶%s:%s页面' % (interface, self.tab[interface]['name'].get()))

    def work_for_data(self, key, work=''):
        """存/取tab字典,取快捷输入数据"""
        try:
            if key == 'tab':
                if work == 'write':
                    self.save_tab = {}
                    for i in self.tab:
                        self.save_tab[i] = {}
                        self.save_tab[i]['name_str'] = self.tab[i]['name_str']
                        self.save_tab[i]['serial_value'] = self.tab[i]['serial_value']
                    with open('interface_data.txt', 'w') as f:
                        data = json.dumps(self.save_tab)
                        f.write(data)
                        return True
                if work == 'read':
                    with open('interface_data.txt', 'r') as f:
                        data = f.read()
                        data = json.loads(data)
                        print('读取到的tab:%s' % data)
                        return data
            if key == 'quick_send':
                data_list = []
                with open('quick_send_data.txt', 'r') as f:
                    for i in f:
                        if '*start-' in i:
                            info = {}
                            l = i.replace('*start-', '')
                            name = l[0:l.rfind('=')]
                            l = l[l.rfind('=') + 1:]
                            if '\enter' in l:
                                enter = True
                                l = l.replace('\enter', '')
                            else:
                                enter = False
                            if '\n' in l:
                                l = l.replace('\n', '')
                            l = l.split(';')
                            info['data'] = l
                            info['name'] = name
                            info['enter'] = enter
                            data_list.append(info)
                return data_list
        except Exception as e:
            print('对%s进行%s操作异常:%s' % (key, work, e))

    def output_log(self, log_info, serial_port):
        """实时输出日志"""
        for i in self.tab:
            if self.tab[i]['serial_value'] == serial_port:
                self.write_log_to_Text(i, log_info)
                break

    def stop_recv_data(self):
        self.is_running = False
        print('stopping')

    def send_data_to_server(self, data=''):
        """发送数据给服务器"""
        try:
            if not self.tab[self.active]['serial_value']:
                print('无指定串口')
                return
            if data:
                print('有指定数据:%s' % data)
            else:
                data = self.log_input.get(0.0, 'end')
                self.log_input.delete(0.0, 'end')
                if not data:
                    print('无数据')
                    return
            if data[-1] not in ['\n', '\r']:
                print('给数据加回车')
                data = data + '\n'
            print('给%s发送%s指令' % (self.tab[self.active]['serial_value'], data))
            data = '*<[%s]>*' % self.tab[self.active]['serial_value'][-2:] + data
            sock_conn.send_data(bytes(data, encoding='utf8'))
        except Exception as e:
            print('发送数据异常:%s' % e)

    def quick_print(self, data):
        if data:
            if len(data['data']) == 1:      # 只有一条数据
                if data['enter']:   # 自动发送
                    self.start_send_data(data=data['data'][0])
                else:               # 打印出来,不自动发送
                    self.log_input.insert('end', data['data'][0])
            else:               # 大于1条数据,需要发送多次
                for i in data['data']:
                    if i == 'p':
                        print('sleep(1)')
                        time.sleep(1)
                    else:
                        if data['enter']:
                            self.start_send_data(data=i)
                        else:
                            self.log_input.insert('end', i)

    def change_see_log(self):
        if self.see_log_value.get() in ['1', 1]:
            self.see_log = True
            print('切换成跟随日志')
        else:
            self.see_log = False
            print('切换成不跟随日志')

    def write_log_to_Text(self, interface, log_msg):
        """日志动态打印"""
        self.LOG_LINE_NUM = int(self.tab[interface]['text'].index('end').split('.')[0]) - 1
        if self.LOG_LINE_NUM > 3001:
            print('log超过3000行,%s' % self.LOG_LINE_NUM)
            del_target = float(self.LOG_LINE_NUM - 3000 - 1)
            self.tab[interface]['text'].delete(1.0, del_target)
        logmsg_in = "%s\n" % log_msg
        self.tab[interface]['text'].insert('end', logmsg_in)
        if self.active == interface and self.see_log:
            self.tab[interface]['text'].see('end')

    def thread_it(self, func, *args):
        """将函数打包进线程"""
        # 创建
        t = threading.Thread(target=func, args=args)
        # 守护 !!!
        t.setDaemon(True)
        # 启动
        t.start()

	# 将以下的操作都放进线程中执行,以放工具主界面卡死
    def start_socket_conn(self, *args):
        self.thread_it(self.socket_conn)

    def start_save_log(self, *args):
        self.thread_it(self.save_log)

    def start_change_serial(self, *args):
        self.thread_it(self.change_serial)

    def start_output_log(self, *args):
        self.thread_it(self.output_log)

    def start_send_data(self, ev=None, data=''):
        self.thread_it(self.send_data_to_server, data)
        return 'break'

    def start_stop_task(self, *args):
        self.thread_it(self.stop_recv_data)

    def start_quick_print(self, data=''):
        self.thread_it(self.quick_print, data)

    def start_write_log(self, log_msg):
        self.thread_it(self.write_log_to_Text, log_msg)

    def start_tab_rename(self, *args):
        self.thread_it(self.tab_rename)

root = tk.Tk()						# 创建Tk对象
app = Application(master=root)		# 创建Application类的对象app,主界面为root
root.title("串口收发客户端")			# 设置app标题
root.resizable(False, False)		# 禁止调整窗口大小

sw = root.winfo_screenwidth()   # 得到屏幕宽度
sh = root.winfo_screenheight()  # 得到屏幕高度
# 窗口宽高为1500x900
ww = 1500
wh = 900
x = (sw-ww) / 2
y = (sh-wh) / 2
root.geometry("%dx%d+%d+%d" % (ww, wh, x, y))       # 自适应居中

root.deiconify()        # 显示窗口
app.mainloop()          # 进入消息循环


举报

相关推荐

0 条评论