2020国产成人精品视频,性做久久久久久久久,亚洲国产成人久久综合一区,亚洲影院天堂中文av色

分享

Python進(jìn)階:一個(gè)串口通信工具(tkinter pyserial openpyxl)

 copy_left 2022-04-26

紙上得來(lái)終覺(jué)淺,唯有實(shí)踐出真知。這篇文章我們用Python來(lái)寫(xiě)一個(gè)串口通信的模擬器,使用到的技術(shù)包括tkinter、pySerial、openpyxl和python多線程。

使用tkinter布局界面

tkinter是python自帶的GUI工具包。開(kāi)發(fā)界面雖然有點(diǎn)丑,但是不復(fù)雜的界面用起來(lái)還是比較簡(jiǎn)單方便的。

下面介紹一下界面布局及界面組件的功能。

文章圖片1

界面設(shè)計(jì)

  1. 串口設(shè)置區(qū)域,用下拉框展示系統(tǒng)串口號(hào)、波特率、數(shù)據(jù)位、停止位、校驗(yàn)位供用戶選擇。
  2. 打開(kāi)串口和關(guān)閉串口按鈕。
  3. 信息展示區(qū)域,展示串口打開(kāi)關(guān)閉、異常信息和發(fā)送及接收到的串口數(shù)據(jù)。
  4. 選擇串口指令文件按鈕,彈出選擇文件對(duì)話框,選擇指令模板e(cuò)xcel文件。
  5. 展示當(dāng)前選擇的指令文件路徑。
  6. 指令展示區(qū)。分指令描述和指令內(nèi)容兩列展示指令,單擊指令描述直接發(fā)送選擇的指令,雙擊指令內(nèi)容則在下方指令詳情區(qū)域展示選擇的指令,可編輯選擇的指令。
  7. 指令描述輸入框。
  8. 指令內(nèi)容對(duì)話框。
  9. 添加指令按鈕。點(diǎn)擊后將指令添加到上方指令展示區(qū),此時(shí)并不保存到文件。
  10. 修改當(dāng)前選擇的指令。
  11. 將指令展示區(qū)的指令保存到指令模板文件。
  12. 發(fā)送指令到當(dāng)前打開(kāi)的串口。
  13. 清空按鈕。清空信息展示區(qū)內(nèi)容。

下拉框Combobox

以串口號(hào)下拉框?yàn)槔?,介紹一下tk中Combobox的使用方法。

#串口號(hào)下拉框#保存選中的串口號(hào)self.com_choose=StringVar()#下拉框self.com_choose_combo=ttk.Combobox(self.com_choose_frame,width=30,textvariable=self.com_choose)#設(shè)置為只讀,不允許修改self.com_choose_combo['state']='readonly'self.com_choose_combo.grid(row=0,column=1,columnspan=2,sticky=W+E,padx=2)#下拉框的值通過(guò)com_name_get()方法獲取self.com_choose_combo['values']=self.com_name_get()

使用pyserial獲取系統(tǒng)串口:

def com_name_get(self):        self.port_list=list(serial.tools.list_ports.comports())        self.com_port_names=[]        if len(self.port_list)>0:            for i in range(len(self.port_list)):                self.com_name=str(self.port_list[i])                self.com_port_names.append(self.com_name)        return self.com_port_names

選擇文件

按鈕綁定點(diǎn)擊事件,command=self.thread_file()。在thread_file函數(shù)中我們新建一個(gè)線程來(lái)打開(kāi)選擇文件窗口,這樣主界面線程不會(huì)卡住。

self.file_choose_button=Button(self.init_window_name,text='選擇文件',bg='lightblue',width=10,command=self.thread_file)
#新建選擇文件線程    def thread_file(self):        thisthread=threading.Thread(target=self.file_choose)        thisthread.start()    #選擇文件打開(kāi),并在界面中顯示    def file_choose(self):        self.root=Tk()        self.root.withdraw()        file_path=filedialog.askopenfilename()        if file_path:            self.open_file(file_path)

展示選擇的文件路徑,Entry組件文本處理方法如下:

self.file_path_text.delete(0,END)self.file_path_text.insert(END,file_path)

excel文件解析

使用openpyxl處理excel文件。

wb=openpyxl.load_workbook(file_path)sheet=wb[wb.sheetnames[0]]#然后從sheet中循環(huán)獲取數(shù)據(jù)就可以了

指令展示區(qū)點(diǎn)擊事件

指令展示使用Treeview組件,分別針對(duì)不同的列綁定不同的鼠標(biāo)事件。

#鼠標(biāo)左鍵雙擊self.code_tree.bind('<Double-1>', self.set_cell_value)#鼠標(biāo)左鍵單擊self.code_tree.bind('<1>', self.cell_operate)

處理左鍵單擊事件,需要獲取當(dāng)前點(diǎn)擊的行和列,如果是第一列column=='#1',則發(fā)送對(duì)應(yīng)的指令到串口。

# 發(fā)送按鈕執(zhí)行def cell_operate(self,event):    # 列    column= self.code_tree.identify_column(event.x)     # 行    row = self.code_tree.identify_row(event.y)     # 點(diǎn)擊指令描述列發(fā)送指令    if column=='#1' and self.code_tree.get_children():        data = self.code_tree.item(row, 'values')[1]        self.writeSerial(data)

雙擊指令內(nèi)容,則將當(dāng)前指令信息填充到下方指令編輯區(qū)。

# 雙擊進(jìn)入編輯狀態(tài) def set_cell_value(self,event): # 列 column= self.code_tree.identify_column(event.x) # 行 row = self.code_tree.identify_row(event.y) self.selected_command_row=row if (column=='#2') and self.code_tree.get_children(): self.command_name.delete(0,END) self.command_name.insert(0,self.code_tree.item(row, 'values')[0]) self.command.delete(0,END) self.command.insert(0,self.code_tree.item(row, 'values')[1])

串口操作

使用pyserial操作串口。

打開(kāi)串口代碼如下,根據(jù)用戶選擇的串口號(hào)以及設(shè)置信息打開(kāi)串口。

self.ser=serial.Serial(port=self.ser_name, baudrate=self.ser_baudrate, bytesize=self.ser_bytesize, parity=self.ser_parity, stopbits=self.ser_stopbits)self.ser.timeout=0.01

打開(kāi)串口成功后,需要啟動(dòng)一個(gè)新的線程來(lái)監(jiān)聽(tīng)串口通信,循環(huán)獲取串口收到的數(shù)據(jù)。

# 開(kāi)啟接收串口數(shù)據(jù)線程self.ReadUARTThread = threading.Thread(target=self.ReadUART, daemon=True)self.ReadUARTThread.start()
def ReadUART(self):        try:            while self.connected:                newline=self.ser.readline()#字節(jié)類型                if newline:                    # print(newline)                    self.result_text.insert(END,f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}←{newline.decode('gbk')}\n')                    self.result_text.see(tkinter.END)                    self.result_text.update()        except:            # print(e)            newline=f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}:串口{self.ser_name}已關(guān)閉\n'            self.result_text.insert(END,newline)            self.result_text.see(tkinter.END)            self.result_text.update()

串口發(fā)送數(shù)據(jù)很簡(jiǎn)單,直接使用Serial的write函數(shù)就可以了。

self.ser.write(data.encode('gbk'))

總結(jié)

代碼可以使用工具打包成exe后使用更方便。這個(gè)工具很簡(jiǎn)單,但涉及到的知識(shí)點(diǎn)也不少,初學(xué)Python的朋友可以看看。

文章圖片2

運(yùn)行效果

附所有代碼

干貨分享,代碼全部奉上。

requirements.txt,python版本為3.8

pyserial~=3.5openpyxl~=3.0.7pyinstaller~=4.2

simulator.py

import tkinterfrom tkinter import ttk,filedialog,scrolledtextfrom tkinter import *import openpyxlimport threadingimport timefrom datetime import datetimeimport serialimport serial.tools.list_portsimport reimport osclass MY_GUI(): #構(gòu)造函數(shù) def __init__(self,name): self.init_window_name=name self.connected=False #窗口控件設(shè)置初始化 def set_init_window(self): self.init_window_name.title('指令模擬器(串口)') self.init_window_name.geometry('1168x620+20+10') self.init_window_name.resizable(False, False) self.init_window_name.attributes('-alpha',1) #串口選擇框架 self.com_choose_frame=Frame(self.init_window_name,width=10,height=100) self.com_choose_frame.place(x=20,y=12) #串口號(hào)標(biāo)簽 self.com_label=Label(self.com_choose_frame,text='串口號(hào): ') self.com_label.grid(row=0,column=0,sticky=W) #串口號(hào)下拉框 self.com_choose=StringVar() self.com_choose_combo=ttk.Combobox(self.com_choose_frame,width=30,textvariable=self.com_choose) self.com_choose_combo['state']='readonly' self.com_choose_combo.grid(row=0,column=1,columnspan=2,sticky=W+E,padx=2) self.com_choose_combo['values']=self.com_name_get() # 波特率標(biāo)簽 self.baudrate_label=Label(self.com_choose_frame,text='波特率: ') self.baudrate_label.grid(row=0,column=3,sticky=W,padx=6) #波特率選項(xiàng)框 self.baudrate_value=StringVar(value='115200') self.baudrate_choose_combo=ttk.Combobox(self.com_choose_frame,width=6,textvariable=self.baudrate_value) self.baudrate_choose_combo['values']=('115200','9600') self.baudrate_choose_combo['state']='readonly' self.baudrate_choose_combo.grid(row=0,column=4,sticky=W,padx=2) # 數(shù)據(jù)位標(biāo)簽 self.bytesize=Label(self.com_choose_frame,text='數(shù)據(jù)位: ') self.bytesize.grid(row=0,column=5,sticky=W,padx=6) #數(shù)據(jù)位選項(xiàng)框 self.bytesize_value=StringVar(value='8') self.bytesize_choose_combo=ttk.Combobox(self.com_choose_frame,width=6,textvariable=self.bytesize_value) self.bytesize_choose_combo['values']=('5','6','7','8') self.bytesize_choose_combo['state']='readonly' self.bytesize_choose_combo.grid(row=0,column=6,padx=2) # 停止位標(biāo)簽 self.stopbits=Label(self.com_choose_frame,text='停止位: ') self.stopbits.grid(row=1,column=3,sticky=W,padx=6) # 停止位選項(xiàng)框 self.stopbits_value=StringVar(value='1') self.stopbits_choose_combo=ttk.Combobox(self.com_choose_frame,width=6,textvariable=self.stopbits_value) self.stopbits_choose_combo['values']=('1','1.5','2') self.stopbits_choose_combo['state']='readonly' self.stopbits_choose_combo.grid(row=1,column=4,padx=2) # 校驗(yàn)位標(biāo)簽 self.parity=Label(self.com_choose_frame,text='校驗(yàn)位: ') self.parity.grid(row=1,column=5,sticky=W,padx=6) # 校驗(yàn)位選項(xiàng)框 self.parity_value=StringVar(value='None') self.parity_choose_combo=ttk.Combobox(self.com_choose_frame,width=6,textvariable=self.parity_value) self.parity_choose_combo['values']=('None','Odd','Even','Mark','Space') self.parity_choose_combo['state']='readonly' self.parity_choose_combo.grid(row=1,column=6,padx=2,pady=2) #串口框架內(nèi)部按鈕 self.connect_button=Button(self.com_choose_frame,text='打開(kāi)串口',bg='lightblue',width=30,command=self.com_connect) self.connect_button.grid(row=1,column=1,columnspan=2,padx=1,pady=5) #文件路徑文本框 self.file_path_text=Entry(self.init_window_name,width=57) self.file_path_text.place(x=20,y=95) #選擇文件按鈕 self.file_choose_button=Button(self.init_window_name,text='選擇文件',bg='lightblue',width=10,command=self.thread_file) self.file_choose_button.place(x=450,y=90) #代碼解析后進(jìn)行顯示 self.code_frame=Frame(self.init_window_name,width=78,height=29,bg='white') self.code_frame.place(x=20,y=130) #解析后的代碼放在表格內(nèi)顯示 self.code_tree=ttk.Treeview(self.code_frame,show='headings',height=16,columns=('0','1')) self.code_bar=ttk.Scrollbar(self.code_frame,orient=VERTICAL,command=self.code_tree.yview) self.code_tree.configure(yscrollcommand=self.code_bar.set) self.code_tree.grid(row=0,column=0,sticky=NSEW) self.code_bar.grid(row=0,column=1,sticky=NS) self.code_tree.column('0',width=100,anchor='center') self.code_tree.column('1',width=450) self.code_tree.heading('0',text='指令描述') self.code_tree.heading('1',text='指令') # 雙擊左鍵進(jìn)入編輯 self.code_tree.bind('<Double-1>', self.set_cell_value) self.code_tree.bind('<1>', self.cell_operate) # 默認(rèn)打開(kāi)command.xlsx self.codeline_counter=0 if os.path.exists('command.xlsx'): self.open_file(os.path.abspath('command.xlsx')) elif os.path.exists('command.xls'): self.open_file(os.path.abspath('command.xls')) #文件路徑文本框 self.command_name_label=Label(self.init_window_name,text='指令描述: ') self.command_name_label.place(x=20,y=500) self.command_name=Entry(self.init_window_name,width=68) self.command_name.place(x=90,y=500) self.command=Entry(self.init_window_name,width=78) self.command.place(x=20,y=535) self.command_add_button=Button(self.init_window_name,text='添加',bg='lightblue',width=10,command=self.command_add) self.command_add_button.place(x=40,y=565) self.command_update_button=Button(self.init_window_name,text='修改',bg='lightblue',width=10,command=self.command_update) self.command_update_button.place(x=140,y=565) self.command_save_button=Button(self.init_window_name,text='保存到文件',bg='lightblue',width=10,command=self.command_save) self.command_save_button.place(x=240,y=565) self.command_send=Button(self.init_window_name,text='發(fā)送',bg='lightblue',width=10,command=self.com_send) self.command_send.place(x=480,y=565) #處理結(jié)果顯示滾動(dòng)文本框 self.result_data_label=Label(self.init_window_name,text='輸出結(jié)果') self.result_data_label.place(x=600,y=15) self.clear_button=Button(self.init_window_name,text='清空',bg='lightblue',width=10,command=self.clear_result_text) self.clear_button.place(x=680,y=10) self.result_text=scrolledtext.ScrolledText(self.init_window_name,width=77,height=42) self.result_text.place(x=600,y=50) #自動(dòng)獲取當(dāng)前連接的串口名 def com_name_get(self): self.port_list=list(serial.tools.list_ports.comports()) self.com_port_names=[] if len(self.port_list)>0: for i in range(len(self.port_list)): self.com_name=str(self.port_list[i]) self.com_port_names.append(self.com_name) return self.com_port_names #打開(kāi)串口按鍵的執(zhí)行內(nèi)容 def com_connect(self): if self.connected: self.com_cancel() return self.ser_name=str(self.com_choose.get()) for i in range(len(self.port_list)): if self.ser_name == str(self.port_list[i]): self.ser_name, desc, hwid = self.port_list[i] self.result_text.insert(END,f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}:準(zhǔn)備連接串口{self.ser_name}\n') self.ser_baudrate=int(self.baudrate_value.get()) self.ser_bytesize=int(self.bytesize_value.get()) self.ser_stopbits=float(self.stopbits_value.get()) self.ser_parity=str(self.parity_value.get())[0:1] try: self.ser=serial.Serial(port=self.ser_name, baudrate=self.ser_baudrate, bytesize=self.ser_bytesize, parity=self.ser_parity, stopbits=self.ser_stopbits) self.ser.timeout=0.01 self.result_text.insert(END,f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}:串口{self.ser_name}打開(kāi)成功\n') self.result_text.see(tkinter.END) self.result_text.update() # 按鈕變成“關(guān)閉串口” self.connected=True self.connect_button['text']='關(guān)閉串口' # 開(kāi)啟接收串口數(shù)據(jù)線程 self.ReadUARTThread = threading.Thread(target=self.ReadUART, daemon=True) self.ReadUARTThread.start() except: newline=f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}:串口{self.ser_name}打開(kāi)失敗,串口不存在或被占用\n' self.result_text.insert(END,newline) self.result_text.see(tkinter.END) self.result_text.update() #關(guān)閉串口按鍵的執(zhí)行內(nèi)容 def com_cancel(self): try: self.ser.close() # 按鈕變成“打開(kāi)串口” self.connected=False self.connect_button['text']='打開(kāi)串口' except: newline=time.ctime(time.time())+':'+'串口未打開(kāi)'+'\n' self.result_text.insert(END,newline) self.result_text.see(tkinter.END) self.result_text.update() def clear_result_text(self): self.result_text.delete('1.0',END) def ReadUART(self): try: while self.connected: newline=self.ser.readline()#字節(jié)類型 if newline: # print(newline) self.result_text.insert(END,f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}←{newline.decode('gbk')}\n') self.result_text.see(tkinter.END) self.result_text.update() except: # print(e) newline=f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}:串口{self.ser_name}已關(guān)閉\n' self.result_text.insert(END,newline) self.result_text.see(tkinter.END) self.result_text.update() def com_send(self): data = self.command.get() if data: self.writeSerial(data) def writeSerial(self, data): try: if self.connected and self.ser: # print(data) self.ser.write(data.encode('gbk')) newline=f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}→{data}\n' self.result_text.insert(END,newline) self.result_text.see(tkinter.END) self.result_text.update() except: newline=f'{datetime.now().strftime('%H:%M:%S.%f')[:-3]}:串口{self.ser_name}發(fā)送數(shù)據(jù)失敗\n' self.result_text.insert(END,newline) self.result_text.see(tkinter.END) self.result_text.update() #新建選擇文件線程 def thread_file(self): thisthread=threading.Thread(target=self.file_choose) thisthread.start() #選擇文件打開(kāi),并在界面中顯示 def file_choose(self): self.root=Tk() self.root.withdraw() file_path=filedialog.askopenfilename() if file_path: self.open_file(file_path) def open_file(self, file_path): self.file_path_text.delete(0,END) self.file_path_text.insert(END,file_path) wb=openpyxl.load_workbook(file_path) sheet=wb[wb.sheetnames[0]] # 只支持最大200self.code_sheet=[[0 for i in range(3)]for j in range(200)] if self.code_tree.get_children(): for item in self.code_tree.get_children(): self.code_tree.delete(item) pattern=re.compile(r'_x00(.*?)_',re.S) for i in range(200): if sheet.cell(row=i+2,column=1).value: self.codeline_counter +=1 self.code_context=[] # 指令描述 self.code_sheet[i][0]=sheet.cell(row=i+2,column=1).value self.code_context.append(self.code_sheet[i][0]) # 指令 self.code_sheet[i][1] = sheet.cell(row=i+2,column=2).value special_char=re.findall(pattern,self.code_sheet[i][1]) for c in special_char: self.code_sheet[i][1]=self.code_sheet[i][1].replace('_x00'+c+'_', bytes.fromhex(c).decode('utf-8')) self.code_context.append(self.code_sheet[i][1]) # 雙擊進(jìn)入編輯狀態(tài) def set_cell_value(self,event): # 列 column= self.code_tree.identify_column(event.x) # 行 row = self.code_tree.identify_row(event.y) self.selected_command_row=row if (column=='#2') and self.code_tree.get_children(): self.command_name.delete(0,END) self.command_name.insert(0,self.code_tree.item(row, 'values')[0]) self.command.delete(0,END) self.command.insert(0,self.code_tree.item(row, 'values')[1]) # 發(fā)送按鈕執(zhí)行 def cell_operate(self,event): column= self.code_tree.identify_column(event.x)# 列 row = self.code_tree.identify_row(event.y) # 行 # 點(diǎn)擊指令描述列發(fā)送指令 if column=='#1' and self.code_tree.get_children(): data = self.code_tree.item(row, 'values')[1] self.writeSerial(data) # 修改指令執(zhí)行 def command_update(self): if self.selected_command_row and self.command_name.get() and self.command.get(): self.code_tree.set(self.selected_command_row, column='#1', value=self.command_name.get()) self.code_tree.set(self.selected_command_row, column='#2', value=self.command.get()) # 添加指令執(zhí)行 def command_add(self): row = len(self.code_tree.get_children()) if self.command_name.get() and self.command.get(): self.code_tree.insert('', row, values=[self.command_name.get(), self.command.get()]) print(self.command_name.get()) # 保存到文件指令執(zhí)行 def command_save(self): row = len(self.code_tree.get_children()) file_path = self.file_path_text.get() if row>0 and file_path: try: wb=openpyxl.load_workbook(file_path) sheet=wb[wb.sheetnames[0]] i=2 pattern=re.compile(r'([\x00-\x20])',re.S) for item in self.code_tree.get_children(): command_val=self.code_tree.item(item, 'values')[1] special_char=re.findall(pattern,command_val) for c in special_char: hexstr = hex(ord(c)) if len(hexstr) == 3: command_val = command_val.replace(c,'_x00'+hexstr.replace('x','')+'_') elif len(hexstr) == 4: command_val = command_val.replace(c,'_x00'+hexstr.replace('0x','')+'_') # print(command_val) sheet.cell(row=i,column=1).value = self.code_tree.item(item, 'values')[0] sheet.cell(row=i,column=2).value = command_val i+=1 wb.save(file_path) tkinter.messagebox.showinfo('保存成功',f'保存文件成功{file_path}') except PermissionError as e: tkinter.messagebox.showinfo('保存失敗',e) except: tkinter.messagebox.showinfo('保存失敗','描述或指令含有特殊字符?') #主線程def start(): init_window=Tk() my_window=MY_GUI(init_window) my_window.set_init_window() init_window.mainloop()start()

指令模板e(cuò)xcel格式如下:

描述

指令

參數(shù)設(shè)置

EP#BSTMODE=T

設(shè)備狀態(tài)

SQ

結(jié)束復(fù)位

EP#RESET

文章圖片3

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約