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] == '\n':
425 data = data[0:-1]
426 if self.linebreak.get():
427 data = data.replace('\n', '\r\n')
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()