USB继电器控制小工具
1. 基础知识介绍
tkinter
是一个简单入手,但是功能十分强大的GUI编程库,学习入门很快,如果你还不会,点击阅读Tkinter详细教程:- 强大的的GUI开发库 - Tkinter详细教程一
- 强大的的GUI开发库 - Tkinter详细教程二
- 强大的的GUI开发库 - Tkinter详细教程三
串口Uart
是嵌入式最基础,最简单,也是使用最广范的一种通信协议,在诸多通信都使用;pyserial
是python中处理串口通讯的一个模块名,操做十分简单,如果还不了解,点击阅读详细教程:- python serial模块详细教程
2. 需求背景介绍说明
-
usb控制继电器模块
常用在控制设备的上下电管理,常常用来自动化控制
、自动化测试
-
USB继电器就是通过串口通信协议控制继电器的,如下图是一个4路的USB继电器控制模块,通过USB连接电脑,控制继电器的开关,从而控制设备的上下电。
-
继电器的控制指令很简单,按照下图所示的说明,即可控制对应端口的开闭;控制协议为4个字节,含义如下:
- 数据1:
启动标识
,默认使用0xA0
- 数据2:
地址码
,即控制那路继电器,OUT 1就是0x01
,OUT 2就是0x02
- 数据3:
控制码
,0x00为关不反馈;0x01为开不反馈;0x02为关并反馈;0x03为开并反馈;0x04为取反并反馈;0x05为查询状态;0x06为闪断并反馈 - 数据4:
校验码
,前面三个数据相加的和
- 数据1:
-
控制工具需求说明
- 软件工具能够查找到指定的串口端口,并进行断开和连接
- 能够选择对应的端口
- 能够控制端口的闭合时长和断开时长
- 能够统计继电器工作时长
- 能够统计继电器的执行次数
-
按照工具需求,设计出的
软件工具界面示例图
如下图所示:
-
设计源码分成2部分
GUI界面设计
,设计出工具的界面继电器控制
,并将GUI的界面事件和控制指令进行绑定
3. GUI界面开发源码
- GUI界面源码文件名:
PowerControl.py
- 源码如下:
import sys, os
sys.path.append( os.path.dirname( os.path.dirname( os.path.abspath(__file__))))
import ttkbootstrap as tkbot
from ttkbootstrap.constants import *
import time
import PowerDriver as PowerDrv
class PowerControl_App(tkbot.Frame):
def __init__(self, master=None) :
super().__init__(master)
self.master = master
self.AllComChannel = [
"Not find"] # 端口设置
self.PowerChannel = [
"OUT 1" ,
"OUT 2" ,
"OUT 3" ,
"OUT 4"] # 继电器端口设置
self.LogDispalyList = [] # 数据显示窗口显示内容
self.PowerDriver = PowerDrv.PowerDriver() # 串口工具对象
self.ConnectTime = tkbot.StringVar()
self.DisConnectTime = tkbot.StringVar()
self.ConnectTime.set('120')
self.DisConnectTime.set('10')
self.IsFindPort = False # 是否检测到串口
self.IsSeriolConnect = False # 当前串口是否连接
self.IsPowerWork = False # 继电器是否工作
self.TimeHour = 0 # 执行时间统计
self.TimeMine = 0
self.TimeS = 0
self.createWidget(master)
def createWidget(self, master) :
InformDispalyFrame = tkbot.Labelframe(master, text=" 显示窗口 ", bootstyle="info")
InformDispalyFrame.place(x=13, y=5, width=700, height=340)
logsb = tkbot.Scrollbar(InformDispalyFrame, bootstyle="info-round")
logsb.pack(side=RIGHT, fill=Y)
Result_Display = tkbot.Text(InformDispalyFrame, bg="white", font=("宋体",12), yscrollcommand=logsb.set)
Result_Display.place(x=10, y=10, width=670, height=290)
logsb.config(command=Result_Display.yview)
def UpDateLogDisplay():
for item in self.LogDispalyList:
Result_Display.insert(END, item)
self.LogDispalyList.clear()
master.after(100, UpDateLogDisplay)
UpDateLogDisplay()
def findSeriol() :
if self.IsSeriolConnect :
self.LogDispalyList.append("通信串口已连接"+"\n")
else :
if self.PowerDriver.Find_Seriollist() :
self.AllComChannel = self.PowerDriver.Get_Seriollist()
PortChannelCombobox["value"] = self.AllComChannel
PortChannelCombobox.current(0)
SerilPort = str(self.AllComChannel[0])
SerilPortStr = SerilPort[0:5]
self.PowerDriver.Set_SeriolChanel(SerilPortStr)
self.IsFindPort = True
Length_text = "设置通信串口 : " + SerilPortStr +"\n"
self.LogDispalyList.append(Length_text)
else :
self.IsFindPort = False
self.AllComChannel = ["Not find"]
PortChannelCombobox["value"] = self.AllComChannel
PortChannelCombobox.current(0)
self.LogDispalyList.append("未检测任何到串口"+"\n")
Refresh_Btn = tkbot.Button(master, text="串口刷新", bootstyle="info-outline",
command= findSeriol)
Refresh_Btn.place(x=30, y=360, width=100, height=40)
PortChannelLable = tkbot.Label(master, text="串口选择", font=("楷体",12), bootstyle="danger")
PortChannelLable.place(x=160, y=360, width=100, height=40)
def SetChosePort(event):
if self.IsFindPort :
SerilPort = str(PortChannelCombobox.get())
SerilPortStr = SerilPort[0:5]
self.PowerDriver.Set_SeriolChanel(SerilPortStr)
Length_text = "设置通信串口 : " + SerilPortStr +"\n"
self.LogDispalyList.append(Length_text)
else :
self.LogDispalyList.append("未检测任何到串口"+"\n")
PortChannelCombobox = tkbot.Combobox(master, font=("宋体",12), width=18)
PortChannelCombobox.place(x=260, y=363, width=160, height=40)
PortChannelCombobox["value"] = self.AllComChannel
PortChannelCombobox.current(0)
PortChannelCombobox.bind("<<ComboboxSelected>>", SetChosePort)
def DeviceConnect():
if self.IsFindPort :
if self.IsSeriolConnect :
self.LogDispalyList.append("通信串口已经连接成功"+"\n")
else :
if self.PowerDriver.Open_Seriol() :
self.IsSeriolConnect = True
Length_text = "打开串口成功"+"\n"
self.LogDispalyList.append(Length_text)
else :
Length_text = "打开串口失败"+"\n"
self.IsSeriolConnect = False
self.LogDispalyList.append(Length_text)
else :
self.IsSeriolConnect = False
Length_text = "未连接任何串口, 打开失败" +"\n"
self.LogDispalyList.append(Length_text)
Send_Btn = tkbot.Button(master, text="串口连接", bootstyle="info-outline",
command=DeviceConnect)
Send_Btn.place(x=460, y=360, width=100, height=40)
def DeviceDisConnect():
if self.IsSeriolConnect :
self.IsSeriolConnect = False
self.IsPowerWork = False
self.PowerDriver.Close_Seriol()
Length_text = "断开通信串口,继电器停止工作"+"\n"
self.LogDispalyList.append(Length_text)
Length_text = "本次执行次数 : " + str(self.PowerDriver.Get_RunCount()) \
+", 执行时间 : " + str(self.TimeHour)+"H:"+str(self.TimeMine)+"M:"+str(self.TimeS)+"S" + "\n"
self.LogDispalyList.append(Length_text)
else :
Length_text = "通信串口尚未连接"+"\n"
self.LogDispalyList.append(Length_text)
Stop_Btn = tkbot.Button(master, text="串口断开", bootstyle="info-outline",
command=DeviceDisConnect)
Stop_Btn.place(x=590, y=360, width=100, height=40)
PowerChannelLable = tkbot.Label(master, text="继电器端口选择", font=("楷体",12), bootstyle="danger")
PowerChannelLable.place(x=20, y=420, width=150, height=40)
def SetPowerOutPort(event):
SerilPort = str(PowerChannelCombobox.get())
if SerilPort == "OUT 1" :
PortIndex = 1
elif SerilPort == "OUT 2" :
PortIndex = 2
elif SerilPort == "OUT 3" :
PortIndex = 3
elif SerilPort == "OUT 4" :
PortIndex = 4
self.PowerDriver.Set_PowerOutIndex(PortIndex)
Length_text = "选择继电器输出端口 : " + str(PortIndex) +"\n"
self.LogDispalyList.append(Length_text)
PowerChannelCombobox = tkbot.Combobox(master, font=("宋体",12), width=18)
PowerChannelCombobox.place(x=175, y=423, width=85, height=40)
PowerChannelCombobox["value"] = self.PowerChannel
PowerChannelCombobox.current(0)
PowerChannelCombobox.bind("<<ComboboxSelected>>", SetPowerOutPort)
def StartWork():
if self.IsSeriolConnect :
if self.IsPowerWork :
self.LogDispalyList.append("继电器已近开始开始工作了"+"\n")
else :
self.IsPowerWork = True
self.TimeHour = 0
self.TimeMine = 0
self.TimeS = 0
ConnectTime = int(self.ConnectTime.get())
self.PowerDriver.Set_ConnectTime(ConnectTime)
DisConnectTime = int(self.DisConnectTime.get())
self.PowerDriver.Set_DisconnectTime(DisConnectTime)
self.PowerDriver.Start_PowerControl()
Length_text = "继电器开始工作, " +"闭合 : "+ str(ConnectTime) +" s, " \
+"断开 : "+ str(DisConnectTime) +" s" + "\n"
self.LogDispalyList.append(Length_text)
else :
self.IsPowerWork = False
Length_text = "通信串口尚未连接, 继电器无法工作"+"\n"
self.LogDispalyList.append(Length_text)
CloseTimeLable = tkbot.Label(master, text="闭合时长", font=("楷体",12), bootstyle="danger")
CloseTimeLable.place(x=290, y=420, width=80, height=40)
CloseTime = tkbot.Entry(master, textvariable=self.ConnectTime, font=("宋体",15))
CloseTime.place(x=380, y=420, width=80, height=40)
TimeSLable = tkbot.Label(master, text="S", font=("楷体",12), bootstyle="danger")
TimeSLable.place(x=465, y=420, width=20, height=40)
OpenLable = tkbot.Label(master, text="断开时长", font=("楷体",12), bootstyle="danger")
OpenLable.place(x=500, y=420, width=80, height=40)
OpenTime = tkbot.Entry(master, textvariable=self.DisConnectTime, font=("宋体",15))
OpenTime.place(x=590, y=420, width=80, height=40)
TimeS1Lable = tkbot.Label(master, text="S", font=("楷体",12), bootstyle="danger")
TimeS1Lable.place(x=675, y=420, width=20, height=40)
PowerStart_Btn = tkbot.Button(master, text="继电器启动", bootstyle="info-outline",
command= StartWork)
PowerStart_Btn.place(x=200, y=480, width=120, height=40)
def StoptWork():
if self.IsPowerWork :
self.IsPowerWork = False
self.PowerDriver.Stop_PowerControl()
Length_text = "继电器停止工作" + "\n"
self.LogDispalyList.append(Length_text)
Length_text = "本次执行次数 : " + str(self.PowerDriver.Get_RunCount()) \
+", 执行时间 : " + str(self.TimeHour)+"H:"+str(self.TimeMine)+"M:"+str(self.TimeS)+"S" + "\n"
self.LogDispalyList.append(Length_text)
else :
Length_text = "继电器尚未工作" + "\n"
self.LogDispalyList.append(Length_text)
PowerStop_Btn = tkbot.Button(master, text="继电器停止", bootstyle="info-outline",
command=StoptWork)
PowerStop_Btn.place(x=400, y=480, width=120, height=40)
countLable = tkbot.Label(master, text="当前时间", font=("楷体",12))
countLable.place(x=15, y=540, width=100, height=40)
countEntry = tkbot.Label(master, text="0H:0M:0S", font=("宋体",16),
bootstyle="inverse-secondary", width=12)
countEntry.place(x=105, y=540, width=120, height=40)
TimeLable = tkbot.Label(master, text="运行时间", font=("楷体",12))
TimeLable.place(x=255, y=540, width=100, height=40)
TimeEntry = tkbot.Label(master,text="00H:00M:00S", font=("宋体",16),
bootstyle="inverse-secondary", width=12)
TimeEntry.place(x=345, y=540, width=160, height=40)
CountLable = tkbot.Label(master, text="执行次数", font=("楷体",12))
CountLable.place(x=535, y=540, width=100, height=40)
CountEntry = tkbot.Label(master,text="0", font=("宋体",16),
bootstyle="inverse-secondary", width=12)
CountEntry.place(x=625, y=540, width=80, height=40)
def UpdateTiming():
if self.IsPowerWork :
if self.TimeS == 59 :
self.TimeMine = self.TimeMine + 1
self.TimeS = 0
else:
self.TimeS = self.TimeS + 1
if self.TimeMine == 59 :
self.TimeHour = self.TimeHour + 1
self.TimeMine = 0
TimeEntry.config(text=str(self.TimeHour)+"H:"+str(self.TimeMine)+"M:"+str(self.TimeS)+"S")
Nowtimestr = time.strftime("%H:%M:%S")
countEntry.config(text=Nowtimestr)
Runcount = str(self.PowerDriver.Get_RunCount())
CountEntry.config(text=Runcount)
master.after(1000, UpdateTiming)
UpdateTiming()
if __name__ == "__main__" :
# 主窗口设置
root = tkbot.Window()
root.title("电源继电器小工具 QwQ")
withset = 725
hightset = 600
root.geometry("%dx%d"%(withset, hightset))
PowerControlApp = PowerControl_App(root)
root.mainloop()
4. 串口驱动源码
- GUI界面源码文件名:
PowerDriver.py
- 源码如下:
import serial #导入模块
import serial.tools.list_ports
import threading
import time
class PowerDriver():
def __init__(self):
super().__init__()
self.Seriollist = [] # 串口对象获取
self.Seriol = serial.Serial() # 串口对象
self.SeriolChanel = "com6" # 端口号
self.SeriolBaudRate = 9600 # 波特率
self.PowerOutIndex = 1 # 电源输出端口
self.ConnectTime = 1 # 闭合时长
self.DisconnectTime = 1 # 断开时长
self.RunCount = 0 # 执行次数
self.PowerTask_IsAlive = False # 继电器任务
self.ConnectRunCount = 0 # 闭合计时计数
self.DisConnectRunCount = 0 # 断开计时计数
self.Do_Connect = False # 当前是在执行闭合还是断开互斥标志
def Set_SeriolChanel(self, SeriolChanel):
self.SeriolChanel = SeriolChanel
def Set_PowerOutIndex(self, PowerOutIndex):
self.PowerOutIndex = PowerOutIndex
def Set_ConnectTime(self, ConnectTime):
self.ConnectTime = ConnectTime
def Set_DisconnectTime(self, DisconnectTime):
self.DisconnectTime = DisconnectTime
def Get_RunCount(self):
return self.RunCount
def Find_Seriollist(self):
port_list = list(serial.tools.list_ports.comports())
if len(port_list) == 0:
self.Seriollist = []
return False
else:
self.Seriollist = port_list
first_port = str(port_list[0])
self.SeriolChanel = first_port[0:5] # 及时修改端口
return True
def Get_Seriollist(self):
return self.Seriollist
def Open_Seriol(self):
try:
# 打开串口,并得到串口对象
self.Seriol = serial.Serial(self.SeriolChanel, self.SeriolBaudRate, timeout=5)
# 判断是否打开成功
if(False == self.Seriol.isOpen):
return False
else :
SeriolRx_Thread = threading.Thread(target=self.PowerTask)
SeriolRx_Thread.setDaemon(True)
SeriolRx_Thread.start()
return True
except Exception as error:
return False
def Close_Seriol(self):
self.Seriol.close()
self.Do_Connect = False
self.PowerTask_IsAlive = False
self.ConnectRunCount = 0
self.DisConnectRunCount = 0
def Power_Connect(self):
if self.PowerOutIndex == 1:
Order = [0xA0, 0x01, 0x00, 0xA1]
elif self.PowerOutIndex == 2:
Order = [0xA0, 0x02, 0x00, 0xA2]
elif self.PowerOutIndex == 3:
Order = [0xA0, 0x03, 0x00, 0xA3]
else :
Order = [0xA0, 0x04, 0x00, 0xA4]
self.Seriol.write(Order)
def Power_DisConnect(self):
if self.PowerOutIndex == 1:
Order = [0xA0, 0x01, 0x01, 0xA2]
elif self.PowerOutIndex == 2:
Order = [0xA0, 0x02, 0x01, 0xA3]
elif self.PowerOutIndex == 3:
Order = [0xA0, 0x03, 0x01, 0xA4]
else :
Order = [0xA0, 0x04, 0x01, 0xA5]
self.Seriol.write(Order)
def PowerTask(self):
while True :
if self.PowerTask_IsAlive :
if self.Do_Connect :
if self.ConnectRunCount < self.ConnectTime :
self.ConnectRunCount = self.ConnectRunCount + 1
else :
self.Do_Connect = False
self.ConnectRunCount = 0
self.Power_DisConnect()
else :
if self.DisConnectRunCount < self.DisconnectTime :
self.DisConnectRunCount = self.DisConnectRunCount + 1
else :
self.Do_Connect = True
self.DisConnectRunCount = 0
self.RunCount = self.RunCount + 1
self.Power_Connect()
time.sleep(1)
def Start_PowerControl(self):
self.Power_Connect()
self.RunCount = 0
self.RunCount = self.RunCount + 1
self.Do_Connect = True
self.PowerTask_IsAlive = True
self.ConnectRunCount = 0
self.DisConnectRunCount = 0
def Stop_PowerControl(self):
self.Do_Connect = False
self.PowerTask_IsAlive = False
self.ConnectRunCount = 0
self.DisConnectRunCount = 0
感谢阅读 若有错误 欢迎指正 !!!