前面已經初步實現了pzh-py-com的串口功能,並且通過了最基本的測試,但目前的pzh-py-com相比市面上流行的串口調試工具還差得很遠,有很大的優化空間。優化可以從兩方面進行:一、是功能上的優化,可以添加更多實用的功能;二、是界面效果上的優化,可以增加一些界面動畫效果或者重新配色美化界面。下面痞子衡從這兩方面分別為pzh-py-com做一些簡單的優化:
一、功能優化
1.1 增強魯棒性
最開始要做的功能優化應該是增強軟件魯棒性,即在任何異常用戶輸入的情況下,軟件都不能掛掉,痞子衡在實測中發現當用戶在"Com Port"裡輸入的是無效串口設備號時,軟件會掛掉,因此做了以下改進,在打開設備時使用try except語句,如有異常,直接退出,不會繼續後面代碼的執行。此類改進還有很多,不一一例舉。
<code>class mainWin(win.com_win): def openClosePort( self, event ): if s_serialPort.isOpen(): s_serialPort.close() self.m_button_openClose.SetLabel('Open') else: # ... self.setParitybits() # 添加代碼開始 try: s_serialPort.open() except Exception, e: # Show warning message return # 添加代碼結束 self.m_button_openClose.SetLabel('Close') # ... 1.2 自動檢測可用Port/<code>
最初版本實現Port口選擇是用戶按標準格式“COMx”手動輸入,但這樣有一個問題,即用戶輸入的格式有可能不合法,並且即使是一個合法的格式輸入,但也可能不是一個可用的有效Port。參照市面上流行的串口調試助手,有的是下拉菜單選擇所有COM口(比如AccessPort,這樣可以解決不合法格式輸入的問題),有的是下拉菜單選擇可用的COM口(比如sscom,這樣可以解決Port是否有效的問題),痞子衡參照sscom的做法對pzh-py-com進行了如下優化:
<code>class mainWin(win.com_win): def __init__(self, parent): self.refreshComPort(None) self.m_choice_comPort.SetSelection( 0 ) def refreshComPort( self, event ): comports = list(serial.tools.list_ports.comports()) ports = [None] * len(comports) for i in range(len(comports)): comport = list(comports[i]) # example comport = [u'COM3', u'Intel(R) Active Management Technology - SOL (COM3)', u'PCI\\VEN_8086&DEV_9D3D&SUBSYS_06DC1028&REV_21\\3&11583659&0&B3'] ports[i] = comport[0] + ' - ' + comport[1] self.m_choice_comPort.Clear() self.m_choice_comPort.SetItems(ports) def setPort ( self ): index = self.m_choice_comPort.GetSelection() comPort = self.m_choice_comPort.GetString(index) comPort = comPort.split(' - ') s_serialPort.port = comPort[0] 1.3 實現格式切換功能/<code>
Char/Hex格式轉換屬於比較實用的功能,一般的串口調試助手都會有這個功能,pzh-py-com之前默認總是按照Char格式來輸入和顯示,"Format"選項框的功能實際上並沒有實現,因此痞子衡在這裡加上了格式切換功能。
<code>import formatter s_formatter = formatter.formatter() s_lastRecvFormat = None s_lastSendFormat = None class mainWin(win.com_win): # 函數功能實現 def setSendFormat( self, event ): lines = self.m_textCtrl_send.GetNumberOfLines() if lines != 0: m_sendFormat = self.m_choice_sendFormat.GetString(self.m_choice_sendFormat.GetSelection()) global s_lastSendFormat if s_lastSendFormat == m_sendFormat: return else: s_lastSendFormat = m_sendFormat # Get existing data from textCtrl_send data = '' for i in range(0, lines): data += str(self.m_textCtrl_send.GetLineText(i)) # Convert data format according to choice_sendFormat if m_sendFormat == 'Char': status, data = s_formatter.hexToChar(data) if not status: self.m_textCtrl_send.Clear() self.m_textCtrl_send.write('Invalid format! Correct example: 12 34 56 ab cd ef') return elif m_sendFormat == 'Hex': data = s_formatter.charToHex(data) # Re-show converted data in textCtrl_send self.m_textCtrl_send.Clear() self.m_textCtrl_send.write(data) def sendData( self, event ): if s_serialPort.isOpen(): lines = self.m_textCtrl_send.GetNumberOfLines() if lines != 0: data = '' for i in range(0, lines): data += str(self.m_textCtrl_send.GetLineText(i)) # 添加代碼開始 # Make sure data is always in 'Char' format m_sendFormat = self.m_choice_sendFormat.GetString(self.m_choice_sendFormat.GetSelection()) if m_sendFormat == 'Hex': status, data = s_formatter.hexToChar(data) if not status: self.m_textCtrl_send.Clear() self.m_textCtrl_send.write('Invalid format! Correct example: 12 34 56 ab cd ef') return # 添加代碼結束 s_serialPort.write(data) # 函數功能實現 def setRecvFormat( self, event ): lines = self.m_textCtrl_recv.GetNumberOfLines() if lines != 0: m_recvFormat = self.m_choice_recvFormat.GetString(self.m_choice_recvFormat.GetSelection()) global s_lastRecvFormat if s_lastRecvFormat == m_recvFormat: return else: s_lastRecvFormat = m_recvFormat # Get existing data from textCtrl_recv data = '' for i in range(0, lines): data += str(self.m_textCtrl_recv.GetLineText(i)) # Convert data format according to choice_recvFormat if m_recvFormat == 'Char': status, data = s_formatter.hexToChar(data) elif m_recvFormat == 'Hex': data = s_formatter.charToHex(data) # Re-show converted data in textCtrl_recv self.m_textCtrl_recv.Clear() self.m_textCtrl_recv.write(data) def recvData( self ): if s_serialPort.isOpen(): num = s_serialPort.inWaiting() if num != 0: data = s_serialPort.read(num) # 添加代碼開始 # Note: Assume that data is always in 'Char' format # Convert data format if dispaly format is 'Hex' m_recvFormat = self.m_choice_recvFormat.GetString(self.m_choice_recvFormat.GetSelection()) if m_recvFormat == 'Hex': data = s_formatter.charToHex(data) # 添加代碼結束 self.m_textCtrl_recv.write(data) 發送輸入框格式切換功能實測如下,尤其是在Hex模式下,如果有異常輸入,pzh-py-com會直接清屏,並在輸入框裡提示正確的示例。接收顯示框格式切換功能雷同,但並不包含異常輸入提示,因為這是個結果顯示輸出框。/<code>
1.4 啟用菜單欄
菜單欄是一個功能齊全的軟件的標配,用於實現各種特性功能,此處痞子衡僅添加了一個“Help”菜單,用於顯示pzh-py-com的主頁以及作者信息。首先需要在wxFormBuilder添加menu控件,然後設置回調函數名,下面是回調函數的實現:
<code>def showHomepageMessage( self, event ): messageText = (('Code: \n https://github.com/JayHeng/pzh-py-com.git \n') + ('Doc: \n https://www.cnblogs.com/henjay724/p/9416096.html \n')) wx.MessageBox(messageText, "Homepage", wx.OK | wx.ICON_INFORMATION) def showAboutMessage( self, event ): messageText = (('Author: Jay Heng \n') + ('Email: [email protected] \n')) wx.MessageBox(messageText, "About", wx.OK | wx.ICON_INFORMATION)/<code>
1.5 啟用狀態欄
狀態欄也是一般串口調試助手的標配,用於顯示發送/接收數據統計信息以及串口開關狀態,因此痞子衡為pzh-py-com也加上了狀態欄功能,狀態欄主要分為三欄:接收數據統計、發送數據統計、串口狀態。
<code>s_recvStatusFieldIndex = 0 s_sendStatusFieldIndex = 1 s_infoStatusFieldIndex = 2 s_recvStatusStr = 'Recv: ' s_recvTotalBytes = 0 s_sendStatusStr = 'Send: ' s_sendTotalBytes = 0 class mainWin(win.com_win): def openClosePort( self, event ): if s_serialPort.isOpen(): s_serialPort.close() self.m_button_openClose.SetLabel('Open') # 添加代碼開始 self.statusBar.SetStatusText(s_serialPort.name + ' is closed', s_infoStatusFieldIndex) # 添加代碼結束 else: # 添加代碼開始 self.statusBar.SetFieldsCount(3) self.statusBar.SetStatusWidths([150, 150, 400]) # 添加代碼結束 self.setPort() # ... self.m_button_openClose.SetLabel('Close') # 添加代碼開始 self.statusBar.SetStatusText(s_recvStatusStr + str(s_recvTotalBytes), s_recvStatusFieldIndex) self.statusBar.SetStatusText(s_sendStatusStr + str(s_sendTotalBytes), s_sendStatusFieldIndex) self.statusBar.SetStatusText(s_serialPort.name + ' is open, ' + str(s_serialPort.baudrate) + ', ' + str(s_serialPort.bytesizes) + ', ' + s_serialPort.parity + ', ' + str(s_serialPort.stopbits), s_infoStatusFieldIndex) # 添加代碼結束 s_serialPort.reset_input_buffer() # ... def sendData( self, event ): if s_serialPort.isOpen(): lines = self.m_textCtrl_send.GetNumberOfLines() if lines != 0: # ... s_serialPort.write(data) # 添加代碼開始 global s_sendTotalBytes s_sendTotalBytes += len(data) self.statusBar.SetStatusText(s_sendStatusStr + str(s_sendTotalBytes), s_sendStatusFieldIndex) # 添加代碼結束 else: self.statusBar.SetStatusText(s_serialPort.name + ' is not open !!!', s_infoStatusFieldIndex) def recvData( self ): if s_serialPort.isOpen(): num = s_serialPort.inWaiting() if num != 0: # ... self.m_textCtrl_recv.write(data) # 添加代碼開始 global s_recvTotalBytes s_recvTotalBytes += len(data) self.statusBar.SetStatusText(s_recvStatusStr + str(s_recvTotalBytes), s_recvStatusFieldIndex) # 添加代碼結束 狀態欄實測功能如下:/<code>
二、界面優化
2.1 添加串口開關亮燈效果
界面優化的地方有很多,痞子衡簡單做了一個與串口開關按鈕同步的小燈顯示效果,當串口打開時,小燈顯示綠色;當串口關閉時,小燈顯示黑色;代碼裡的實現其實就是兩張圖片之間的切換。
<code>class mainWin(win.com_win): def openClosePort( self, event ): if s_serialPort.isOpen(): s_serialPort.close() self.m_button_openClose.SetLabel('Open') # 添加代碼開始 self.m_bitmap_led.SetBitmap(wx.Bitmap( u"../img/led_black.png", wx.BITMAP_TYPE_ANY )) # 添加代碼結束 else: # ... self.m_button_openClose.SetLabel('Close') # 添加代碼開始 self.m_bitmap_led.SetBitmap(wx.Bitmap( u"../img/led_green.png", wx.BITMAP_TYPE_ANY )) # 添加代碼結束 # .../<code>