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] == '\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()