python上位机开发经验总结01
文章目录
- python上位机开发经验总结01
- python变量与文件的处理
- 全局变量与局部变量
- 文件间的变量处理
- threading模块使用经验
- 管理线程
- 定义线程
- tkinter使用经验
- tkinter显示视频
- Frame的使用以及各种摆放方式
- PanedWindow配合LabelFrame
- 下拉框与输入框
- 下拉框
- 输入框
- tkinter.after()实现定时器
- 动态的显示:StringVar()
- 带滑条的Text
- 好看的字体设置
- tkinter修改关闭窗口操作
- 其它零零碎碎的经验
- OpenCV实现点击'X'关闭窗口
- 打包文件
- pyinstaller错误:RecursionError: maximum recursion depth exceeded
python上位机开发经验总结01
魔方机器人比赛中使用python写了上位机,总结一些经验待以后翻看。主要分4个方面,python变量与文件的处理,threading多线程模块使用经验,tkinter使用经验,其它零零碎碎的经验
python变量与文件的处理
主要是单文件内变量的处理经验与多文件内变量的处理经验。
全局变量与局部变量
python中的局部变量产生在函数内,在函数返回之后被释放,可以(但不建议)与全局变量同名。全局变量只能在模块(文件)级别使用,不能跨模块使用。
python中的全局变量在使用时有两个注意
- 全局变量需要在模块级别定义
- 在函数中修改全局变量时需要先用修饰词
global
声明
当函数中没有对变量作global
修饰时,有两种情况
- 函数中只对变量作读取操作,则可以直接读取在模块级别定义的同名变量
- 函数中对变量作了写(修改)操作,则认为此变量为局部变量,即使变量与模块级别定义的变量同名,也会新建局部变量而不会去修改同名变量
如以下的例子
flag = 1def false_reset_flag():flag = 0def right_reset_flag():global flagflag = 0false_reset_flag()
print(flag)
right_reset_flag()
print(flag)
以上程序的执行结果为
1
0
并且,把变量传入函数之后进行修改也是不可行的,python会将传入的变量复制作为局部参数。但是如果在程序中需要很多flag(标志变量),那么逐一定义并修改全局变量是非常不优雅的,我们可以采用传入实例,调用实例中的函数修改自己的变量的方法。终究,python还是面向对象的语言。如以下例子
flag = 1class FlagClass(object):def __init__(self):self.flag = 1def reset_flag(self):self.flag = 0def false_reset(x):x = 0def reset(x):x.flag = 0false_reset(flag)
print(flag)
flag_class = FlagClass()
reset(flag_class)
print(flag_class.flag)
以上程序的执行结果为
1
0
文件间的变量处理
上面其实已经提到了,python最优雅的编程方式是面向对象编程,模块(文件)间的处理也是一样。最好把每个模块中的东西全部封装成类,实现模块之间的解耦。但是有的时候(尤其是完成一个特定的算法和工作流程)面向过程的编程更为方便,这时候我们就需要去处理模块之间的很多flag(标志变量)了。
首先我们要明确一个核心思想:模块之间是不能相互写(修改)变量的。如果从其它模块import
了某个变量,实际上是把这个变量复制过来,作为一个本模块内的“局部变量”。所以我们模块间修改变量的策略也是和封装成类差不多:把一个模块看成一个庞大的实体,在模块内定义修改自己变量的函数,在其它模块中调用这个函数。如以下例子
# module1.pyflag = 1def reset_flag():global flagflag = 0# 下面是需要调用的函数,里面会修改flag的值来标志有没有完成
......
# module2.pyfrom module1 import flag, reset_flag
print('flag')if flag:print('动作完成,重置标志')reset_flag()
from module1 import flag
print('flag')
运行module2得到的结果如下
1
动作完成,重置标志
0
threading模块使用经验
threading是python提供的在底层的_threading基础上封装好的使用方便的多线程模块,但是这个模块并不太完善。
之所以说它使用方便,是因为它可以方便地创建线程,且线程中可以直接修改全局变量(像上面说的一样用global
声明即可);说它不方便是因为它没有办法很好地管理线程(我的意思是很简单方便,有能力自己设计机制管理的大佬请忽略),如果你的线程中运行的是一个死循环,那么一旦线程开始,就处于失控状态,再也无法结束。
分享我本次使用threading模块的2个经验。
管理线程
下面是我在本次工程中管理线程的经验,虽然并不是一个非常优雅的方法,但是还算好用。核心思想是定义一个线程的flag,定义成全局变量还是定义在类里可以根据实际情况决定。把死循环改为一个带判断的while循环,那么结束线程时只需修改flag的值即可。如以下例子
class COM(object):def __init__(self, port, baud):self.port = portself.baud = int(baud)self.com = serial.Serial()self.recvBuffer = Queue(maxsize=10)self.__recv_running = True # 标志接收线程是否运行self.recvThread = threading.Thread()def open(self):try:self.com = serial.Serial(self.port, self.baud)except Exception:print('Open COM failed: {}'.format(self.port))def close(self):if self.com is not None and self.com.isOpen():self.com.close()self.stop_receive()def isOpen(self):return self.com.isOpen()def activate_receive(self):def receive_loop():while self.__recv_running:temp = self.com.read()if temp in serial_dict:self.recvBuffer.put(serial_dict[temp])time.sleep(0.02)self.recvThread = threading.Thread(target=receive_loop)self.recvThread.start()def stop_receive(self):self.__recv_running = False
以上是我在本次工程中定义的串口类,其中接收串口数据就是用了单独的一个线程。在类中我定义了__recv_running
变量来标志接收线程是否运行,在线程的target
函数中每次循环都取判断__recv_running
的值,需要结束线程时只需调用stop_receive
修改__recv_running
的值,那么接收线程执行完当前循环就会自动结束。
当然你应该发现了,修改标志变量值之后需要等待线程中本次循环结束,线程才能结束,无法强行杀死线程。一方面,这样结束线程可能效率不高;另一方面,这样结束线程对程序的冲击最小。
定义线程
在定义线程时,最好把线程的函数定义在启动的函数内,这样程序的可读性有很大的提高。这一点在上面的代码中就可以看出,这里我把它截取出来
def activate_receive(self):def receive_loop():while self.__recv_running:temp = self.com.read()if temp in serial_dict:self.recvBuffer.put(serial_dict[temp])time.sleep(0.02)self.recvThread = threading.Thread(target=receive_loop)self.recvThread.start()
tkinter使用经验
这次的工程中我用tkinter绘制了一个界面,先上图
如果你还没有学习tkinter,并且时间充裕的话,我建议你使用pyQt。tkinter虽然上手非常快(从学开始到编写完这个界面我花了2天),但是它所有的排版都需要你手动敲代码,相比于pyQt的画图而言,就显得太不人性化了。
本次工程中,关于tkinter的资料搜索量是最大的,总结了以下几点经验。
在这次的工程中我参考了以下网页:Python 实现串口调试助手
tkinter显示视频
这个问题面向百度和CSDN搜索了很久,没有一个非常好的解答。最后在python摄像头视频显示到TK窗口改良版找到的代码基础上改良了一下,很好地实现了视频显示。因为找到的代码是需要付积分下载的,原本我并不想放出源码,但是抱着python全部开源的精神,在此我放出源码并配上讲解,希望各位帅哥美女留下一个赞再走。
import cv2 as cv
from PIL import Image, ImageTk
import threading
from queue import Queue# 放置画布
canvases = []
for i in range(4):canvases.append(tk.Canvas(FRAME_cameras, bg='#ffffff', height=240, width=320))
x = [0, 0, 1, 1]
y = [0, 1, 0, 1]
for i in range(4):canvases[i].grid(row=x[i], column=y[i], sticky='nsew')# 下面的代码解决视频流的显示和关闭
show_cam_flag = Truedef stop_show_cam_thread():"""shut down the show_cameras thread:return: None"""global show_cam_flagshow_cam_flag = False# show camera flows
def show_cameras():def cc():imgQueue = Queue(maxsize=10) # 使用队列存储读取的图像,解决视频闪烁的问题global show_cam_flagwhile show_cam_flag:for i in range(4):ret, frame = cameras[i].read()cv_image = cv.cvtColor(frame, cv.COLOR_BGR2RGB)image = Image.fromarray(cv_image)image_file = ImageTk.PhotoImage(image)imgQueue.put(image_file)canvases[i].create_image(0, 0, anchor='nw', image=image_file, tags='c1')if imgQueue.full():imgQueue.get() # 队满则出队一个,防止内存占用太大程序崩溃time.sleep(0.04)del imgQueueshow_cam_flag = True # 关闭视频流后重新置一,避免无法再次打开t = threading.Thread(target=cc)t.start()
核心的显示思想是使用Canvas来显示图片(因为用Label不断刷新图片,内存会过大,程序会崩溃),用单独的一个线程不断地读取摄像头并刷新Canvas显示的图片。因为OpenCV读取的图像格式没有办法直接显示,所以用其它的库处理了一下。
需要注意的是,如果对显示的图片不加以保存的话,每次刷新图片时之前的图片就会被释放,导致显示的视频闪烁。最早找到的代码把图片保存在了字典中(我不太懂为什么要这样操作,可能是试出来的吧),这样做显然是很不优雅的,而且不定期进行删除操作,一定会导致内存过大程序崩溃(和Label就没有区别了)。所以我定义了一个队列imgQueue
来存储图片,每个循环都会检查队列并处理溢出的图片,最终在关闭视频流时删除这个队列。这应该是非常优雅的方法了,欢迎各位交流更好的方法。
Frame的使用以及各种摆放方式
这一次我主要使用了tkinter的三种排版方式:tkinter.pack(), tkinter.grid(), tkinter.place()。tkinter的排版还是比较麻烦的,全部使用place肯定非常不优雅。想要整齐的排版,就需要多建立一些Frame,然后3个函数配合使用。这里贴上我的设计
具体怎么写就不赘述了,提几点小建议
- 在新建完Frame之后直接放置,这样排版和实现功能的代码可以分开
- 在开始绘制的时候再新建Frame,否则写多了容易找不到
- 另外,注意实例的命名格式,个人使用的是全拼大写开头,小驼峰表征位置和功能,例如
FRAME_left
,ENTRY_release
,TEXT_status
PanedWindow配合LabelFrame
个人认为这些划线框让界面看起来更有条理,它们的实现方式其实也非常简单,如标题一样用PanedWindow配合LabelFrame即可。可参考如下代码
PAN_com = tk.PanedWindow(FRAME_r_up, orient=tk.VERTICAL, height=180)
PAN_init = tk.PanedWindow(FRAME_r_up, orient=tk.VERTICAL, height=180)FRAME_com = tk.LabelFrame(PAN_com, text="串口设置")
FRAME_init = tk.LabelFrame(PAN_init, text="转换初始化")PAN_com.add(FRAME_com)
PAN_com.pack(side=tk.LEFT)
PAN_init.add(FRAME_init)
PAN_init.pack(side=tk.RIGHT)
然后把要加的东西加到LabelFrame里就行了,即上面的FRAME_com
和FRAME_init
里。
下拉框与输入框
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQuEECl8-1601652622895)(./pictures/PanedWindow.png)]
还是这张图,里面的下拉框和输入框是不是也很简介美观,下面是实现它们的方法。
下拉框
tkinter.Combobox()个人认为是不大好用的,我使用了tkinter.ttk中的Combobox(),可以这样实现
from tkinter import ttkport_list = list(list_ports.comports())
serial_com_list = []
# varPort = tk.StringVar()
COMBO_com_com = ttk.Combobox(FRAME_com, width=8, height=2, justify=tk.CENTER)
for port in port_list:tmp_port = list(port)serial_com_list.append(tmp_port[0])
COMBO_com_com.grid(row=0, column=1)
COMBO_com_com['values'] = serial_com_listdef refresh_com():global port_list, serial_com_listport_list = list(list_ports.comports())serial_com_list = []for port in port_list:tmp_port = list(port)serial_com_list.append(tmp_port[0])COMBO_com_com['values'] = serial_com_listFRAME_com.after(500, refresh_com)refresh_com()
这一段代码实现了COM:后面的下拉窗口,可以动态显示可用的串口,每500ms刷洗一次。后面还会讲使用tkinter.StringVar()实现动态显示的方法。
输入框
输入框使用tkinter.Entry即可,读取时使用tkinter.Entry.get()
,设定默认值可以使用tkinter.Entry.insert()
,例如
ENTRY_init_release = tk.Entry(FRAME_init, show=None, width=4)
ENTRY_init_release.insert(0, 25)
ENTRY_init_release.grid(row=0, column=1)
tkinter.after()实现定时器
tkinter.after()函数实际上是等待一段时间后执行一个函数,但是可以通过函数本身的嵌套去实现一个定时器的功能,在stringVar()中一起介绍
动态的显示:StringVar()
有时候你会想在界面上显示一个实时变动的东西,这时候你就需要一个变量去放置你要显示的东西。我使用了一个很好用的类tkinter.StringVar。下面给出使用tkinter.after和tkinter.StringVar实现计时器的方法
global time_count
run_time = -1def start_time_count():"""开始计时界面的定时刷新,此定时器可用time_count来停止:return:"""global time_countglobal run_timerun_time += 1var_time.set('%.2f' % (run_time / 100))time_count = FRAME_time.after(10, start_time_count)PAN_time = tk.PanedWindow(FRAME_r_down, orient=tk.VERTICAL, height=100, width=364)
FRAME_time = tk.LabelFrame(PAN_time, text="运行计时")
PAN_time.add(FRAME_time)
PAN_time.pack()
var_time = tk.StringVar()
LABEL_time = tk.Label(FRAME_time, textvariable=var_time, font=('Arial', 40), fg='red')
LABEL_time.pack()
var_time.set('0.00')
run_time = -1
可以通过函数start_time_count()
来启动计时器,使用FRAME_time.after_cancel(time_count)
来结束。
带滑条的Text
如果你觉得创建Text再创建滑条太麻烦,那么可以像我一样使用tkinter.scrolledtext中的同名类来创建带滑条的Text窗口。可参考如下代码
import tkinter as tk
from tkinter import scrolledtext# FRAME_status
FRAME_status = tk.Frame(FRAME_r_down)
FRAME_status.pack()
TEXT_status = scrolledtext.ScrolledText(FRAME_status, width=49, height=8, font=('Consolas', 10))
TEXT_status.pack()
TEXT_status.insert(tk.END, 'Welcome to use Beihang Robotics CubicRobot\n')
TEXT_status.see(tk.END)
其中TEXT_status.see(tk.END)
可以使Text窗聚焦在底部
好看的字体设置
tkinter几乎所有地方都可以设置字体,例如以下:
LABEL_time = tk.Label(FRAME_time, textvariable=var_time, font=('Arial', 40), fg='red')
BUTTON_start = tk.Button(FRAME_main, text='开始', font=('黑体', 16), width=10, height=2, command=main_start)
TEXT_status = scrolledtext.ScrolledText(FRAME_status, width=49, height=8, font=('Consolas', 10))
在此推荐一种好看的英文字体,适合用在提示窗信息中:Consolas
tkinter修改关闭窗口操作
有时候为了保证程序的正常退出(如需要手动关闭线程),需要修改点击窗口右上方’X’号时触发的函数。tkinter中点击’X’产生的事件为'WM_DELETE_WINDOW'
,想要修改可以参考如下方法
# 重写关闭窗口函数,先关闭线程再关闭窗口
def terminate():# 关闭视频流线程stop_show_cam_thread()# 若串口打开,关闭串口try:com.close()except NameError:pass# 等待线程关闭再关闭窗口,实测有效root.after(1000, root.destroy)root.protocol('WM_DELETE_WINDOW', terminate)
其它零零碎碎的经验
OpenCV实现点击’X’关闭窗口
一般来说OpenCV都是通过按键盘来关闭(waitKey),但是这样的方法放在一个成熟的GUI中就有失优雅,于是我在Stack Overflow上找到了解决方法,可以看以下例子,也可以参见网址OpenCV Python: How to detect if a window is closed?
total_frames = 50
cv2.cv.NamedWindow("Dragonfly Simulation")
cv2.cv.StartWindowThread()
for i in range(total_frames):# do stuffimg_name = # somethingimg = cv2.cv.LoadImage(img_name)cv2.cv.ShowImage("Dragonfly Simulation", img)cv2.cv.WaitKey(2)
cv2.cv.DestroyWindow("Dragonfly Simulation")
cv2.cv.WaitKey(1)
# rest of code
以上是原网址的代码示例
while get_points_cnt < 9:cv.imshow(get_points_window, image)cv.waitKey(50)if cv.getWindowProperty(get_points_window, 0) < 0:TEXT_status.insert(END, 'Window shut down before finishing setting sampling points\n')TEXT_status.see(END)return
以上是我本次使用的代码,简单地说就是每次刷新图像的循环都对cv2.getWindowProperty(window_name, 0)
进行判断,如果窗口已经关闭,这个函数的返回值为-1,如果窗口已经关闭,则打断循环即可
打包文件
写完的代码可以用pyinstaller打包成exe文件,可以让你的程序在没有python环境的电脑上运行。这里只记录一个常用的打包方式
pyinstaller -F -i logo.ico -w main.py -p sub1.py -p sub2.py
-F是指打包成单独的exe文件,而不是很多dll依赖文件的文件夹。
-i指exe文件的图标
-w指生成窗口程序
-p后是子模块
pyinstaller错误:RecursionError: maximum recursion depth exceeded
打包程序是遇到了这个问题,意思是递归深度超过限制。在Stack Overflow上找到了解决方法:pyinstaller creating EXE RuntimeError: maximum recursion depth exceeded while calling a Python object
大概意思就是,使用的某一个包在疯狂递归,导致超过了python的栈深度限制。解决方法是首次运行pyinstaller命令,提示出错后,生成了一个module_name.spec
文件,记事本打开这个文件,加上下面两行即可
import sys
sys.setrecursionlimit(5000)
这里的5000指迭代深度,如果不够可以设置得更大
python上位机开发经验总结01相关推荐
- “Xilinx ZYNQ+TCP通信+Python上位机”实现实时视频传输系统
笔者在CSDN的第一篇万字长文,请多多支持. 本文是笔者的公众号 IC设计者笔记 文章的转载.很多优质原创内容都会第一时间发布在公众号,欢迎关注公众号,一起交流学习.公众号后台回复"ZYNQ ...
- python3中利用serial模块实现单片机与python上位机的通信(串口调试助手)
1.指标: python上位机向单片机发送字符,单片机如果收到的字符为'1',则点亮灯1,如果收到的字符为'2',则点亮灯2:单片机若接受到字符,读取字符后,向python上位机发送字符(1-& ...
- python上位机开发实例-python上位机
广告关闭 腾讯云双11爆品提前享,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高满返5000元! 若python上位机接受到的字符为"1',则print出ok,如果字符是 ...
- 用python做一个上位机串口通信_【教程】简易Python上位机之LED控制
电子爱好者应该不会对"上位机"这个词感到陌生,毕竟或多或少有过接触.但若是说到上位机的开发的话,大家就不一定熟悉了.很多电子爱好者完全没有接触过上位机的开发工作,他们真的没有相应的 ...
- python led屏控制_【教程】简易Python上位机之LED控制
电子爱好者应该不会对"上位机"这个词感到陌生,毕竟或多或少有过接触.但若是说到上位机的开发的话,大家就不一定熟悉了.很多电子爱好者完全没有接触过上位机的开发工作,他们真的没有相应的 ...
- C# / VB / LabVIEW / VC / Python 上位机使用S7-TCP协议与西门子PLC进行网口通信的教程 (Win/Linux)
现在越来越多的项目开始使用上位机了,在上位机实现数据存储.曲线绘制时,使用高级语言自行开发程序,比各种组态软件更加自由,更加强大.在进行上位机软件开发时,第一步就是要跟PLC取得通信,能够读写PLC内 ...
- Python上位机软件图形界面实战——PyQt
转载:https://blog.csdn.net/qq_25939803/article/details/97894219 文章目录 引言 1 环境配置 2 新建一个软件窗口 3 QtDesigner ...
- python 上位机直接与西门子变频器建立通信
利用python直接读取西门子变频器参数,省去变频器与PLC的连接,代码如下. 硬件连接:一根网线连接电脑与变频器,变频器必须要有PN口. 电脑的网络需与变频器的网络ip在同一个网段.如变频器为192 ...
- python上位机界面设计_用Python写界面--上位机开发
Python真的可以说是无所不能,上到人工智能.图像识别.下到控制电机.爬虫.数据处理,前不久发现Python还可以做界面,虽然比较丑,但是还是可以一试. Python内置图形界面库--Tkinter ...
最新文章
- 100万人同时抢1万张火车票,极限并发带来的思考
- 运行程序报“应用程序配置不正确”或者缺少运行库造成程序不可移植的问题...
- xml中1字节的UTF-8序列的字节1无效([字符编码]Invalid byte 1 of 1-byte UTF-8 sequence终极解决方案)
- [C#] C#访问数据库(SQL Server版本)
- 低功耗蓝牙系统结构流程图
- 排序算法 —— 插入排序
- 学习笔记 | 传统企业互联网改革之道
- es 安装kopf_Elasticsearch-kopf导览
- android之uniapp原生打包
- reactive streams与观察者模式
- 我与分布式机器学习的故事
- 31个实用find命令的案例
- PostgreSQL命令导入sql文件
- C# Microsoft.Office.Interop.Word 将多个word合成一个并插入图片 转换成pdf
- IEC60068-2-5太阳辐射模拟试验测试
- Canto加速市场的发展,连接全球的金融衍生品市场
- 远不止三色,图片被压扁了,用Python的Tkinter做一个既高颜值又好用的计算器
- 简述变分法在泛函极值问题中的应用
- 挑战大数据 金仓助力三农自助支付系统
- Python实现门禁管理系统(源码)