/
SerialPort.py
1242 lines (1147 loc) · 46.1 KB
/
SerialPort.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
"""
Module implementing SerialPort.
"""
import serial, serial.tools.list_ports, threading, re
import sys, time, traceback
from datetime import datetime
from PyQt5.QtCore import pyqtSlot, QAbstractNativeEventFilter, QSettings, pyqtSignal, QSize, QEvent, Qt
from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QLabel, QFontDialog, QMenu, QToolButton, QShortcut, QColorDialog
from PyQt5.QtGui import QTextCursor, QFont, QIcon
from io import StringIO
import json
import ctypes.wintypes
from Ui_SerialPort import Ui_MainWindow
from Codec import Codec
from About import About
from AutoConnect import AutoConnect
from option import Option
from common import Common
'''
待解决问题
1、添加当前端口号列表
2、选择自动连接后禁止端口选择
3、自动换行检测行头换行 完成
4、添加序号标记 终止
5、添加时间标记 完成
6、添加字符串监控 完成
7、字体设置 完成
8、保存设置参数 完成
10、自动连接时漏接起始接收数据 未完成
在IDE里不会漏接。
在IDE外直接运行程序有漏接现象。去掉多余调试打印信息后漏接现象减少,推测在IDE外print打印会占用更多时间。
11、textBrowser.append 添加文本会自动换行 已解决
12、显示时间时会自动换行,导致换行前已在行头时,会多出一个空行 已解决
13、数据量达到限值清除时,异常退出 已解决
- 初步分析清除调用 textBrowser.clear 导致
- 子线程调用 GUI 更新所致
14、添加收发标记
15、按照 html/css 语法实现全局颜色控制
16、接收为查询接收,不利于时间测量
'''
SERIAL_STOP = 0
SERIAL_RUN = 1
SERIAL_PAUSE = 2
class SerialPort(QMainWindow, Ui_MainWindow):
"""
Class documentation goes here.
"""
sigDispaly = pyqtSignal(str)
sigRxCnt = pyqtSignal(int)
sigLcdNum = pyqtSignal(int)
def __init__(self, parent=None):
"""
Constructor
@param parent reference to the parent widget
@type QWidget
"""
super(SerialPort, self).__init__(parent)
self.setupUi(self)
# 去掉标题栏
# self.setWindowFlags(Qt.FramelessWindowHint)
# self.setWindowIcon(QIcon(':/icon/resource/icon/serial256.ico'))
self.toolBar.setIconSize(QSize(40,40))
self.settings = QSettings("./user.ini", QSettings.IniFormat)
#接收栏字体
textBrowserFont = self.settings.value('Font')
if textBrowserFont:
textBrowserFont = textBrowserFont.split(',')
if len(textBrowserFont) == 3:
family = textBrowserFont[0]
pointSize = int(textBrowserFont[1])
weight = int(textBrowserFont[2])
font = QFont(family, pointSize, weight)
if font:
self.textBrowser.setFont(font)
#显示时间
DisplayTime = self.settings.value('Time')
if DisplayTime and DisplayTime == '1':
self.checkBoxTime.setChecked(True)
else:
self.checkBoxTime.setChecked(False)
#自动换行
AutoWrap = self.settings.value('AutoWrap')
if AutoWrap and AutoWrap == '1':
self.checkBoxNewLine.setChecked(True)
else:
self.checkBoxNewLine.setChecked(False)
#接收格式 1:ASCII 0:HEX
RecvFormat = self.settings.value('RecvFormat')
if RecvFormat and RecvFormat == "0":
self.radioButtonRecvHex.setChecked(True)
else:
self.radioButtonRecvASCII.setChecked(True)
#发送格式 1:ASCII 0:HEX
SendFormat = self.settings.value('SendFormat')
if SendFormat and SendFormat == "0":
self.radioButtonSendHex.setChecked(True)
else:
self.radioButtonSendASCII.setChecked(True)
# 运行状态
self.runStates = SERIAL_STOP
#自动连接端口
AutoConnectPort = self.settings.value('Auto')
if AutoConnectPort:
self.AutoConnectPort = AutoConnectPort
else:
self.AutoConnectPort = None
#获取端口
self.port = None
port_list=list(serial.tools.list_ports.comports())
port_list.sort()
for port_list_0 in port_list:
port = list(port_list_0)
self.comboBoxPort.addItem(port[1])
self.comboBoxBaud.addItems(["9600", "19200", "38400", "57600", "115200", "Custom"])
self.comboBoxBaud.setCurrentIndex(4)
self.comboBoxDataBit.addItems(["5", "6", "7", "8"])
self.comboBoxDataBit.setCurrentIndex(3)
self.comboBoxParity.addItems(["None", "Even", "Odd", "Mark", "Space"])
self.comboBoxStopBit.addItems(["1", "1.5", "2"])
self.comboBoxFlow.addItems(["None", "RTS/CTS", "XON/XOFF"])
self.serial = serial.Serial()
self.autoScroll = True #接收自动滚动
#接收监测
self.monitorCnt = 0
self.lcdNumber.display(self.monitorCnt)
MonitorEnable = self.settings.value('Monitor')
if MonitorEnable and MonitorEnable == '1':
self.checkBoxMonitor.setChecked(True)
else:
self.checkBoxMonitor.setChecked(False)
#提示音
BeepEnable = self.settings.value('Beep')
if BeepEnable and BeepEnable == '1':
self.checkBoxBeep.setChecked(True)
else:
self.checkBoxBeep.setChecked(False)
#设置栏
sideView = self.settings.value('sideView')
if sideView and sideView == '0':
self.sideView.setChecked(False)
else:
self.sideView.setChecked(True)
#发送栏
sendView = self.settings.value('SendView')
if sendView and sendView == '0':
self.sendView.setChecked(False)
else:
self.sendView.setChecked(True)
# 发送快捷键
self.sendShortcut = QShortcut(self)
self.sendShortcut.setKey("Ctrl+Return")
self.sendShortcut.setAutoRepeat(False)
self.sendShortcut.activated.connect(self.on_pushButtonSend_clicked)
# 发送历史
self.sendHistory = []
# 发送历史向前快捷键
self.historyUpShortcut = QShortcut(self)
self.historyUpShortcut.setKey("Ctrl+Up")
self.historyUpShortcut.activated.connect(self.serial_send_history_up)
# 发送历史向前快捷键
self.historyDownShortcut = QShortcut(self)
self.historyDownShortcut.setKey("Ctrl+Down")
self.historyDownShortcut.activated.connect(self.serial_send_history_down)
# 清除快捷键
self.clearShortcut = QShortcut(self)
self.clearShortcut.setKey("Ctrl+Delete")
self.clearShortcut.setAutoRepeat(False)
self.clearShortcut.activated.connect(self.on_clear_triggered)
#显示发送
EchoEnable = self.settings.value('Echo')
if EchoEnable and EchoEnable == '1':
self.checkBoxEcho.setChecked(True)
else:
self.checkBoxEcho.setChecked(False)
#添加回车
SendReturnEnable = self.settings.value('Return')
if SendReturnEnable and SendReturnEnable == '1':
self.sendReturn.setChecked(True)
else:
self.sendReturn.setChecked(False)
#转义序列
EscapeEnable = self.settings.value('Escape')
if EscapeEnable and EscapeEnable == '1':
self.sendEscape.setChecked(True)
else:
self.sendEscape.setChecked(False)
#发送列表
SendList = self.settings.value('SendList')
if SendList:
self.comboBoxSend.addItems(SendList)
#收发统计
self.rxCount = 0
self.txCount = 0
self.SelectByte = 0
self.SelectWord = 0
#状态条信息
self.InfoPort = QLabel()
self.InfoPort.setStyleSheet("color: red;font: 9pt 'Arial'")
self.InfoPort.setText('CLOSED')
self.statusBar.addWidget(self.InfoPort, 2)
self.InfoRx = QLabel()
self.InfoRx.setText('RX: {} Bytes'.format(self.rxCount))
self.statusBar.addWidget(self.InfoRx, 1)
self.InfoTx = QLabel()
self.InfoTx.setText('TX: {} Bytes'.format(self.txCount))
self.statusBar.addWidget(self.InfoTx, 1)
self.InfoSelect = QLabel()
self.InfoSelect.setAlignment(Qt.AlignHCenter)
self.InfoSelect.setStyleSheet("font: 9pt 'Microsoft YaHei'")
self.InfoSelect.setText('{} 词 / {} 字'.format(self.SelectWord, self.SelectByte))
self.statusBar.addWidget(self.InfoSelect, 1)
# 缓存流
self.memStream = StringIO()
self.streamCursor = 0
self.sigDispaly.connect(self.stream_displayRender)
self.sigRxCnt.connect(self.rxCntUpdate)
self.sigLcdNum.connect(self.lcdNumUpdate)
self.resendThreadState = False
self.resendThread = threading.Thread(target=self.serial_resendThread, name='resendThread')
# 渲染事件
self.renderEvent = threading.Event()
# 视图工具
viewMenu = QMenu("view")
viewMenu.addAction(self.sideView)
viewMenu.addAction(self.sendView)
self.viewLayout = QToolButton()
self.viewLayout.setMenu(viewMenu)
self.viewLayout.setToolTip("视图")
self.viewLayout.setPopupMode(QToolButton.MenuButtonPopup)
self.viewLayout.setIcon(QIcon(':/icon/resource/icon/view48.png'))
self.toolBar.insertWidget(self.option, self.viewLayout)
self.viewLayout.clicked.connect(self.on_viewLayout_clicked)
# 异常捕获
sys.excepthook = self.unknown_exceptions
# 未知异常捕获
def unknown_exceptions(self, ExceptType, ExceptValue, Traceback):
traceback_format = traceback.format_exception(ExceptType, ExceptValue, Traceback)
traceback_string = "".join(traceback_format)
fError = open("error.log", 'a')
print(datetime.now(), file = fError)
print('{}'.format(traceback_string), file=fError)
fError.close()
QMessageBox.warning(self, ExceptType.__name__, '{}'.format(traceback_string),
QMessageBox.Ok, QMessageBox.Ok)
#窗口改变事件
def changeEvent(self, event):
if event.type() != QEvent.WindowStateChange:
return
if self.windowState() == Qt.WindowNoState:
self.serial_recvAutoScroll()
print('%s: %d %08X'%(sys._getframe().f_code.co_name, event.type(), self.windowState()))
def closeEvent(self, event):
if event.type() == 19:
print('Close type: close event')
else:
print('Close type:', event.type())
self.serial_close()
sendList = []
count = self.comboBoxSend.count()
for i in range(0, count):
sendList.append(self.comboBoxSend.itemText(i))
self.settings.setValue('SendList', sendList)
def lcdNumUpdate(self, num):
self.lcdNumber.display(num)
def rxCntUpdate(self, cnt):
self.InfoRx.setText('RX: {} Bytes'.format(cnt))
# html 特殊字符处理
def htmlCharProcess(self, data):
data = re.sub('(&)', '&', data)
data = re.sub('(<)', '<', data)
data = re.sub('(>)', '>', data)
return data
#行首检测
def stream_isHome(self):
if self.streamCursor > 0:
self.memStream.seek(self.streamCursor-1, 0)
endchar = self.memStream.read(1)
if endchar != '\n':
return False
return True
def stream_write(self, data):
self.memStream.seek(0, 2)
self.memStream.write(data)
def stream_displayRender(self, data):
#自动下拉滚动条
if self.textBrowser.verticalScrollBar().value()==self.textBrowser.verticalScrollBar().maximum():
self.autoScroll = True
else:
self.autoScroll = False
textCursor = self.textBrowser.textCursor()
textCursor.movePosition(QTextCursor.End)
textCursor.insertHtml(data)
def stream_renderThread(self):
while self.recvThreadState:
textBuff = ''
self.renderEvent.wait()
self.renderEvent.clear()
self.memStream.seek(0, 2)
offset = self.memStream.tell()
while self.streamCursor < offset:
# time.sleep(0.01)
streamRead = self.streamCursor
self.memStream.seek(streamRead, 0)
dataHead = self.memStream.readline()
jsonHead = json.loads(dataHead)
dataLength = jsonHead['Length']
data = self.memStream.read(dataLength)
streamRead = self.memStream.tell()
lineHomeAdd = 0
# html 特殊字符处理
data = self.htmlCharProcess(data)
received = jsonHead['Received']
# 接收监测
if received:
monitorEnable = jsonHead['MonitorEnable']
monitor = self.htmlCharProcess(jsonHead['Monitor'])
if monitorEnable and len(monitor):
monitorFont = '<span style="background-color: #ffff00">' + monitor + '</span>'
data = data.replace(monitor, monitorFont)
# 时间戳
timeEnable = jsonHead['TimeEnable']
if timeEnable:
timestamp = jsonHead['Timestamp']
timestamp = '<font color=#800040>' + timestamp + '</font>'
if not re.match('(\r\n|\n)', data):
data = timestamp + data
else:
data = re.sub('^(\r\n|\n)', '', data)
if len(data):
data = timestamp + '<br />' + timestamp + data
else:
data = timestamp + '<br />'
data = re.sub('(\r\n|\n)$', '<br />', data)
data = re.sub('(\r\n|\n)', '<br />'+timestamp, data)
# 时间戳使能则自动换行
if not self.stream_isHome():
lineHomeAdd = 1
else:
data = re.sub('(\r\n|\n)', '<br />', data)
if received: #接收显示
data = '<font color=#000000>' + data + '</font>'
# 自动换行
lineEnable = jsonHead['LineEnable']
if lineEnable:
if not self.stream_isHome():
lineHomeAdd = 1
else: #发送显示
data = '<font color=#008000>' + data + '</font>'
# 发送显示自动换行
if not self.stream_isHome():
lineHomeAdd = 1
if lineHomeAdd:
data = '<br />' + data
print('Insert line home')
# textCursor.insertHtml(data)
textBuff += data
self.streamCursor = streamRead
# 指针移到最后
self.memStream.seek(0, 2)
offset = self.memStream.tell()
self.sigDispaly.emit(textBuff)
def serial_recvAutoScroll(self):
if self.autoScroll:
#self.textBrowser.moveCursor(QTextCursor.End)
max = self.textBrowser.verticalScrollBar().maximum()
self.textBrowser.verticalScrollBar().setSliderPosition(max)
def serial_recvFont(self, font):
"""
Slot documentation goes here.
"""
print('serial_receive_font', font.family(), font.pointSize(), font.weight())
self.textBrowser.setFont(font)
font = '{},{},{}'.format(font.family(), font.pointSize(), font.weight())
self.settings.setValue('Font', font)
def serial_recvThread(self):
# print(threading.current_thread().name, "start")
while self.recvThreadState:
try:
self.serial.timeout = None
# 阻塞获取一个字节
data = self.serial.read()
# 显示时间
if self.checkBoxTime.isChecked():
jsonTimeEnable = 1
jsonTimestamp = '['+datetime.now().strftime('%H:%M:%S.%f') [:-3]+']'
else:
jsonTimeEnable = 0
jsonTimestamp = ''
self.serial.timeout = 0.002
while True:
temp = self.serial.read(500)
if temp and len(temp):
data += temp
if len(temp) < 500:
break
self.serial.timeout = None
except Exception as e:
self.recvThreadState = False
if str(e).find('OSError(9,') != -1:
print(sys._getframe().f_code.co_name + ':', '端口关闭,退出接收')
else:
print('{}[{}]: {}'.format(sys._getframe().f_code.co_name, sys._getframe().f_lineno, str(e)))
print(traceback.format_exc())
break
if data and not self.memStream.closed:
if self.rxCount > 500*10000:
self.clear.trigger()
time.sleep(0.05)
self.rxCount += len(data)
self.sigRxCnt.emit(self.rxCount)
if self.radioButtonRecvASCII.isChecked():
data = data.decode("gbk", "ignore")
else:
data = ' '.join("%02X" % x for x in data)
data = data + ' '
#接收监测
if self.checkBoxMonitor.isChecked():
jsonMonitorEnable = 1
jsonMonitorString = self.lineEditMonitor.text()
if len(self.lineEditMonitor.text()):
cnt = data.count(self.lineEditMonitor.text())
if cnt > 0:
self.monitorCnt += cnt
self.sigLcdNum.emit(self.monitorCnt)
if self.checkBoxBeep.isChecked():
QApplication.beep()
else:
jsonMonitorEnable = 0
jsonMonitorString = ''
#自动换行
if self.checkBoxNewLine.isChecked():
jsonLineEnable = 1
else:
jsonLineEnable = 0
jsonRecv = 1
jsonDataLength = len(data)
jsonHead = '{"Received":%d, "Length":%d, "TimeEnable":%d, "Timestamp":"%s", "LineEnable":%d, '\
'"MonitorEnable":%d, "Monitor":"%s"}\n'%(jsonRecv, jsonDataLength, jsonTimeEnable, \
jsonTimestamp, jsonLineEnable, jsonMonitorEnable, jsonMonitorString)
self.stream_write(jsonHead)
self.stream_write(data)
self.renderEvent.set()
continue
def serial_recvThreadStart(self):
self.recvThreadState = True
self.recvThread = threading.Thread(target=self.serial_recvThread, name='recvThread')
# join 和 setDaemon 作用相反,前者等待子线程结束,后者不等子线程结束,有可能把子线程强制结束。
# 如果都不设置,主线程和子线程各自运行,互不影响
# setDaemon必须在start() 方法调用之前设置,否则程序会被无限挂起。参数True表示主调线程为为守护线程,
self.recvThread.setDaemon(True)
self.recvThread.start()
#join在start()之后调用,参数为超时时间
#self.recvThread.join()
self.renderThread = threading.Thread(target=self.stream_renderThread, name='renderThread')
self.renderThread.setDaemon(True)
self.renderThread.start()
def serial_recvThreadEnd(self):
self.recvThreadState = False
while self.recvThread.is_alive():
continue
def serial_resendThread(self):
while self.resendThreadState:
if self.spinBoxTime.value():
self.serial_send()
time.sleep(self.spinBoxTime.value()/1000)
self.pushButtonSend.setText("发 送")
def serial_send_history_add(self, item):
self.comboBoxSend.blockSignals(True)
currentText = self.comboBoxSend.currentText()
count = self.comboBoxSend.count()
for i in range(0, count):
if self.comboBoxSend.itemText(i) == item:
self.comboBoxSend.removeItem(i)
break
self.comboBoxSend.insertItem(0, item)
print('serial_send_history_add:', item)
count = self.comboBoxSend.count()
if count > 15:
self.comboBoxSend.removeItem(count-1)
index = self.comboBoxSend.findText(currentText)
if index == -1:
index = 0
self.comboBoxSend.setCurrentIndex(index)
self.comboBoxSend.blockSignals(False)
@pyqtSlot()
def serial_send_history_up(self):
self.comboBoxSend.blockSignals(True)
count = self.comboBoxSend.count()
if count <= 1:
return
index = self.comboBoxSend.currentIndex()
inc = count - 1
index = (index + inc)%count
self.comboBoxSend.setCurrentIndex(index)
text = self.comboBoxSend.itemText(index)
self.plainTextEdit.setPlainText(text)
self.comboBoxSend.blockSignals(False)
@pyqtSlot()
def serial_send_history_down(self):
self.comboBoxSend.blockSignals(True)
count = self.comboBoxSend.count()
if count <= 1:
return
index = self.comboBoxSend.currentIndex()
inc = 1
index = (index + inc)%count
self.comboBoxSend.setCurrentIndex(index)
text = self.comboBoxSend.itemText(index)
self.plainTextEdit.setPlainText(text)
self.comboBoxSend.blockSignals(False)
def serial_send(self):
if self.serial.isOpen():
inputString = self.plainTextEdit.toPlainText()
if len(inputString) == 0:
return
self.serial_send_history_add(inputString)
data=''
hexData=''
if self.radioButtonSendASCII.isChecked():
if len(inputString):
# 转义替换
if self.sendEscape.isChecked():
inputString = inputString.replace('\\r', '\r')
inputString = inputString.replace('\\n', '\n')
if self.sendReturn.isChecked():
inputString = inputString + '\r\n'
data += inputString
hexData=inputString.encode("gbk")
else:
hexDataList = []
datasplit = re.split(r"[^0-9a-fA-F]+", inputString)
for x in datasplit:
i=0
while x[i:i+2]:
hexDataList.append(int(x[i:i+2], 16))
data+=' %02X'%int(x[i:i+2], 16)
i += 2
hexData=bytes(hexDataList)
if len(hexData):
self.serial.write(hexData)
self.txCount += len(hexData)
self.InfoTx.setText('TX: {} Bytes'.format(self.txCount))
else:
return
#发送回显
if self.checkBoxEcho.isChecked():
if self.radioButtonRecvASCII.isChecked():
data = hexData.decode('gbk', errors='ignore')
else:
data = ''
for x in hexData:
data = data + '%02X '%x
data = data.lstrip()
#发送结束换行
if data[-1] != '\n':
data = data + '\n'
#显示时间
if self.checkBoxTime.isChecked():
jsonTimeEnable = 1
jsonTimestamp = '['+datetime.now().strftime('%H:%M:%S.%f') [:-3]+']'
else:
jsonTimeEnable = 0
jsonTimestamp = ''
jsonRecv = 0
jsonDataLength = len(data)
jsonHead = '{"Received":%d, "Length":%d, "TimeEnable":%d, "Timestamp":"%s"}\n'%(jsonRecv, jsonDataLength, jsonTimeEnable, jsonTimestamp)
self.stream_write(jsonHead)
self.stream_write(data)
self.renderEvent.set()
def serial_open(self):
if self.serial.isOpen():
if self.serial.port == self.port and \
self.serial.baudrate == self.baudrate and \
self.serial.bytesize == self.bytesize and \
self.serial.parity == self.parity and \
self.serial.stopbits == self.stopbits and \
self.serial.xonxoff == self.xonxoff and \
self.serial.rtscts == self.rtscts:
print(sys._getframe().f_code.co_name + ':', 'opend')
return True
else:
self.serial_close()
try:
print(sys._getframe().f_code.co_name + ':', self.port)
self.serial = serial.Serial(port=self.port, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits,
xonxoff=self.xonxoff, rtscts=self.rtscts)
except serial.SerialException as e:
self.InfoPort.clear()
QApplication.processEvents()
time.sleep(0.1)
print('%s[%d]:%s'%(sys._getframe().f_code.co_name, sys._getframe().f_lineno, str(e)))
if str(e).find('PermissionError') != -1:
print(sys._getframe().f_code.co_name + ':', 'PermissionError')
elif str(e).find('FileNotFoundError') != -1:
print(sys._getframe().f_code.co_name + ':', 'FileNotFoundError')
elif str(e).find('OSError(22,') != -1:
print(sys._getframe().f_code.co_name + ':', '请求的资源在使用中')
QMessageBox.question(self, '{}'.format(self.port), '无法打开 {}\n请确认是否占用'.format(self.port),
QMessageBox.Ok, QMessageBox.Ok)
else:
print(sys._getframe().f_code.co_name + ':', 'Undefined exception!')
self.InfoPort.setStyleSheet("color: red;font: 9pt 'Arial'")
self.InfoPort.setText('{} 拒绝访问'.format(self.port))
return False
self.InfoPort.setStyleSheet("color: green;font: 9pt 'Arial'")
self.InfoPort.setText('{} OPENED {} {} {} {}'.format(self.port, self.baudrate, self.bytesize, self.parity, self.stopbits))
self.serial_recvThreadStart()
if self.runStates == SERIAL_STOP:
self.runStates = SERIAL_RUN
self.run.setIcon(QIcon(':/icon/resource/icon/pause_bk48.png'))
return True
def serial_close(self):
if self.serial.isOpen():
port = self.serial.port
self.serial.close()
self.serial_recvThreadEnd()
self.resendThreadState = False
self.InfoPort.setStyleSheet("color: red;font: 9pt 'Arial'")
self.InfoPort.setText('{} CLOSED'.format(port))
print('{}: {}'.format(sys._getframe().f_code.co_name, port))
else:
print('{}: closed'.format(sys._getframe().f_code.co_name))
if self.runStates != SERIAL_STOP:
self.runStates = SERIAL_STOP
self.run.setIcon(QIcon(':/icon/resource/icon/trist48.png'))
#端口刷新
def serial_port_refresh(self):
self.comboBoxPort.blockSignals(True)
self.comboBoxPort.clear()
portNameList = []
port_list=list(serial.tools.list_ports.comports())
port_list.sort()
for port_list_0 in port_list:
port = list(port_list_0)
self.comboBoxPort.addItem(port[1])
portNameList.append(port[0])
if self.port is None:
if self.comboBoxPort.count():
self.port = portNameList[self.comboBoxPort.currentIndex()]
if self.port not in portNameList:#端口移除
print('{}: remove {}'.format(sys._getframe().f_code.co_name, self.port))
self.serial_close()
else:
print('{}: insert {}'.format(sys._getframe().f_code.co_name, self.port))
# print(portNameList)
self.comboBoxPort.setCurrentIndex(portNameList.index(self.port))
if self.actionAutoConnect.isChecked():
self.serial_open()
self.comboBoxPort.blockSignals(False)
def serial_port_set(self, port):
portComList = []
port_list=list(serial.tools.list_ports.comports())
port_list.sort()
for port_list_0 in port_list:
com = list(port_list_0)
portComList.append(com[0])
if port in portComList:
self.comboBoxPort.setCurrentIndex(portComList.index(port))
return True
else:
return False
def view_send_visible(self, visible):
if visible:
self.dataLayout.insertLayout(1, self.sendBox)
self.sendBox.insertWidget(0, self.plainTextEdit)
self.plainTextEdit.show()
self.sendBox.insertWidget(1, self.pushButtonSend)
self.pushButtonSend.show()
self.dataLayout.insertWidget(2, self.comboBoxSend)
self.comboBoxSend.show()
self.serial_recvAutoScroll()
else:
self.plainTextEdit.setParent(None)
self.pushButtonSend.setParent(None)
self.comboBoxSend.setParent(None)
self.dataLayout.removeItem(self.sendBox)
@pyqtSlot()
def on_textBrowser_textChanged(self):
"""
Slot documentation goes here.
"""
self.serial_recvAutoScroll()
@pyqtSlot()
def on_textBrowser_selectionChanged(self):
"""
Slot documentation goes here.
"""
text = self.textBrowser.textCursor().selectedText()
self.SelectByte, self.SelectWord = Common.word_count(text)
self.InfoSelect.setText('{} 词 / {} 字'.format(self.SelectWord, self.SelectByte))
@pyqtSlot()
def on_plainTextEdit_selectionChanged(self):
"""
Slot documentation goes here.
"""
text = self.plainTextEdit.textCursor().selectedText()
self.SelectByte, self.SelectWord = Common.word_count(text)
self.InfoSelect.setText('{} 词 / {} 字'.format(self.SelectWord, self.SelectByte))
@pyqtSlot(bool)
def on_checkBoxResend_toggled(self, checked):
"""
Slot documentation goes here.
@param checked DESCRIPTION
@type bool
"""
if not checked:
self.resendThreadState=False
@pyqtSlot()
def on_pushButtonSend_clicked(self):
"""
Slot documentation goes here.
"""
if self.checkBoxResend.isChecked():
if self.resendThreadState:
self.resendThreadState = False
self.pushButtonSend.setText("发 送")
else:
print('resend', self.serial.isOpen())
if not self.serial.isOpen():
print('resend not', self.serial.isOpen())
return
print('resend2', self.serial.isOpen())
self.resendThreadState = True
self.pushButtonSend.setText("停 止")
if not self.resendThread.is_alive():
self.resendThread = threading.Thread(target=self.serial_resendThread, name='resendThread')
#join和setDaemon作用相反,前者等待子线程结束,后者不等子线程结束,有可能把子线程强制结束。
#如果都不设置,主线程和子线程各自运行,互不影响
#setDaemon必须在start() 方法调用之前设置,否则程序会被无限挂起。参数True表示主调线程为为守护线程,
self.resendThread.setDaemon(True)
self.resendThread.start()
#join在start()之后调用,参数为超时时间
#self.resendThread.join()
else:
self.serial_send()
@pyqtSlot(bool)
def on_run_triggered(self):
"""
Slot documentation goes here.
@param checked DESCRIPTION
@type bool
"""
if self.runStates == SERIAL_STOP:
portNameList = []
port_list=list(serial.tools.list_ports.comports())
port_list.sort()
for port_list_0 in port_list:
port = list(port_list_0)
portNameList.append(port[0])
if self.comboBoxPort.count() == 0:
return
if self.port is None:
self.port = portNameList[self.comboBoxPort.currentIndex()]
elif self.port not in portNameList:
self.port = portNameList[self.comboBoxPort.currentIndex()]
self.serial_open()
# print(sys._getframe().f_code.co_name + ': Open failed.', 'Port is {}'.format(self.port))
elif self.runStates == SERIAL_RUN:
self.runStates = SERIAL_PAUSE
self.run.setIcon(QIcon(':/icon/resource/icon/trist_bk48.png'))
elif self.runStates == SERIAL_PAUSE:
self.runStates = SERIAL_RUN
self.run.setIcon(QIcon(':/icon/resource/icon/pause_bk48.png'))
else:
print(sys._getframe().f_code.co_name + ':', 'run state error!')
@pyqtSlot()
def on_stop_triggered(self):
"""
Slot documentation goes here.
"""
self.actionAutoConnect.setChecked(False)
self.serial_close()
@pyqtSlot()
def on_clear_triggered(self):
"""
Slot documentation goes here.
"""
self.rxCount = 0
self.txCount = 0
self.streamCursor = 0
self.memStream.close()
self.textBrowser.clear()
self.InfoRx.setText('RX: {} Bytes'.format(self.rxCount))
self.InfoTx.setText('TX: {} Bytes'.format(self.txCount))
self.monitorCnt = 0
self.lcdNumber.display(self.monitorCnt)
self.memStream = StringIO()
@pyqtSlot(int)
def on_comboBoxPort_currentIndexChanged(self, index):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
if index == -1:
return
port_list=list(serial.tools.list_ports.comports())
port_list.sort()
self.port = list(port_list)[index][0]
print('comboBoxPort', self.port)
if self.actionAutoConnect.isChecked():
self.AutoConnectPort = self.port
self.settings.setValue('Auto', self.AutoConnectPort)
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(int)
def on_comboBoxBaud_currentIndexChanged(self, index):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
if self.comboBoxBaud.currentIndex()>=5:
self.comboBoxBaud.setEditable(True)
else:
self.comboBoxBaud.setEditable(False)
if self.comboBoxBaud.currentText().isdigit():
self.baudrate=int(self.comboBoxBaud.currentText())
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(str)
def on_comboBoxBaud_editTextChanged(self, p0):
"""
Slot documentation goes here.
@param p0 DESCRIPTION
@type str
"""
if self.comboBoxBaud.currentText().isdigit() and self.comboBoxBaud.currentIndex()>=5:
self.baudrate=int(self.comboBoxBaud.currentText())
print("Baud change %d" % self.baudrate)
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(int)
def on_comboBoxDataBit_currentIndexChanged(self, index):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
self.bytesize=int(self.comboBoxDataBit.currentText())
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(int)
def on_comboBoxFlow_currentIndexChanged(self, index):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
if index == 0:
self.xonxoff=False
self.rtscts=False
elif index == 1:
self.xonxoff=False
self.rtscts=True
elif index == 2:
self.xonxoff=True
self.rtscts=False
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(int)
def on_comboBoxStopBit_currentIndexChanged(self, index):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
self.stopbits=float(self.comboBoxStopBit.currentText())
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(int)
def on_comboBoxParity_currentIndexChanged(self, index):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
parity=["N", "E", "O", "M", "S"]
self.parity=parity[index]
if self.runStates != SERIAL_STOP:
self.serial_open()
@pyqtSlot(str)
def on_comboBoxSend_textActivated(self, text):
"""
Slot documentation goes here.
@param index DESCRIPTION
@type int
"""
print('on_comboBoxSend_textActivated:', text)
if len(text):
self.plainTextEdit.setPlainText(text)
@pyqtSlot()
def on_radioButtonRecvASCII_pressed(self):
"""
Slot documentation goes here.
"""
self.settings.setValue('RecvFormat', 1)
@pyqtSlot()
def on_radioButtonRecvHex_pressed(self):
"""
Slot documentation goes here.
"""
self.settings.setValue('RecvFormat', 0)
@pyqtSlot()
def on_radioButtonSendASCII_pressed(self):
"""
Slot documentation goes here.
"""
self.settings.setValue('SendFormat', 1)
@pyqtSlot()
def on_radioButtonSendHex_pressed(self):
"""
Slot documentation goes here.
"""
self.settings.setValue('SendFormat', 0)
@pyqtSlot(bool)
def on_actionAutoConnect_toggled(self, p0):
"""
Slot documentation goes here.
@param p0 DESCRIPTION
@type bool
"""
if p0:
result = False
port = None
if self.AutoConnectPort:
port, result = AutoConnect.getAutoConnectPort(self.AutoConnectPort)
else:
port, result = AutoConnect.getAutoConnectPort(self.port)
if result:
self.port = port
self.AutoConnectPort = self.port