热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

python3tkinter实践历程(四)——基于socket通信与tkinter的TCP串口客户端(仿CRT)文章目录系列文章目录分享背景制作背景最终功能工具截图展示代码详解系列

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 &#61; socket.socket(socket.AF_INET, socket.SOCK_STREAM)print(&#39;开始连接串口服务器%s:%d&#39; % (ip, port))self.sock.connect((ip, port))print(&#39;连接完成&#39;)def get_serial_data(self):# 接收从服务器发送过来的日志&#xff0c;返回给上层recv_info &#61; self.sock.recv(7168).decode("utf-8", "replace") # 服务器传过来的是bytes类型,收到数据后编码变成str# print(recv_info)return recv_infodef send_data(self, data):# 将已经处理好的数据直接发送给服务器self.sock.send(data)class Application(tk.Frame):def __init__(self, master&#61;None):# 进行初始化&#xff0c;打开工具时完成的动作tk.Frame.__init__(self, master)self.grid()self.save_log_dir &#61; &#39;&#39; # 记录保存日志的路径self.is_running &#61; &#39;&#39; # 记录是否TCP交互正在进行self.LOG_LINE_NUM &#61; 0 # 记录日志框中打印的日志行数self.see_log &#61; True # 是否滚动条跟随最新打印的日志self.button_column &#61; 0 # 用于记录多个tab页所用过的column属性的最大值&#xff0c;以便于后新增的tab页可以出现在最后面。否则在多次删减过后&#xff0c;出现新增tab页生成位置异常的bugself.top &#61; &#39;&#39; # 重命名窗口&#xff0c;后续变成重命名窗口界面的对象self.tab &#61; {} # TAB集合&#xff0c;所有的TAB按钮与日志打印框的对象和显示的text文本都记录在这里&#xff0c;多tab页日志打印框的核心字典self.active &#61; &#39;&#39; # 当前置顶&#xff0c;记录当前切换的TAB&#xff0c;查看、发送、删除、重命名四个的操作都基于这个调用self.create_widgets() # 执行下面函数&#xff0c;创建图形化界面def create_widgets(self):"""创建图形化界面"""# 每一行用一个Frame包裹&#xff0c;里面的控件基于Frame来创建# 第一行row1 &#61; tk.Frame(self)row1.grid(row&#61;0, column&#61;0, sticky&#61;&#39;w&#39;, padx&#61;(15, 0), pady&#61;10)# IP端口输入框server_url &#61; tk.Label(row1, text&#61;"serial server IP and port(xx:xx)")server_url.grid(row&#61;0, column&#61;1)server_url &#61; tk.StringVar()server_url.set("10.100.10.180:9036") # set的作用的预置的文字(在标签内可变的文本)self.server_url &#61; tk.Entry(row1, textvariable&#61;server_url, width&#61;20)self.server_url.grid(row&#61;0, column&#61;2, padx&#61;2, pady&#61;2)# 日志存放目录tk.Label(row1, text&#61;"", width&#61;1).grid(row&#61;0, column&#61;3)tk.Label(row1, text&#61;"log dir").grid(row&#61;0, column&#61;4)self.dir &#61; tk.StringVar()self.dir.set("")self.log_dir &#61; tk.Entry(row1, textvariable&#61;self.dir, width&#61;35)self.log_dir.grid(row&#61;0, column&#61;5)tk.Button(row1, text&#61;"select directory", command&#61;self.selectPath).grid(row&#61;0, column&#61;6)# 开始按钮--连接服务器并接收保存日志tk.Label(row1, text&#61;"", width&#61;2).grid(row&#61;0, column&#61;7)start_btn &#61; tk.Button(row1, text&#61;"start connect and save log", command&#61;self.start_socket_conn).grid(row&#61;0, column&#61;8)# 停止按钮stop_btn &#61; tk.Button(row1, text&#61;"stop conn", command&#61;self.start_stop_task).grid(row&#61;0, column&#61;9, padx&#61;(10, 0))# 滚动条跟随日志复选框self.see_log_value &#61; tk.IntVar()self.see_log_value.set(1)tk.Checkbutton(row1, text&#61;"Follow log", variable&#61;self.see_log_value, command&#61;self.change_see_log).grid(row&#61;0, column&#61;14, padx&#61;20)# 第二、第三行都先创建一个Frame用来固定宽度# 第二行 tab页 非固定行self.row2 &#61; tk.Frame(self)self.row2.grid(row&#61;1, column&#61;0, sticky&#61;&#39;w&#39;, padx&#61;(15, 0))# 第二行的三个按钮: “&#43;”、“-”、“重命名”# 对应增加一个TAB页、删除当前置顶的TAB页、重命名当前TAB页的功能# “&#43;”绑定self.add_log_text方法来实现&#xff0c;“-”绑定self.del_log_text方法来实现&#xff0c;“重命名”绑定self.start_tab_rename方法来实现&#xff0c;三个函数均没有放进线程执行# 三个按钮的column属性分别为1000、1001、1002&#xff0c;目的是实现无论新增多少个TAB&#xff0c;这三个按钮都按顺序排在其他TAB按钮之后&#xff0c;具体展示可看图tk.Button(self.row2, text&#61;&#39;&#43;&#39;, relief&#61;&#39;solid&#39;, bd&#61;1, command&#61;self.add_log_text, width&#61;2).grid(row&#61;0, column&#61;1000, padx&#61;2)tk.Button(self.row2, text&#61;&#39;-&#39;, relief&#61;&#39;solid&#39;, bd&#61;1, command&#61;self.del_log_text, width&#61;2).grid(row&#61;0, column&#61;1001, padx&#61;2)tk.Button(self.row2, text&#61;&#39;重命名&#39;, relief&#61;&#39;solid&#39;, bd&#61;1, command&#61;self.start_tab_rename, width&#61;5).grid(row&#61;0, column&#61;1002, padx&#61;2)# 第三行 非固定行# 先创建一个Frame固定第三行&#xff0c;然后有多少个TAB就在这个Frame里的同一位置创建多少个Frame# 这样需要隐藏、显示、销毁这个界面的时候&#xff0c;只需控制里面的Frame完成grid(显示)、grid_forget(隐藏)、destroy(销毁)即可self.row3 &#61; tk.Frame(self, height&#61;650)self.row3.grid(row&#61;2, column&#61;0, sticky&#61;&#39;w&#39;, padx&#61;(15, 0))# 以下从外部文件interface_data.txt读取第二第三行(TAB按钮&#43;日志打印框)的内容# 调用work_for_data方法&#xff0c;返回字典interface_data &#61; self.work_for_data(&#39;tab&#39;, &#39;read&#39;)# 如果存在数据&#xff0c;则使用存在的数据来创建界面&#xff0c;如果不存在数据&#xff0c;则使用初始界面if not interface_data:# 初始界面为&#xff1a;{&#39;log1&#39;: {&#39;name_str&#39;: &#39;log1&#39;, &#39;serial_value&#39;: &#39;&#39;}}&#xff0c;即一个TAB&#xff0c;标识符为log1&#xff0c;输出在界面的名称也为log1&#xff0c;选择的串口号为空&#xff0c;即不选择interface_data &#61; {&#39;log1&#39;: {&#39;name_str&#39;: &#39;log1&#39;, &#39;serial_value&#39;: &#39;&#39;}}for name in interface_data:column &#61; self.button_column &#43; 1 # 每一次column &#43; 1&#xff0c;即在下一个位置创建控件# tab字典以这个遍历到的name作为key来创建新字典&#xff0c;来记录这个name名下所有的数据# 解释tab字典内的每一个key的含义&#xff1a;# tab[name][&#39;name&#39;] &#xff1a;字符串容器&#xff0c;用来修改显示在界面上的tab名称&#xff0c;如图的10.108&#xff0c;用set()设置&#xff0c;get()获取# tab[name][&#39;name_str&#39;] &#xff1a;字符串&#xff0c;记录显示在界面上的tab名称&#xff0c;如图的10.108# tab[name][&#39;c_button&#39;]&#xff1a;作为第二行的TAB的对象&#xff0c;绑定change_interface方法&#xff0c;来更改置顶的TAB# tab[name][&#39;Frame&#39;]&#xff1a;作为第三行的Frame里面的Frame&#xff0c;控制name名下的第三行全部控件&#xff0c;只需对该对象使用grid(显示)、grid_forget(隐藏)、destroy(销毁)方法即可# tab[name][&#39;text&#39;]: Text框(日志打印框)的对象&#xff0c;该TAB日志框的增删都通过该对象操作# tab[name][&#39;serial&#39;]&#xff1a;串口选择下拉框的对象# tab[name][&#39;serial&#39;][&#39;value&#39;]&#xff1a;串口下拉框的所有选项self.tab[name] &#61; dict()self.tab[name][&#39;name&#39;] &#61; tk.StringVar()self.tab[name][&#39;name&#39;].set(interface_data[name][&#39;name_str&#39;])self.tab[name][&#39;name_str&#39;] &#61; interface_data[name][&#39;name_str&#39;]self.tab[name][&#39;c_button&#39;] &#61; tk.Button(self.row2, textvariable&#61;self.tab[name][&#39;name&#39;], relief&#61;&#39;raised&#39;,bd&#61;1, command&#61;lambda _name&#61;name: self.change_interface(_name))self.tab[name][&#39;c_button&#39;].grid(row&#61;0, column&#61;column)self.button_column &#61; column # 刷新self.button_column的值&#xff0c;等于当前columnself.tab[name][&#39;Frame&#39;] &#61; tk.Frame(self.row3)self.tab[name][&#39;Frame&#39;].grid(row&#61;0, column&#61;0)ft &#61; tf.Font(size&#61;10)self.tab[name][&#39;text&#39;] &#61; tk.Text(self.tab[name][&#39;Frame&#39;], width&#61;180, height&#61;50, font&#61;ft, padx&#61;5, pady&#61;5,relief&#61;&#39;sunken&#39;)log_slide_bar &#61; tk.Scrollbar(self.tab[name][&#39;Frame&#39;], command&#61;self.tab[name][&#39;text&#39;].yview,orient&#61;"vertical") # 日志框的滚动条log_slide_bar.grid(row&#61;0, column&#61;1, sticky&#61;&#39;ns&#39;)self.tab[name][&#39;text&#39;].grid(row&#61;0, column&#61;0)self.tab[name][&#39;text&#39;].config(font&#61;ft, yscrollcommand&#61;log_slide_bar.set)tk.Label(self.tab[name][&#39;Frame&#39;], text&#61;"select serial").grid(row&#61;0, column&#61;2, sticky&#61;&#39;n&#39;, padx&#61;(0, 5))self.tab[name][&#39;serial&#39;] &#61; ttk.Combobox(self.tab[name][&#39;Frame&#39;], state&#61;&#39;readonly&#39;, width&#61;10)self.tab[name][&#39;serial&#39;].grid(row&#61;0, column&#61;3, sticky&#61;&#39;n&#39;)self.tab[name][&#39;serial_value&#39;] &#61; interface_data[name][&#39;serial_value&#39;]self.tab[name][&#39;serial&#39;][&#39;value&#39;] &#61; (&#39;serial01&#39;, &#39;serial02&#39;, &#39;serial03&#39;, &#39;serial04&#39;, &#39;serial05&#39;, &#39;serial06&#39;,&#39;serial07&#39;, &#39;serial08&#39;, &#39;serial09&#39;, &#39;serial10&#39;, &#39;serial11&#39;, &#39;serial12&#39;,&#39;serial13&#39;, &#39;serial14&#39;, &#39;serial15&#39;, &#39;serial16&#39;, &#39;serial17&#39;, &#39;serial18&#39;,&#39;serial19&#39;, &#39;serial20&#39;, &#39;serial21&#39;, &#39;serial22&#39;, &#39;serial23&#39;, &#39;serial24&#39;)self.tab[name][&#39;serial&#39;].bind(&#39;<>&#39;, self.start_change_serial)if interface_data[name][&#39;serial_value&#39;]:self.tab[name][&#39;serial&#39;].set(interface_data[name][&#39;serial_value&#39;])# TAB全部生成完成&#xff0c;选择一个TAB作为置顶&#xff0c;显示该TAB的第三行&#xff0c;同时按钮设置成不可操作。其他TAB的第三行隐藏&#xff0c;按钮设置成可操作for i in self.tab:if not self.active:self.active &#61; iself.tab[i][&#39;Frame&#39;].grid()self.tab[i][&#39;c_button&#39;].config(state&#61;tk.DISABLED)print(&#39;置顶%s界面&#39; % i)else:self.tab[i][&#39;Frame&#39;].grid_forget()self.tab[i][&#39;c_button&#39;].config(state&#61;tk.ACTIVE)# 第四行&#xff08;命令输入框&#43;发送按钮&#xff09;row4 &#61; tk.Frame(self)row4.grid(row&#61;3, column&#61;0, sticky&#61;&#39;w&#39;, padx&#61;(15, 0), pady&#61;(15, 0))# 命令输入框ft &#61; tf.Font(size&#61;10)self.log_input &#61; tk.Text(row4, width&#61;100, height&#61;4, font&#61;ft)self.log_input.grid(row&#61;0, column&#61;1)self.log_input.bind(&#39;&#39;, self.start_send_data) # text框绑定回车键&#xff0c;当收到回车键就触发start_send_data方法# 注意&#xff1a;self.start_send_data以return &#39;break&#39;结尾&#xff0c;则回车的换行效果不会在文本框触发&#xff0c;如果没有&#xff0c;则会触发换行tk.Button(row4, text&#61;"send info", command&#61;self.start_send_data).grid(row&#61;0, column&#61;2, padx&#61;10)# 第五行 (快速发送命令按钮)row5 &#61; tk.Frame(self)row5.grid(row&#61;4, column&#61;0, sticky&#61;&#39;w&#39;, padx&#61;(15, 0), pady&#61;(15, 0))# 快速发送命令按钮# 只能从外部的quick_send_data.txt文件获取&#xff0c;不能在界面上添加quick_send_data &#61; self.work_for_data(&#39;quick_send&#39;) 调用work_for_data方法获取文本中的信息if quick_send_data:print(quick_send_data)column &#61; 0for data in quick_send_data:# 解析出文本的信息&#xff0c;遍历内容构建按钮&#xff0c;按钮绑定self.start_quick_print方法# data是一个字典&#xff0c;包含了显示在界面的按钮名称和执行的命令。# 将data2&#61;data&#xff0c;然后把data2传入self.start_quick_print方法tk.Button(row5, text&#61;data[&#39;name&#39;], command&#61;lambda data2&#61;data: self.start_quick_print(data2)).grid(row&#61;0, column&#61;column, padx&#61;(0, 10))column &#61; column &#43; 1def add_log_text(self):"""增加多个tab页"""# 创建界面完成后&#xff0c;点击“&#43;”按钮触发此函数&#xff0c;新增TAB按钮与第三行的日志框&#xff0c;方法与create_widgets中第二第三行的逻辑大同小异now_log_text_num &#61; 0for ii in range(1, 1000):if &#39;log&#39; &#43; str(ii) not in self.tab:now_log_text_num &#61; iibreakelse:now_log_text_num &#61; 1000column &#61; self.button_column &#43; 1name &#61; &#39;log&#39; &#43; str(now_log_text_num)self.tab[name] &#61; dict()self.tab[name][&#39;name&#39;] &#61; tk.StringVar()self.tab[name][&#39;name&#39;].set(name)self.tab[name][&#39;name_str&#39;] &#61; nameself.tab[name][&#39;c_button&#39;] &#61; tk.Button(self.row2, textvariable&#61;self.tab[name][&#39;name&#39;], relief&#61;&#39;raised&#39;, bd&#61;1, command&#61;lambda _name&#61;name: self.change_interface(_name))self.tab[name][&#39;c_button&#39;].grid(row&#61;0, column&#61;column)self.button_column &#61; columnself.tab[name][&#39;Frame&#39;] &#61; tk.Frame(self.row3)self.tab[name][&#39;Frame&#39;].grid(row&#61;0, column&#61;0)ft &#61; tf.Font(size&#61;10)self.tab[name][&#39;text&#39;] &#61; tk.Text(self.tab[name][&#39;Frame&#39;], width&#61;180, height&#61;50, font&#61;ft, padx&#61;5, pady&#61;5, relief&#61;&#39;sunken&#39;)log_slide_bar &#61; tk.Scrollbar(self.tab[name][&#39;Frame&#39;], command&#61;self.tab[name][&#39;text&#39;].yview,orient&#61;"vertical")log_slide_bar.grid(row&#61;0, column&#61;1, sticky&#61;&#39;ns&#39;)self.tab[name][&#39;text&#39;].grid(row&#61;0, column&#61;0)self.tab[name][&#39;text&#39;].config(font&#61;ft, yscrollcommand&#61;log_slide_bar.set)tk.Label(self.tab[name][&#39;Frame&#39;], text&#61;"select serial").grid(row&#61;0, column&#61;2, sticky&#61;&#39;n&#39;, padx&#61;(0, 5))self.tab[name][&#39;serial&#39;] &#61; ttk.Combobox(self.tab[name][&#39;Frame&#39;], state&#61;&#39;readonly&#39;, width&#61;10)self.tab[name][&#39;serial&#39;].grid(row&#61;0, column&#61;3, sticky&#61;&#39;n&#39;)self.tab[name][&#39;serial_value&#39;] &#61; &#39;&#39;self.tab[name][&#39;serial&#39;][&#39;value&#39;] &#61; (&#39;serial01&#39;, &#39;serial02&#39;, &#39;serial03&#39;, &#39;serial04&#39;, &#39;serial05&#39;, &#39;serial06&#39;,&#39;serial07&#39;, &#39;serial08&#39;, &#39;serial09&#39;, &#39;serial10&#39;, &#39;serial11&#39;, &#39;serial12&#39;,&#39;serial13&#39;, &#39;serial14&#39;, &#39;serial15&#39;, &#39;serial16&#39;, &#39;serial17&#39;, &#39;serial18&#39;,&#39;serial19&#39;, &#39;serial20&#39;, &#39;serial21&#39;, &#39;serial22&#39;, &#39;serial23&#39;, &#39;serial24&#39;)self.tab[name][&#39;serial&#39;].bind(&#39;<>&#39;, self.start_change_serial)self.active &#61; namefor i in self.tab:if i &#61;&#61; self.active:self.tab[i][&#39;Frame&#39;].grid()self.tab[i][&#39;c_button&#39;].config(state&#61;tk.DISABLED)print(&#39;置顶%s界面&#39; % i)else:self.tab[i][&#39;Frame&#39;].grid_forget()self.tab[i][&#39;c_button&#39;].config(state&#61;tk.ACTIVE)# 存放tab信息result &#61; self.work_for_data(&#39;tab&#39;, &#39;write&#39;)if result:print(&#39;存放tab信息与interface_data.txt完成&#39;)def del_log_text(self):"""删除当前置顶的tab页"""# 先换一个置顶的tab页面if len(self.tab) &#61;&#61; 1:print(&#39;只有一个tab页&#xff0c;不可销毁&#39;)returntarget &#61; self.active# 换一个置顶的TAB&#xff0c;该TAB显示&#xff0c;按钮不可用&#xff0c;其他TAB隐藏&#xff0c;按钮可用for i in self.tab:if i !&#61; self.active:self.active &#61; iself.tab[i][&#39;Frame&#39;].grid()self.tab[i][&#39;c_button&#39;].config(state&#61;tk.DISABLED)print(&#39;置顶%s界面&#39; % i)break# 销毁目标的第三行、与TAB按钮self.tab[target][&#39;Frame&#39;].destroy()self.tab[target][&#39;c_button&#39;].destroy()del self.tab[target]# 存放tab信息result &#61; self.work_for_data(&#39;tab&#39;, &#39;write&#39;)if result:print(&#39;存放tab信息与interface_data.txt完成&#39;)def tab_rename(self):"""给tab页面重命名"""try:if self.top: # 如果重命名窗口已经存在&#xff0c;就销毁这个窗口self.top.destroy()self.top &#61; &#39;&#39;self.top &#61; tk.Toplevel() # 创建最上层的窗口self.top.title(&#39;重命名&#39;) # 给该窗口命令self.top.transient(self) # Toplevel注册成master的临时窗口self.top.resizable(0, 0) # 不可改变大小# 居中width &#61; self.winfo_screenwidth()height &#61; self.winfo_screenheight()ww &#61; 150wh &#61; 75x &#61; (width-ww)/2y &#61; (height-wh)/2self.top.geometry("%dx%d&#43;%d&#43;%d" % (ww, wh, x, y)) # 自适应居中self.top.grid()# 重命名输入框name &#61; tk.StringVar()name.set(self.tab[self.active][&#39;name_str&#39;]) # 把该TAB按钮原来的名字显示出来self.rename_entry &#61; tk.Entry(self.top, textvariable&#61;name)self.rename_entry.grid(row&#61;0, column&#61;0, columnspan&#61;2, ipady&#61;5)# 确定按钮 # 绑定self.control_rename_btn方法self.rename_sure_btn &#61; tk.Button(self.top, text&#61;&#39;确定&#39;, command&#61;lambda : self.control_rename_btn(&#39;确定&#39;))self.rename_sure_btn.grid(row&#61;1, column&#61;0, padx&#61;10, pady&#61;5)# 取消按钮self.rename_cancel_btn &#61; tk.Button(self.top, text&#61;&#39;取消&#39;, command&#61;lambda : self.control_rename_btn(&#39;取消&#39;))self.rename_cancel_btn.grid(row&#61;1, column&#61;1, padx&#61;10, pady&#61;5)except Exception as e:print(&#39;初始化重命名界面异常:%s&#39; % e)def control_rename_btn(self, operate):"""控制重命名的确定取消按钮"""# self.tab[self.active][&#39;name&#39;]为该TAB按钮绑定的字符串容器&#xff0c;set()方法直接改变显示的名称&#xff0c;最后销毁重命名窗口if operate &#61;&#61; &#39;确定&#39;:if self.rename_entry.get():self.tab[self.active][&#39;name_str&#39;] &#61; self.rename_entry.get()self.tab[self.active][&#39;name&#39;].set(self.rename_entry.get())self.top.destroy()if operate &#61;&#61; &#39;取消&#39;:self.top.destroy()def socket_conn(self):"""与串口服务器建立TCP连接,无限循环获取数据&#xff0c;放进线程中执行"""ip_port &#61; self.server_url.get().split(&#39;:&#39;) # 获取TCP服务器IP和端口ip &#61; ip_port[0]port &#61; int(ip_port[1])global sock_conn # 创建全局变量&#xff0c;为SocketServer类的对象sock_conn &#61; SocketServer(ip, port)self.is_running &#61; True # 将 self.is_running设置为Truewhile True:if self.is_running: # 如果self.is_running被设置为False时&#xff0c;退出循环try:recv_info &#61; sock_conn.get_serial_data() # 获取数据except Exception as e:print(&#39;获取数据异常:%s&#39; % e)continuetry:# 以下为解析数据的方法&#xff0c;根据TCP服务器已经设定好的规则处理接收的数据&#xff0c;分解出日志本身与对应的串口parse_rule &#61; re.compile(r&#39;\*<\[(\d{2})]>\*&#39;) parse_result &#61; parse_rule.search(recv_info)if parse_result:log_info &#61; recv_info.replace(parse_result.group(0), &#39;&#39;)serial &#61; &#39;serial&#39; &#43; parse_result.group(1)else:print(&#39;串口标识识别失败:%s&#39; % recv_info)continueexcept Exception as e:print(&#39;解析数据异常:%s,异常数据:%s&#39; % (e, recv_info))continuetry:self.save_log(log_info, serial) # 保存该行日志except Exception as e:print(&#39;保存日志异常:%s&#39; % e)continuetry:self.output_log(log_info, serial) # 打印该行日志except Exception as e:print(&#39;输出日志异常:%s&#39; % e)continueelse:print(&#39;stopped&#39;)breakdef selectPath(self):"""把获取到的串口目录传入Entry"""dir_ &#61; askdirectory()self.dir.set(str(dir_))self.save_log_dir &#61; self.log_dir.get()def save_log(self, log_info, serial_port):"""把收到的日志分别传入不同的log文本中"""if not self.save_log_dir:# print(&#39;无传入日志目录&#39;)return# print(&#39;获取到的日志目录为:%s&#39; % self.save_log_dir)now_day &#61; datetime.datetime.now().strftime(&#39;%Y-%m-%d&#39;)# print(&#39;当前日期为:%s&#39; % now_day)log_folder &#61; os.path.join(self.save_log_dir, now_day)# print(&#39;存放今天日志目录为:%s&#39; % log_folder)if not os.path.exists(log_folder):# print(&#39;不存在%s文件夹,进行新建&#39; % log_folder)os.mkdir(log_folder)log_file &#61; os.path.join(log_folder, &#39;%s_%s.log&#39; % (serial_port, now_day))log_info &#61; log_info.rstrip(&#39;\n&#39;)with open(log_file, &#39;a&#43;&#39;, errors&#61;&#39;ignore&#39;, newline&#61;&#39;&#39;) as f:f.write(log_info)def change_serial(self):"""更改输出日志的串口"""# 当串口下拉框被操作时&#xff0c;会将当前置顶的TAB字典中的serial_value的值变成选择的串口&#xff0c;并且清空该TAB的日志框if self.tab[self.active][&#39;serial_value&#39;] !&#61; self.tab[self.active][&#39;serial&#39;].get():self.tab[self.active][&#39;serial_value&#39;] &#61; self.tab[self.active][&#39;serial&#39;].get()self.tab[self.active][&#39;text&#39;].delete(0.0, &#39;end&#39;)print(&#39;更改%s的输出串口为:%s&#39; % (self.active, self.tab[self.active][&#39;serial_value&#39;]))# 存放tab信息&#xff0c;因为有TAB的串口发生变成&#xff0c;所以把当前tab字典更新在interface_data.txt文件result &#61; self.work_for_data(&#39;tab&#39;, &#39;write&#39;)if result:print(&#39;存放tab信息与interface_data.txt完成&#39;)def change_interface(self, interface):"""显示界面"""for i in self.tab:if i &#61;&#61; interface:self.tab[interface][&#39;Frame&#39;].grid()self.tab[interface][&#39;c_button&#39;].config(state&#61;tk.DISABLED)else:self.tab[i][&#39;Frame&#39;].grid_forget()self.tab[i][&#39;c_button&#39;].config(state&#61;tk.ACTIVE)self.active &#61; interfaceprint(&#39;置顶%s:%s页面&#39; % (interface, self.tab[interface][&#39;name&#39;].get()))def work_for_data(self, key, work&#61;&#39;&#39;):"""存/取tab字典&#xff0c;取快捷输入数据"""try:if key &#61;&#61; &#39;tab&#39;:if work &#61;&#61; &#39;write&#39;:self.save_tab &#61; {}for i in self.tab:self.save_tab[i] &#61; {}self.save_tab[i][&#39;name_str&#39;] &#61; self.tab[i][&#39;name_str&#39;]self.save_tab[i][&#39;serial_value&#39;] &#61; self.tab[i][&#39;serial_value&#39;]with open(&#39;interface_data.txt&#39;, &#39;w&#39;) as f:data &#61; json.dumps(self.save_tab)f.write(data)return Trueif work &#61;&#61; &#39;read&#39;:with open(&#39;interface_data.txt&#39;, &#39;r&#39;) as f:data &#61; f.read()data &#61; json.loads(data)print(&#39;读取到的tab:%s&#39; % data)return dataif key &#61;&#61; &#39;quick_send&#39;:data_list &#61; []with open(&#39;quick_send_data.txt&#39;, &#39;r&#39;) as f:for i in f:if &#39;*start-&#39; in i:info &#61; {}l &#61; i.replace(&#39;*start-&#39;, &#39;&#39;)name &#61; l[0:l.rfind(&#39;&#61;&#39;)]l &#61; l[l.rfind(&#39;&#61;&#39;) &#43; 1:]if &#39;\enter&#39; in l:enter &#61; Truel &#61; l.replace(&#39;\enter&#39;, &#39;&#39;)else:enter &#61; Falseif &#39;\n&#39; in l:l &#61; l.replace(&#39;\n&#39;, &#39;&#39;)l &#61; l.split(&#39;;&#39;)info[&#39;data&#39;] &#61; linfo[&#39;name&#39;] &#61; nameinfo[&#39;enter&#39;] &#61; enterdata_list.append(info)return data_listexcept Exception as e:print(&#39;对%s进行%s操作异常:%s&#39; % (key, work, e))def output_log(self, log_info, serial_port):"""实时输出日志"""for i in self.tab:if self.tab[i][&#39;serial_value&#39;] &#61;&#61; serial_port:self.write_log_to_Text(i, log_info)breakdef stop_recv_data(self):self.is_running &#61; Falseprint(&#39;stopping&#39;)def send_data_to_server(self, data&#61;&#39;&#39;):"""发送数据给服务器"""try:if not self.tab[self.active][&#39;serial_value&#39;]:print(&#39;无指定串口&#39;)returnif data:print(&#39;有指定数据:%s&#39; % data)else:data &#61; self.log_input.get(0.0, &#39;end&#39;)self.log_input.delete(0.0, &#39;end&#39;)if not data:print(&#39;无数据&#39;)returnif data[-1] not in [&#39;\n&#39;, &#39;\r&#39;]:print(&#39;给数据加回车&#39;)data &#61; data &#43; &#39;\n&#39;print(&#39;给%s发送%s指令&#39; % (self.tab[self.active][&#39;serial_value&#39;], data))data &#61; &#39;*<[%s]>*&#39; % self.tab[self.active][&#39;serial_value&#39;][-2:] &#43; datasock_conn.send_data(bytes(data, encoding&#61;&#39;utf8&#39;))except Exception as e:print(&#39;发送数据异常:%s&#39; % e)def quick_print(self, data):if data:if len(data[&#39;data&#39;]) &#61;&#61; 1: # 只有一条数据if data[&#39;enter&#39;]: # 自动发送self.start_send_data(data&#61;data[&#39;data&#39;][0])else: # 打印出来&#xff0c;不自动发送self.log_input.insert(&#39;end&#39;, data[&#39;data&#39;][0])else: # 大于1条数据,需要发送多次for i in data[&#39;data&#39;]:if i &#61;&#61; &#39;p&#39;:print(&#39;sleep(1)&#39;)time.sleep(1)else:if data[&#39;enter&#39;]:self.start_send_data(data&#61;i)else:self.log_input.insert(&#39;end&#39;, i)def change_see_log(self):if self.see_log_value.get() in [&#39;1&#39;, 1]:self.see_log &#61; Trueprint(&#39;切换成跟随日志&#39;)else:self.see_log &#61; Falseprint(&#39;切换成不跟随日志&#39;)def write_log_to_Text(self, interface, log_msg):"""日志动态打印"""self.LOG_LINE_NUM &#61; int(self.tab[interface][&#39;text&#39;].index(&#39;end&#39;).split(&#39;.&#39;)[0]) - 1if self.LOG_LINE_NUM > 3001:print(&#39;log超过3000行&#xff0c;%s&#39; % self.LOG_LINE_NUM)del_target &#61; float(self.LOG_LINE_NUM - 3000 - 1)self.tab[interface][&#39;text&#39;].delete(1.0, del_target)logmsg_in &#61; "%s\n" % log_msgself.tab[interface][&#39;text&#39;].insert(&#39;end&#39;, logmsg_in)if self.active &#61;&#61; interface and self.see_log:self.tab[interface][&#39;text&#39;].see(&#39;end&#39;)def thread_it(self, func, *args):"""将函数打包进线程"""# 创建t &#61; threading.Thread(target&#61;func, args&#61;args)# 守护 !!!t.setDaemon(True)# 启动t.start()# 将以下的操作都放进线程中执行&#xff0c;以放工具主界面卡死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&#61;None, data&#61;&#39;&#39;):self.thread_it(self.send_data_to_server, data)return &#39;break&#39;def start_stop_task(self, *args):self.thread_it(self.stop_recv_data)def start_quick_print(self, data&#61;&#39;&#39;):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 &#61; tk.Tk() # 创建Tk对象
app &#61; Application(master&#61;root) # 创建Application类的对象app&#xff0c;主界面为root
root.title("串口收发客户端") # 设置app标题
root.resizable(False, False) # 禁止调整窗口大小sw &#61; root.winfo_screenwidth() # 得到屏幕宽度
sh &#61; root.winfo_screenheight() # 得到屏幕高度
# 窗口宽高为1500x900
ww &#61; 1500
wh &#61; 900
x &#61; (sw-ww) / 2
y &#61; (sh-wh) / 2
root.geometry("%dx%d&#43;%d&#43;%d" % (ww, wh, x, y)) # 自适应居中root.deiconify() # 显示窗口
app.mainloop() # 进入消息循环

推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • 本文介绍了django中视图函数的使用方法,包括如何接收Web请求并返回Web响应,以及如何处理GET请求和POST请求。同时还介绍了urls.py和views.py文件的配置方式。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 本文介绍了在CentOS 7.x上进行端口映射配置的方法,通过修改内核和配置防火墙实现端口映射。作者分享了自己使用华为服务器进行端口映射的经验,发现网速比直连还快且稳定。详细的配置过程包括开启系统路由模式功能、设置IP地址伪装、设置端口映射等。同时,还介绍了如何监听本地端口的tcp请求,以及删除规则和开放的端口的方法。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
虛情徦噫d_951
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有