Python串口通信助手

  1 # -*- coding: utf-8 -*-
  2 import sys
  3 import serial
  4 import serial.tools.list_ports
  5 import threading
  6 import signal
  7 import binascii
  8 
  9 #import strings_zh as strings
 10 APP_NAME = '串口调试助手'
 11 APP_VER = '1.0'
 12 AUTHOR = 'txqdm'
 13 
 14 recv_buf_text = "接收缓冲区"
 15 text_mode_text = "文本模式"
 16 hex_mode_text = "16进制模式"
 17 clear_recv_text = "清空接收"
 18 send_buf_text = "发送缓冲区"
 19 line_break_text = "换行符:'\r\n'"
 20 clear_send_text = "清空发送"
 21 send_btn_text = "发送数据"
 22 port_lbl_text = "串口:"
 23 baud_lbl_text = "波特率:"
 24 size_lbl_text = "数据位:"
 25 parity_lbl_text = "校验位:"
 26 stopbit_lbl_text = "停止位:"
 27 send_lbl_text = "SEND"
 28 recv_lbl_text = "RECV"
 29 clear_btn_text = "清除"
 30 opening = "已打开"
 31 closed = "已关闭"
 32 select_port = "请选择串口"
 33 start_port_text = "打开串口"
 34 close_port_text = "关闭串口"
 35 
 36 if sys.version_info.major == 2:
 37     import Tkinter as tk
 38     import tkMessageBox as msgbox
 39 elif sys.version_info.major == 3:
 40     import tkinter as tk
 41     from tkinter import messagebox as msgbox
 42 
 43 
 44 WINDOWSIZE = '710x510+50+50'
 45 # Common baudrate
 46 BAUDRATES = (1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200)
 47 BYTESIZES = (5, 6, 7, 8)
 48 PARITIES = {'None': 'N', 'Even': 'E', 'Odd': 'O', 'Mark': 'M', 'Space': 'S'}
 49 STOPBITS = (1, 1.5, 2)
 50 BYTETYPES = ('1 Byte', '2 Bytes', '4 Bytes')
 51 
 52 TIMEOUT = 0.015
 53 
 54 font = ("宋体", 10, "normal")
 55 font_text = ("Courier", 9, "normal")
 56 font_button = ("黑体", 12, "normal")
 57 
 58 
 59 def format_data(data):
 60         li = data.split(' ')
 61         result = []
 62         k = 0
 63         for each in li:
 64             if len(each) <= 2:
 65                 result.append(each)
 66 
 67             if len(each) > 2:
 68                 while k < len(each):
 69                     result.append(each[k: k + 2])
 70                     k = k + 2
 71                 k = 0
 72 
 73         for i in range(len(result)):
 74             if len(result[i]) == 1:
 75                 result[i] = '0' + result[i]
 76 
 77         return result
 78 
 79 
 80 def getAvailabelSerialPort():
 81     available_ports = []
 82     coms = serial.tools.list_ports.comports()
 83     if coms is not None:
 84         for com in coms:
 85             if sys.platform == 'win32':
 86                 available_ports.append(com.device)
 87             elif sys.platform == 'linux2':
 88                 if com[2] != 'n/a':
 89                     available_ports.append(com[0])
 90     return tuple(available_ports)
 91 
 92 
 93 if sys.platform == 'win32':
 94     PORTS = sorted(getAvailabelSerialPort(), key=lambda n: int(n[3:]))
 95 else:
 96     PORTS = getAvailabelSerialPort()
 97 
 98 
 99 class SerialAssistantGUI(object):
100     def __init__(self):
101         self.root = tk.Tk()
102         self.root.title('%s v%s by %s' % (APP_NAME, APP_VER,
103                                           AUTHOR))
104         self.root.geometry(WINDOWSIZE)
105         self.root.resizable(width=False, height=False)
106 
107         self.__recv_area()
108         self.__send_area()
109         self.__cmd_area()
110         self.__opt_area()
111 
112         self.root.mainloop()
113 
114     def __recv_area(self):
115         recv_lframe = tk.LabelFrame(self.root,
116                                     text=recv_buf_text,
117                                     height=250)
118         recv_lframe.pack(fill=tk.X, padx=5)
119 
120         recv_optframe = tk.Frame(recv_lframe)
121         recv_txtframe = tk.Frame(recv_lframe)
122         recv_optframe.pack(fill=tk.Y, side=tk.LEFT, padx=5, pady=5)
123         recv_txtframe.pack(fill=tk.Y, side=tk.RIGHT, padx=5, pady=5)
124 
125         self.recv_mode = tk.IntVar()
126         self.recv_mode.set(0)
127         recv_radbtn1 = tk.Radiobutton(recv_optframe,
128                                       text=text_mode_text,
129                                       font=font,
130                                       variable=self.recv_mode, value=0)
131         recv_radbtn2 = tk.Radiobutton(recv_optframe,
132                                       text=hex_mode_text,
133                                       font=font,
134                                       variable=self.recv_mode, value=1)
135         recv_clrbtn = tk.Button(recv_optframe, width=15,
136                                 text=clear_recv_text,
137                                 font=font,
138                                 command=self.clear_received)
139         recv_radbtn1.pack(anchor=tk.W)
140         recv_radbtn2.pack(anchor=tk.W)
141         recv_clrbtn.pack(fill=tk.X)
142 
143         self.recv_txtarea = tk.Text(recv_txtframe, height=15, width=74,
144                                     font=font_text)
145         self.recv_txtarea.pack(side=tk.LEFT, fill=tk.X)
146         recv_scrbar = tk.Scrollbar(recv_txtframe)
147         recv_scrbar.pack(side=tk.RIGHT, fill=tk.Y)
148         self.recv_txtarea['yscrollcommand'] = recv_scrbar.set
149         recv_scrbar['command'] = self.recv_txtarea.yview
150 
151     def __send_area(self):
152         send_lframe = tk.LabelFrame(self.root,
153                                     text=send_buf_text,
154                                     height=100)
155         send_lframe.pack(fill=tk.X, padx=5)
156 
157         send_optframe = tk.Frame(send_lframe)
158         send_txtframe = tk.Frame(send_lframe)
159         send_optframe.pack(fill=tk.Y, side=tk.LEFT, padx=5, pady=5)
160         send_txtframe.pack(fill=tk.Y, side=tk.RIGHT, padx=5, pady=5)
161 
162         self.send_mode = tk.IntVar()
163         self.send_mode.set(0)
164         send_radbtn1 = tk.Radiobutton(send_optframe,
165                                       text=text_mode_text,
166                                       font=font,
167                                       variable=self.send_mode, value=0)
168         send_radbtn2 = tk.Radiobutton(send_optframe,
169                                       text=hex_mode_text,
170                                       font=font,
171                                       variable=self.send_mode, value=1)
172         self.linebreak = tk.IntVar()
173         if sys.platform == 'win32':
174             self.linebreak.set(1)
175         elif sys.platform == 'linux2':
176             self.linebreak.set(0)
177         send_chkbtn = tk.Checkbutton(send_optframe,
178                                      text=line_break_text,
179                                      font=font,
180                                      variable=self.linebreak)
181         send_clrbtn = tk.Button(send_optframe,
182                                 text=clear_send_text,
183                                 font=font,
184                                 width=15,
185                                 command=self.clear_sent)
186         send_radbtn1.pack(anchor=tk.W)
187         send_radbtn2.pack(anchor=tk.W)
188         send_chkbtn.pack(anchor=tk.W)
189         send_clrbtn.pack(fill=tk.X)
190 
191         self.send_txtarea = tk.Text(send_txtframe, height=7, width=74,
192                                     font=font_text)
193         self.send_txtarea.pack(side=tk.LEFT, fill=tk.X)
194         send_scrbar = tk.Scrollbar(send_txtframe)
195         send_scrbar.pack(side=tk.RIGHT, fill=tk.Y)
196         self.send_txtarea['yscrollcommand'] = send_scrbar.set
197         send_scrbar['command'] = self.send_txtarea.yview
198 
199     def __cmd_area(self):
200         cmd_frame = tk.Frame(self.root)
201         cmd_frame.pack(fill=tk.X, padx=5, pady=5)
202         #cmd_btn = tk.Button(cmd_frame,
203         #                    text=send_btn_text,
204         #                    font=font,
205         #                    command=self.send_data)
206         #cmd_btn.pack(side=tk.LEFT)
207         
208     def __opt_area(self):
209         opt_frame = tk.Frame(self.root)
210         opt_frame.pack(fill=tk.X, padx=5, pady=5)
211 
212         # Serial port setting
213         port_label = tk.Label(opt_frame,
214                               text=port_lbl_text,
215                               font=font)
216 
217         self.port_var = tk.StringVar()
218         self.port_var.set(select_port)
219 
220         if len(PORTS) == 0:
221             port_opmenu = tk.OptionMenu(opt_frame, self.port_var, '')
222         else:
223             port_opmenu = tk.OptionMenu(opt_frame, self.port_var, *PORTS)
224         port_opmenu.config(anchor=tk.W,
225                            width=10,
226                            font=font)
227         port_label.pack(side=tk.LEFT)
228         port_opmenu.pack(side=tk.LEFT)
229 
230         # Baudrate setting
231         brt_label = tk.Label(opt_frame,
232                              text=baud_lbl_text,
233                              font=font)
234         self.brt_var = tk.StringVar()
235         self.brt_var.set(BAUDRATES[3])
236         brt_opmenu = tk.OptionMenu(opt_frame, self.brt_var, *BAUDRATES)
237         brt_opmenu.config(anchor=tk.W,
238                           width=6,
239                           font=font)
240         brt_label.pack(side=tk.LEFT)
241         brt_opmenu.pack(side=tk.LEFT)
242 
243         # Bytesize setting
244         size_label = tk.Label(opt_frame,
245                               text=size_lbl_text,
246                               font=font)
247         self.size_var = tk.StringVar()
248         self.size_var.set(BYTESIZES[3])
249         size_opmenu = tk.OptionMenu(opt_frame, self.size_var, *BYTESIZES)
250         size_opmenu.config(anchor=tk.W,
251                            width=3,
252                            font=font)
253         size_label.pack(side=tk.LEFT)
254         size_opmenu.pack(side=tk.LEFT)
255 
256         # Parity setting
257         parity_label = tk.Label(opt_frame,
258                                 text=parity_lbl_text,
259                                 font=font)
260         self.parity_var = tk.StringVar()
261         self.parity_var.set('None')
262         parity_opmenu = tk.OptionMenu(opt_frame, self.parity_var,
263                                       *PARITIES)
264         parity_opmenu.config(anchor=tk.W,
265                              width=4,
266                              font=font)
267         parity_label.pack(side=tk.LEFT)
268         parity_opmenu.pack(side=tk.LEFT)
269 
270         # Stopbit setting
271         stop_label = tk.Label(opt_frame,
272                               text=stopbit_lbl_text,
273                               font=font)
274         self.stop_var = tk.StringVar()
275         self.stop_var.set(STOPBITS[0])
276         stop_opmenu = tk.OptionMenu(opt_frame, self.stop_var, *STOPBITS)
277         stop_opmenu.config(anchor=tk.W,
278                            width=3,
279                            font=font)
280         stop_label.pack(side=tk.LEFT)
281         stop_opmenu.pack(side=tk.LEFT)
282 
283         # Set buttons
284         control_frame = tk.Frame(self.root, width=300)
285         status_frame = tk.Frame(self.root, width=300)
286         control_frame.pack(side=tk.LEFT)
287         status_frame.pack(side=tk.RIGHT)
288 
289         start_btn = tk.Button(control_frame,
290                               text=start_port_text,
291                               width=12,font=font_button,
292                               command=self.start_port)
293         close_btn = tk.Button(control_frame,
294                               text=close_port_text,
295                               width=12,font=font_button,
296                               command=self.close_port)
297         cmd_btn = tk.Button(control_frame,
298                            text=send_btn_text,
299                             width=12,font=font_button,
300                             command=self.send_data)
301         start_btn.pack(side=tk.LEFT, padx=5)
302         close_btn.pack(side=tk.LEFT, padx=5)
303         cmd_btn.pack(side=tk.LEFT, padx=5)
304         self.state_lbl = tk.Label(control_frame, text='')
305         self.state_lbl.pack(side=tk.LEFT, padx=5)
306 
307         # Status frame widgets
308         send_cnt_label = tk.Label(status_frame,
309                                   text=send_lbl_text,
310                                   font=font)
311         self.send_cnt = tk.StringVar()
312         self.send_cnt.set(self.TX)
313         send_cnt_entry = tk.Entry(status_frame,
314                                   textvariable=self.send_cnt,
315                                   width=10,font=font,
316                                   relief=tk.SUNKEN,
317                                   state=tk.DISABLED,
318                                   justify=tk.RIGHT)
319         send_cnt_label.pack(side=tk.LEFT)
320         send_cnt_entry.pack(side=tk.LEFT)
321 
322         recv_cnt_label = tk.Label(status_frame,
323                                   text=recv_lbl_text,
324                                   font=font)
325         self.recv_cnt = tk.StringVar()
326         self.recv_cnt.set(self.RX)
327         recv_cnt_entry = tk.Entry(status_frame,
328                                   textvariable=self.recv_cnt,
329                                   width=10,font=font,
330                                   relief=tk.SUNKEN,
331                                   state=tk.DISABLED,
332                                   justify=tk.RIGHT)
333         recv_cnt_label.pack(side=tk.LEFT)
334         recv_cnt_entry.pack(side=tk.LEFT)
335 
336         clr_btn = tk.Button(status_frame,
337                             text=clear_btn_text,
338                             width=12,font=font_button,
339                             command=self.clear_count)
340         #clr_btn.pack()
341         clr_btn.pack(side=tk.LEFT, padx=10)
342 
343     def clear_received(self):
344         self.recv_txtarea.delete(0.0, tk.END)
345 
346     def clear_sent(self):
347         self.send_txtarea.delete(0.0, tk.END)
348 
349     def clear_count(self):
350         pass
351 
352     def start_port(self):
353         pass
354 
355     def send_data(self):
356         pass
357 
358     def recv_data(self):
359         pass
360 
361 
362 class SerialAssistant(SerialAssistantGUI):
363 
364     portisopen = 0
365     TX = 0
366     RX = 0
367 
368     def __init__(self):
369         super(SerialAssistant, self).__init__()
370 
371     def clear_count(self):
372         self.RX = 0
373         self.TX = 0
374         self.send_cnt.set(self.RX)
375         self.recv_cnt.set(self.TX)
376 
377     def start_port(self):
378         port = self.port_var.get()
379         baudrate = int(self.brt_var.get())
380         bytesize = int(self.size_var.get())
381         parity = PARITIES[self.parity_var.get()]
382         stopbits = float(self.stop_var.get())
383 
384         try:
385             self.s = serial.Serial(port=port,
386                                    baudrate=baudrate,
387                                    bytesize=bytesize,
388                                    parity=parity,
389                                    stopbits=stopbits,
390                                    timeout=TIMEOUT)
391         except serial.SerialException as e:
392             msgbox.showerror("OpenError", e)
393         except serial.SerialTimeoutException as e:
394             msgbox.showerror("OpenError", e)
395         else:
396             self.portisopen = 1
397             self.state_lbl.config(background='green',
398                                   text=port + opening)
399             self.th = threading.Thread(target=self.recv_data)
400             self.th.daemon = True
401             self.th.start()
402 
403     def send_data(self):
404         if self.portisopen:
405             data_fmt = []
406             data = self.send_txtarea.get(0.0, tk.END)
407 
408             if len(data) == 1:
409                 return
410 
411             if self.send_mode.get():
412                 if data[-1] == 'x0a':
413                     data = data[0:-1]
414                 data_fmt = format_data(data)
415                 for each in data_fmt:
416                     try:
417                         x = binascii.a2b_hex(each)
418                     except TypeError:
419                         x = 'x00'
420 
421                     self.s.write(x)
422                     self.TX += 1
423             else:
424                 if data[-1] == '
':
425                     data = data[0:-1]
426                 if self.linebreak.get():
427                     data = data.replace('
', '
')
428 
429                 self.s.write(data.encode('utf-8'))
430                 self.TX += len(data)
431             self.send_cnt.set(self.TX)
432         else:
433             msgbox.showerror("Error", "Port NOT open!")
434             return
435 
436     def recv_data(self):
437         while self.portisopen:
438             data = self.s.read()
439             if len(data) != 0:
440                 if self.recv_mode.get() == 1:
441                     data = binascii.b2a_hex(data)
442                     if sys.version_info.major == 3:
443                         data += b' '
444                     elif sys.version_info.major == 2:
445                         data += ' '
446                     self.RX += 1
447                 else:
448                     self.RX += len(data)
449             self.recv_cnt.set(self.RX)
450             if data != 'x0d':
451                 self.recv_txtarea.insert(tk.END, data)
452                 self.recv_txtarea.see(tk.END)
453         return
454 
455     def close_port(self):
456         if self.portisopen:
457             self.portisopen = 0
458             self.s.close()
459             self.state_lbl.config(background='red',
460                                   text=self.port_var.get() + closed)
461         else:
462             return
463 
464     def handler(self, signum, frame):
465         self.portisopen = 0
466         self.th.join()
467 
468     def __del__(self):
469         self.close_port()
470         signal.signal(signal.SIGINT, self.handler)
471 
472 
473 def main():
474     SerialAssistant()
475 
476 
477 if __name__ == '__main__':
478     main()
原文地址:https://www.cnblogs.com/txqdm/p/13787102.html