mmcb-rs232-avt 1.0.19__py3-none-any.whl → 1.1.37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mmcb_rs232_avt-1.1.37.dist-info/METADATA +210 -0
- mmcb_rs232_avt-1.1.37.dist-info/RECORD +26 -0
- {mmcb_rs232_avt-1.0.19.dist-info → mmcb_rs232_avt-1.1.37.dist-info}/WHEEL +1 -1
- mmcb_rs232_avt-1.1.37.dist-info/entry_points.txt +12 -0
- mmcb_rs232_avt-1.1.37.dist-info/top_level.txt +1 -0
- mmcbrs232/MDP.py +35 -0
- mmcbrs232/broker_and_workers.py +582 -0
- {mmcb_rs232 → mmcbrs232}/common.py +57 -35
- {mmcb_rs232 → mmcbrs232}/detect.py +459 -137
- mmcbrs232/dmm.py +220 -0
- {mmcb_rs232 → mmcbrs232}/dmm_interface.py +25 -4
- {mmcb_rs232 → mmcbrs232}/iv.py +36 -10
- {mmcb_rs232 → mmcbrs232}/lexicon.py +12 -0
- mmcbrs232/liveplot.py +335 -0
- mmcbrs232/mdbroker.py +311 -0
- mmcbrs232/mdcliapi.py +110 -0
- mmcbrs232/mdclientlib.py +266 -0
- mmcbrs232/mdwrkapi.py +183 -0
- {mmcb_rs232 → mmcbrs232}/psuset.py +96 -64
- {mmcb_rs232 → mmcbrs232}/psustat.py +197 -16
- {mmcb_rs232 → mmcbrs232}/psuwatch.py +13 -8
- {mmcb_rs232 → mmcbrs232}/sequence.py +2 -3
- {mmcb_rs232 → mmcbrs232}/ult80.py +123 -79
- mmcbrs232/zhelpers.py +58 -0
- mmcbrs232/zpsustat.py +72 -0
- mmcb_rs232/dmm.py +0 -126
- mmcb_rs232_avt-1.0.19.dist-info/METADATA +0 -62
- mmcb_rs232_avt-1.0.19.dist-info/RECORD +0 -17
- mmcb_rs232_avt-1.0.19.dist-info/entry_points.txt +0 -10
- mmcb_rs232_avt-1.0.19.dist-info/top_level.txt +0 -1
- {mmcb_rs232 → mmcbrs232}/__init__.py +0 -0
|
@@ -19,6 +19,7 @@ expected.
|
|
|
19
19
|
import argparse
|
|
20
20
|
import collections
|
|
21
21
|
import concurrent.futures as cf
|
|
22
|
+
import contextlib
|
|
22
23
|
import functools
|
|
23
24
|
import itertools
|
|
24
25
|
import json
|
|
@@ -28,16 +29,97 @@ import time
|
|
|
28
29
|
import serial
|
|
29
30
|
import serial.tools.list_ports as stlp
|
|
30
31
|
|
|
31
|
-
from
|
|
32
|
-
from
|
|
32
|
+
from mmcbrs232 import common
|
|
33
|
+
from mmcbrs232 import lexicon
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
##############################################################################
|
|
37
|
+
# data structures
|
|
38
|
+
##############################################################################
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Config:
|
|
42
|
+
"""
|
|
43
|
+
Container for USB-RS232 adaptor S.No. and equipment device type.
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, equipment_ident):
|
|
46
|
+
self.usb_rs232_serno, self.device_type = equipment_ident
|
|
47
|
+
|
|
48
|
+
def __repr__(self):
|
|
49
|
+
return (
|
|
50
|
+
f'Config(["{self.usb_rs232_serno}","{self.device_type}"])'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def __str__(self):
|
|
54
|
+
return (
|
|
55
|
+
f'usb_rs232_serno={self.usb_rs232_serno}, '
|
|
56
|
+
f'device_type={self.device_type}'
|
|
57
|
+
)
|
|
33
58
|
|
|
34
59
|
|
|
35
60
|
##############################################################################
|
|
36
61
|
# constants
|
|
37
62
|
##############################################################################
|
|
38
63
|
|
|
64
|
+
|
|
39
65
|
DEBUG = False
|
|
40
66
|
|
|
67
|
+
|
|
68
|
+
##############################################################################
|
|
69
|
+
# file i/o
|
|
70
|
+
##############################################################################
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_valid_lines(filepath):
|
|
74
|
+
"""
|
|
75
|
+
Read configuration file.
|
|
76
|
+
|
|
77
|
+
-----------------------------------------------------------------------
|
|
78
|
+
args
|
|
79
|
+
filepath : string
|
|
80
|
+
-----------------------------------------------------------------------
|
|
81
|
+
yields : str, str
|
|
82
|
+
e.g. ('AG0K7YCQ', 'dmm_6500')
|
|
83
|
+
-----------------------------------------------------------------------
|
|
84
|
+
"""
|
|
85
|
+
with open(filepath, 'r', encoding='utf-8') as infile:
|
|
86
|
+
for line in infile:
|
|
87
|
+
no_comment = line.partition('#')[0]
|
|
88
|
+
try:
|
|
89
|
+
usb_rs232_serno, device_type = (
|
|
90
|
+
p.strip() for p in no_comment.split(',')
|
|
91
|
+
)
|
|
92
|
+
except ValueError:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
yield usb_rs232_serno, device_type
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def read_config_file(filepath):
|
|
99
|
+
"""
|
|
100
|
+
Read configuration file and store contents.
|
|
101
|
+
|
|
102
|
+
-----------------------------------------------------------------------
|
|
103
|
+
args
|
|
104
|
+
filepath : string
|
|
105
|
+
-----------------------------------------------------------------------
|
|
106
|
+
returns : dict
|
|
107
|
+
e.g. {
|
|
108
|
+
'AG0K7YCQ': Config(["AG0K7YCQ","dmm_6500"],
|
|
109
|
+
...
|
|
110
|
+
'AB0MLB8N': Config(["AB0MLB8N","lvpsu_hmp4040"],
|
|
111
|
+
}
|
|
112
|
+
The next processing step will substitute the dict keys for their
|
|
113
|
+
allocated serial port name, hence the duplication of the
|
|
114
|
+
USB-RS232 adaptor serial numbers.
|
|
115
|
+
-----------------------------------------------------------------------
|
|
116
|
+
"""
|
|
117
|
+
return {
|
|
118
|
+
pcfg[0]: Config(pcfg)
|
|
119
|
+
for pcfg in get_valid_lines(filepath)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
41
123
|
##############################################################################
|
|
42
124
|
# command line option handler
|
|
43
125
|
##############################################################################
|
|
@@ -66,17 +148,57 @@ def check_arguments():
|
|
|
66
148
|
by other scripts in this suite.'
|
|
67
149
|
)
|
|
68
150
|
parser.add_argument(
|
|
69
|
-
'
|
|
151
|
+
'config', nargs='?', metavar='config',
|
|
152
|
+
help=(
|
|
153
|
+
'Full path to configuration file that indicates which USB-RS232\
|
|
154
|
+
adaptor is associated with which piece of equipment. The file\
|
|
155
|
+
format is CSV with limited support for comments, where any text\
|
|
156
|
+
on a line after a # symbol is ignored.\
|
|
157
|
+
CSV values should be in pairs (FTDI USB-RS232 adaptor serial number,\
|
|
158
|
+
equipment type) e.g. AB0NOLFS, lvpsu_hmp4040. Available\
|
|
159
|
+
equipment types are:\
|
|
160
|
+
dmm_6500 (Keithley DMM 6500); lvpsu (Agilent E3647A, E3634A);\
|
|
161
|
+
lvpsu_hmp4040 (Hameg / Rohde & Schwarz HMP4040);\
|
|
162
|
+
hvpsu (Keithley 2410, ISEG SHQ 222M & 224M);\
|
|
163
|
+
hvpsu_2614b (Keithley 2614b);\
|
|
164
|
+
controller (Newport ESP300 & MM4006);\
|
|
165
|
+
controller_smc (Newport SMC100);\
|
|
166
|
+
ult80 (Thermo Neslab ULT80).\
|
|
167
|
+
If a path to a file is present then only a single test\
|
|
168
|
+
for the given equipment type will be attempted for each specified\
|
|
169
|
+
serial port (this should be the preferred option). Auto-detection\
|
|
170
|
+
will be attempted if no file is given (this process may be\
|
|
171
|
+
unreliable).'
|
|
172
|
+
),
|
|
173
|
+
type=common.check_file_exists, default=None)
|
|
174
|
+
parser.add_argument(
|
|
175
|
+
'-i', '--info',
|
|
176
|
+
action='store_true',
|
|
177
|
+
help='Display a list of machine-readable serial numbers for\
|
|
178
|
+
USB-RS232 adaptors. This option provides information for use\
|
|
179
|
+
with --config, and exits the script afterwards regardless of other\
|
|
180
|
+
options given',
|
|
181
|
+
)
|
|
182
|
+
parser.add_argument(
|
|
183
|
+
'-n', '--nocache',
|
|
70
184
|
action='store_true',
|
|
71
|
-
help='do not create cache file
|
|
185
|
+
help='do not create cache file after detecting devices.',
|
|
72
186
|
)
|
|
73
187
|
parser.add_argument(
|
|
74
|
-
'--delay_dmm6500',
|
|
188
|
+
'-d', '--delay_dmm6500',
|
|
75
189
|
action='store_true',
|
|
76
|
-
help='
|
|
190
|
+
help='2614b devices may not be detected if tested after DMM 6500;\
|
|
191
|
+
This option changes the order of tests to avoid this issue.\
|
|
192
|
+
This option only applies to auto-detection, and will be ignored\
|
|
193
|
+
if a config file is specified.'
|
|
77
194
|
)
|
|
78
195
|
|
|
79
|
-
|
|
196
|
+
args = parser.parse_args()
|
|
197
|
+
|
|
198
|
+
if args.config and args.delay_dmm6500:
|
|
199
|
+
print('--delay_dmm6500 only applies to automatic detection')
|
|
200
|
+
|
|
201
|
+
return args
|
|
80
202
|
|
|
81
203
|
|
|
82
204
|
##############################################################################
|
|
@@ -84,6 +206,63 @@ def check_arguments():
|
|
|
84
206
|
##############################################################################
|
|
85
207
|
|
|
86
208
|
|
|
209
|
+
def find_usb_rs232_adaptors():
|
|
210
|
+
"""
|
|
211
|
+
Display machine-readable names for connected USB-RS232 adaptors.
|
|
212
|
+
|
|
213
|
+
Users may need this information to create the configuration file for use
|
|
214
|
+
with the --config option.
|
|
215
|
+
|
|
216
|
+
--------------------------------------------------------------------------
|
|
217
|
+
args : none
|
|
218
|
+
--------------------------------------------------------------------------
|
|
219
|
+
returns : none
|
|
220
|
+
--------------------------------------------------------------------------
|
|
221
|
+
"""
|
|
222
|
+
port_mapping = {
|
|
223
|
+
com.serial_number: com.device
|
|
224
|
+
for com in stlp.comports()
|
|
225
|
+
if common.rs232_port_is_valid(com)
|
|
226
|
+
}
|
|
227
|
+
if port_mapping:
|
|
228
|
+
for serno, port in port_mapping.items():
|
|
229
|
+
print(f'USB-RS232 adaptor S.No. {serno} on port {port}')
|
|
230
|
+
else:
|
|
231
|
+
print('No FTDI USB-RS232 adaptors found')
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@contextlib.contextmanager
|
|
235
|
+
def managed_serial_port(port, config):
|
|
236
|
+
"""
|
|
237
|
+
A context manager for serial port handling.
|
|
238
|
+
|
|
239
|
+
--------------------------------------------------------------------------
|
|
240
|
+
args
|
|
241
|
+
port : string
|
|
242
|
+
port name
|
|
243
|
+
config : dict
|
|
244
|
+
serial port configuration
|
|
245
|
+
--------------------------------------------------------------------------
|
|
246
|
+
yields
|
|
247
|
+
ser : serial.Serial or None
|
|
248
|
+
--------------------------------------------------------------------------
|
|
249
|
+
"""
|
|
250
|
+
ser = serial.Serial()
|
|
251
|
+
ser.apply_settings(config)
|
|
252
|
+
ser.port = port
|
|
253
|
+
try:
|
|
254
|
+
ser.open()
|
|
255
|
+
ser.reset_input_buffer()
|
|
256
|
+
ser.reset_output_buffer()
|
|
257
|
+
yield ser
|
|
258
|
+
except (OSError, serial.SerialException):
|
|
259
|
+
print(f'could not open port {port}, skipping.')
|
|
260
|
+
yield None
|
|
261
|
+
finally:
|
|
262
|
+
if ser.is_open:
|
|
263
|
+
ser.close()
|
|
264
|
+
|
|
265
|
+
|
|
87
266
|
def find_controller(port, config):
|
|
88
267
|
"""
|
|
89
268
|
Check if an ESP300 or mm4006 is present on the given serial port.
|
|
@@ -130,24 +309,14 @@ def find_controller(port, config):
|
|
|
130
309
|
--------------------------------------------------------------------------
|
|
131
310
|
"""
|
|
132
311
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
133
|
-
ser = serial.Serial()
|
|
134
|
-
ser.apply_settings(config)
|
|
135
|
-
ser.port = port
|
|
136
312
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
except (OSError, serial.SerialException):
|
|
141
|
-
sys.exit(f'could not open port {port}, exiting.')
|
|
142
|
-
else:
|
|
143
|
-
ser.reset_output_buffer()
|
|
144
|
-
ser.reset_input_buffer()
|
|
313
|
+
with managed_serial_port(port, config) as ser:
|
|
314
|
+
if ser is None:
|
|
315
|
+
return retval
|
|
145
316
|
|
|
146
317
|
# manually empty ESP30X's 10 item error FIFO
|
|
147
318
|
# the mm4006 - like the smc100 - only holds the last error
|
|
148
319
|
#
|
|
149
|
-
# FIXME
|
|
150
|
-
#
|
|
151
320
|
# The commands differ: TE? (esp300), TE (mm4006).
|
|
152
321
|
# This does not seem to cause problems with the mm4006, however check
|
|
153
322
|
# how the mm4006 handles this. The esp300 needs to have the FIFO
|
|
@@ -170,7 +339,6 @@ def find_controller(port, config):
|
|
|
170
339
|
ser.readline()
|
|
171
340
|
except serial.SerialException:
|
|
172
341
|
print(f'cannot access serial port {port}')
|
|
173
|
-
ser.close()
|
|
174
342
|
return retval
|
|
175
343
|
|
|
176
344
|
# command string is identical for esp300 and mm4006
|
|
@@ -179,8 +347,8 @@ def find_controller(port, config):
|
|
|
179
347
|
# get response
|
|
180
348
|
try:
|
|
181
349
|
controller = ser.readline().decode('utf-8').strip().lower()
|
|
182
|
-
except UnicodeDecodeError:
|
|
183
|
-
|
|
350
|
+
except (UnicodeDecodeError, serial.SerialException):
|
|
351
|
+
pass
|
|
184
352
|
else:
|
|
185
353
|
channels = ['']
|
|
186
354
|
release_delay = None
|
|
@@ -225,9 +393,6 @@ def find_controller(port, config):
|
|
|
225
393
|
),
|
|
226
394
|
)
|
|
227
395
|
|
|
228
|
-
# no device detected
|
|
229
|
-
ser.close()
|
|
230
|
-
|
|
231
396
|
return retval
|
|
232
397
|
|
|
233
398
|
|
|
@@ -268,26 +433,17 @@ def find_controller_smc(port, config):
|
|
|
268
433
|
--------------------------------------------------------------------------
|
|
269
434
|
"""
|
|
270
435
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
271
|
-
ser = serial.Serial()
|
|
272
|
-
ser.apply_settings(config)
|
|
273
|
-
ser.port = port
|
|
274
436
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
except (OSError, serial.SerialException):
|
|
279
|
-
sys.exit(f'could not open port {port}, exiting.')
|
|
280
|
-
else:
|
|
281
|
-
ser.reset_output_buffer()
|
|
282
|
-
ser.reset_input_buffer()
|
|
437
|
+
with managed_serial_port(port, config) as ser:
|
|
438
|
+
if ser is None:
|
|
439
|
+
return retval
|
|
283
440
|
|
|
284
441
|
# the SMC100 retains a record of the last error only
|
|
285
442
|
ser.write(lexicon.motion('smc', 'read error', identifier='1'))
|
|
286
443
|
try:
|
|
287
444
|
ser.readline()
|
|
288
|
-
except serial.SerialException:
|
|
445
|
+
except (serial.SerialException, AttributeError):
|
|
289
446
|
print(f'cannot access serial port {port}')
|
|
290
|
-
ser.close()
|
|
291
447
|
return retval
|
|
292
448
|
|
|
293
449
|
ser.write(lexicon.motion('smc', 'read controller version', identifier='1'))
|
|
@@ -295,8 +451,8 @@ def find_controller_smc(port, config):
|
|
|
295
451
|
# get response
|
|
296
452
|
try:
|
|
297
453
|
controller = ser.readline().decode('utf-8').strip().lower()
|
|
298
|
-
except UnicodeDecodeError:
|
|
299
|
-
|
|
454
|
+
except (UnicodeDecodeError, serial.SerialException):
|
|
455
|
+
pass
|
|
300
456
|
else:
|
|
301
457
|
if 'smc' in controller:
|
|
302
458
|
release_delay = None
|
|
@@ -320,9 +476,6 @@ def find_controller_smc(port, config):
|
|
|
320
476
|
),
|
|
321
477
|
)
|
|
322
478
|
|
|
323
|
-
# no device detected
|
|
324
|
-
ser.close()
|
|
325
|
-
|
|
326
479
|
return retval
|
|
327
480
|
|
|
328
481
|
|
|
@@ -386,29 +539,20 @@ def find_lvpsu(port, config):
|
|
|
386
539
|
--------------------------------------------------------------------------
|
|
387
540
|
"""
|
|
388
541
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
389
|
-
ser = serial.Serial()
|
|
390
|
-
ser.apply_settings(config)
|
|
391
|
-
ser.port = port
|
|
392
542
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
except (OSError, serial.SerialException):
|
|
397
|
-
sys.exit(f'could not open port {port}, exiting.')
|
|
398
|
-
else:
|
|
399
|
-
ser.reset_input_buffer()
|
|
400
|
-
ser.reset_output_buffer()
|
|
543
|
+
with managed_serial_port(port, config) as ser:
|
|
544
|
+
if ser is None:
|
|
545
|
+
return retval
|
|
401
546
|
|
|
402
547
|
# ask device to identify itself
|
|
403
548
|
ser.write(lexicon.power('e3647a', 'identify'))
|
|
404
549
|
|
|
405
550
|
# attempt to read back response
|
|
406
551
|
try:
|
|
407
|
-
response = ser.readline()
|
|
552
|
+
response = ser.readline().lower()
|
|
408
553
|
except (serial.SerialException, AttributeError):
|
|
409
554
|
print(f'cannot access serial port {port}')
|
|
410
555
|
else:
|
|
411
|
-
response = response.lower()
|
|
412
556
|
if b'agilent' in response or b'hewlett-packard' in response:
|
|
413
557
|
parts = response.decode('utf-8').split(',')
|
|
414
558
|
manufacturer = 'agilent'
|
|
@@ -441,8 +585,6 @@ def find_lvpsu(port, config):
|
|
|
441
585
|
else:
|
|
442
586
|
print(f'lvpsu: unrecognised model {model}')
|
|
443
587
|
|
|
444
|
-
ser.close()
|
|
445
|
-
|
|
446
588
|
return retval
|
|
447
589
|
|
|
448
590
|
|
|
@@ -498,29 +640,20 @@ def find_lvpsu_hmp4040(port, config):
|
|
|
498
640
|
--------------------------------------------------------------------------
|
|
499
641
|
"""
|
|
500
642
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
501
|
-
ser = serial.Serial()
|
|
502
|
-
ser.apply_settings(config)
|
|
503
|
-
ser.port = port
|
|
504
643
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
except (OSError, serial.SerialException):
|
|
509
|
-
sys.exit(f'could not open port {port}, exiting.')
|
|
510
|
-
else:
|
|
511
|
-
ser.reset_input_buffer()
|
|
512
|
-
ser.reset_output_buffer()
|
|
644
|
+
with managed_serial_port(port, config) as ser:
|
|
645
|
+
if ser is None:
|
|
646
|
+
return retval
|
|
513
647
|
|
|
514
648
|
# ask device to identify itself
|
|
515
649
|
ser.write(lexicon.power('hmp4040', 'identify'))
|
|
516
650
|
|
|
517
651
|
# attempt to read back response
|
|
518
652
|
try:
|
|
519
|
-
response = ser.readline()
|
|
653
|
+
response = ser.readline().lower()
|
|
520
654
|
except (serial.SerialException, AttributeError):
|
|
521
655
|
print(f'cannot access serial port {port}')
|
|
522
656
|
else:
|
|
523
|
-
response = response.lower()
|
|
524
657
|
if b'hameg' in response or b'rohde' in response:
|
|
525
658
|
release_delay = 0.026
|
|
526
659
|
parts = response.decode('utf-8').split(',')
|
|
@@ -544,8 +677,6 @@ def find_lvpsu_hmp4040(port, config):
|
|
|
544
677
|
),
|
|
545
678
|
)
|
|
546
679
|
|
|
547
|
-
ser.close()
|
|
548
|
-
|
|
549
680
|
return retval
|
|
550
681
|
|
|
551
682
|
|
|
@@ -584,24 +715,18 @@ def find_hvpsu(port, config):
|
|
|
584
715
|
--------------------------------------------------------------------------
|
|
585
716
|
"""
|
|
586
717
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
587
|
-
ser = serial.Serial()
|
|
588
|
-
ser.apply_settings(config)
|
|
589
|
-
ser.port = port
|
|
590
718
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
else:
|
|
597
|
-
ser.reset_input_buffer()
|
|
598
|
-
ser.reset_output_buffer()
|
|
719
|
+
with managed_serial_port(port, config) as ser:
|
|
720
|
+
if ser is None:
|
|
721
|
+
return retval
|
|
722
|
+
|
|
723
|
+
# ask device to identify itself
|
|
599
724
|
ser.write(lexicon.power('2410', 'identify'))
|
|
600
725
|
|
|
601
726
|
# attempt to read back response
|
|
602
727
|
try:
|
|
603
728
|
response = ser.readline().lower()
|
|
604
|
-
except serial.SerialException:
|
|
729
|
+
except (serial.SerialException, AttributeError):
|
|
605
730
|
print(f'cannot access serial port {port}')
|
|
606
731
|
else:
|
|
607
732
|
if b'keithley' in response:
|
|
@@ -670,8 +795,6 @@ def find_hvpsu(port, config):
|
|
|
670
795
|
),
|
|
671
796
|
)
|
|
672
797
|
|
|
673
|
-
ser.close()
|
|
674
|
-
|
|
675
798
|
return retval
|
|
676
799
|
|
|
677
800
|
|
|
@@ -712,24 +835,18 @@ def find_hvpsu_2614b(port, config):
|
|
|
712
835
|
--------------------------------------------------------------------------
|
|
713
836
|
"""
|
|
714
837
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
715
|
-
ser = serial.Serial()
|
|
716
|
-
ser.apply_settings(config)
|
|
717
|
-
ser.port = port
|
|
718
838
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
else:
|
|
725
|
-
ser.reset_input_buffer()
|
|
726
|
-
ser.reset_output_buffer()
|
|
839
|
+
with managed_serial_port(port, config) as ser:
|
|
840
|
+
if ser is None:
|
|
841
|
+
return retval
|
|
842
|
+
|
|
843
|
+
# ask device to identify itself
|
|
727
844
|
ser.write(lexicon.power('2614b', 'identify'))
|
|
728
845
|
|
|
729
846
|
# attempt to read back response
|
|
730
847
|
try:
|
|
731
848
|
response = ser.readline().lower()
|
|
732
|
-
except serial.SerialException:
|
|
849
|
+
except (serial.SerialException, AttributeError):
|
|
733
850
|
print(f'cannot access serial port {port}')
|
|
734
851
|
else:
|
|
735
852
|
if b'keithley' in response:
|
|
@@ -758,8 +875,6 @@ def find_hvpsu_2614b(port, config):
|
|
|
758
875
|
except IndexError:
|
|
759
876
|
pass
|
|
760
877
|
|
|
761
|
-
ser.close()
|
|
762
|
-
|
|
763
878
|
return retval
|
|
764
879
|
|
|
765
880
|
|
|
@@ -791,18 +906,12 @@ def find_dmm_6500(port, config):
|
|
|
791
906
|
--------------------------------------------------------------------------
|
|
792
907
|
"""
|
|
793
908
|
retval = (None, None, (port, None, None, None, None, None, None))
|
|
794
|
-
ser = serial.Serial()
|
|
795
|
-
ser.apply_settings(config)
|
|
796
|
-
ser.port = port
|
|
797
909
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
else:
|
|
804
|
-
ser.reset_input_buffer()
|
|
805
|
-
ser.reset_output_buffer()
|
|
910
|
+
with managed_serial_port(port, config) as ser:
|
|
911
|
+
if ser is None:
|
|
912
|
+
return retval
|
|
913
|
+
|
|
914
|
+
# ask device to identify itself
|
|
806
915
|
ser.write(lexicon.instrument('dmm6500', 'identify'))
|
|
807
916
|
|
|
808
917
|
# attempt to read back response
|
|
@@ -815,7 +924,7 @@ def find_dmm_6500(port, config):
|
|
|
815
924
|
try:
|
|
816
925
|
release_delay = 0.01
|
|
817
926
|
# ['KEITHLEY INSTRUMENTS', 'MODEL DMM6500', '04592428', '1.7.12b']
|
|
818
|
-
parts = response.decode('utf-8').
|
|
927
|
+
parts = response.decode('utf-8').split(',')
|
|
819
928
|
manufacturer = parts[0].split()[0]
|
|
820
929
|
model = parts[1].split()[-1]
|
|
821
930
|
serial_number = parts[2]
|
|
@@ -838,7 +947,61 @@ def find_dmm_6500(port, config):
|
|
|
838
947
|
except IndexError:
|
|
839
948
|
pass
|
|
840
949
|
|
|
841
|
-
|
|
950
|
+
return retval
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
def find_ult80(port, config):
|
|
954
|
+
"""
|
|
955
|
+
"Find ULT80", this does not have a machine-readable ID, so just return
|
|
956
|
+
an empty result.
|
|
957
|
+
|
|
958
|
+
--------------------------------------------------------------------------
|
|
959
|
+
args
|
|
960
|
+
port : string
|
|
961
|
+
port name
|
|
962
|
+
config : dict
|
|
963
|
+
serial port configuration
|
|
964
|
+
--------------------------------------------------------------------------
|
|
965
|
+
returns
|
|
966
|
+
retval : tuple
|
|
967
|
+
(category : string,
|
|
968
|
+
settings : dict,
|
|
969
|
+
(port : string,
|
|
970
|
+
manufacturer : string,
|
|
971
|
+
model : string,
|
|
972
|
+
serial_number : string,
|
|
973
|
+
detected : bool,
|
|
974
|
+
channels : list,
|
|
975
|
+
release_delay : float))
|
|
976
|
+
--------------------------------------------------------------------------
|
|
977
|
+
"""
|
|
978
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
979
|
+
|
|
980
|
+
with managed_serial_port(port, config) as ser:
|
|
981
|
+
if ser is None:
|
|
982
|
+
return retval
|
|
983
|
+
|
|
984
|
+
# arbitrary value...
|
|
985
|
+
release_delay = 0.01
|
|
986
|
+
manufacturer = 'thermoneslab'
|
|
987
|
+
model = 'ult80'
|
|
988
|
+
serial_number = None
|
|
989
|
+
detected = True
|
|
990
|
+
channels = ['']
|
|
991
|
+
settings = ser.get_settings()
|
|
992
|
+
retval = (
|
|
993
|
+
'chiller',
|
|
994
|
+
settings,
|
|
995
|
+
(
|
|
996
|
+
port,
|
|
997
|
+
manufacturer,
|
|
998
|
+
model,
|
|
999
|
+
serial_number,
|
|
1000
|
+
detected,
|
|
1001
|
+
channels,
|
|
1002
|
+
release_delay,
|
|
1003
|
+
),
|
|
1004
|
+
)
|
|
842
1005
|
|
|
843
1006
|
return retval
|
|
844
1007
|
|
|
@@ -888,6 +1051,36 @@ def detect_device_on_port(port, tests, configs):
|
|
|
888
1051
|
return retval
|
|
889
1052
|
|
|
890
1053
|
|
|
1054
|
+
def detect_device_type_on_port(port, test_func, config):
|
|
1055
|
+
"""
|
|
1056
|
+
try all tests on the given port in a bid to identify which category of
|
|
1057
|
+
device is present
|
|
1058
|
+
|
|
1059
|
+
--------------------------------------------------------------------------
|
|
1060
|
+
args
|
|
1061
|
+
port : string
|
|
1062
|
+
port name
|
|
1063
|
+
test_func : dict
|
|
1064
|
+
contains the function to call for each category of device
|
|
1065
|
+
config : dict of dicts
|
|
1066
|
+
{device_type: serial_port_configuration, ...}
|
|
1067
|
+
--------------------------------------------------------------------------
|
|
1068
|
+
returns
|
|
1069
|
+
retval : tuple
|
|
1070
|
+
(category : string,
|
|
1071
|
+
settings : dict,
|
|
1072
|
+
(port : string,
|
|
1073
|
+
manufacturer : string,
|
|
1074
|
+
model : string,
|
|
1075
|
+
serial_number : string,
|
|
1076
|
+
detected : bool,
|
|
1077
|
+
channels : list,
|
|
1078
|
+
release_delay : float))
|
|
1079
|
+
--------------------------------------------------------------------------
|
|
1080
|
+
"""
|
|
1081
|
+
return test_func(port, config)
|
|
1082
|
+
|
|
1083
|
+
|
|
891
1084
|
##############################################################################
|
|
892
1085
|
# format detail for detected device
|
|
893
1086
|
##############################################################################
|
|
@@ -923,17 +1116,106 @@ def display_found(device_type, value, padding):
|
|
|
923
1116
|
|
|
924
1117
|
|
|
925
1118
|
##############################################################################
|
|
926
|
-
#
|
|
1119
|
+
# autodetect or user specified
|
|
927
1120
|
##############################################################################
|
|
928
1121
|
|
|
929
1122
|
|
|
930
|
-
def
|
|
1123
|
+
def user_specified(args, configs, found):
|
|
931
1124
|
"""
|
|
932
|
-
|
|
933
|
-
|
|
1125
|
+
Detect devices present on the serial ports, that are attached via FTDI USB
|
|
1126
|
+
to RS232 adaptors. This method uses a user-supplied config file that
|
|
1127
|
+
gives the relationship between USB-RS232 adaptors (specified by their
|
|
1128
|
+
machine-readable serial numbers). This function obtains the relationship
|
|
1129
|
+
between the USB-RS232 adaptors and port names. This provides a mapping
|
|
1130
|
+
between port name and the type of equipment present on the port, which
|
|
1131
|
+
enables just a single test to be run for each serial port.
|
|
1132
|
+
|
|
1133
|
+
--------------------------------------------------------------------------
|
|
1134
|
+
args
|
|
1135
|
+
args : <class 'argparse.Namespace'>
|
|
1136
|
+
configs : dict
|
|
1137
|
+
found : dict
|
|
1138
|
+
--------------------------------------------------------------------------
|
|
1139
|
+
returns
|
|
1140
|
+
found : dict
|
|
1141
|
+
--------------------------------------------------------------------------
|
|
934
1142
|
"""
|
|
935
|
-
|
|
1143
|
+
# usb_rs232_serno, make, model, serno
|
|
1144
|
+
# {pcfg[0]: Config(pcfg)}
|
|
1145
|
+
cfg = read_config_file(args.config)
|
|
1146
|
+
|
|
1147
|
+
# get port to USB-RS232 mapping : {'AG0K7YCQ': '/dev/ttyUSB0', ...}
|
|
1148
|
+
port_mapping = {
|
|
1149
|
+
com.serial_number: com.device
|
|
1150
|
+
for com in stlp.comports()
|
|
1151
|
+
if common.rs232_port_is_valid(com)
|
|
1152
|
+
}
|
|
1153
|
+
if not port_mapping:
|
|
1154
|
+
all_ports = ', '.join(com.device for com in stlp.comports())
|
|
1155
|
+
sys.exit(f'No usable serial ports found: {all_ports}')
|
|
1156
|
+
|
|
1157
|
+
# sort by USB-RS232 adaptor serial number
|
|
1158
|
+
port_mapping = dict(
|
|
1159
|
+
sorted(port_mapping.items(), key=lambda x: x[1])
|
|
1160
|
+
)
|
|
1161
|
+
|
|
1162
|
+
model_to_detect_func = {
|
|
1163
|
+
'dmm_6500': find_dmm_6500,
|
|
1164
|
+
'lvpsu': find_lvpsu,
|
|
1165
|
+
'lvpsu_hmp4040': find_lvpsu_hmp4040,
|
|
1166
|
+
'hvpsu': find_hvpsu,
|
|
1167
|
+
'hvpsu_2614b': find_hvpsu_2614b,
|
|
1168
|
+
'controller': find_controller,
|
|
1169
|
+
'controller_smc': find_controller_smc,
|
|
1170
|
+
'ult80': find_ult80,
|
|
1171
|
+
}
|
|
936
1172
|
|
|
1173
|
+
# {serial_port_name: (test_func, serial_port_config), ...}
|
|
1174
|
+
tasks = {
|
|
1175
|
+
port_mapping[serial_number]: (
|
|
1176
|
+
model_to_detect_func[config_details.device_type],
|
|
1177
|
+
configs[config_details.device_type],
|
|
1178
|
+
)
|
|
1179
|
+
for serial_number, config_details in cfg.items()
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
padding = max(map(len, model_to_detect_func))
|
|
1183
|
+
|
|
1184
|
+
# detect devices on ports
|
|
1185
|
+
_ddtop_pf = functools.partial(detect_device_type_on_port)
|
|
1186
|
+
with cf.ThreadPoolExecutor() as executor:
|
|
1187
|
+
detected = (
|
|
1188
|
+
executor.submit(_ddtop_pf, port_name, tfun, tcfg)
|
|
1189
|
+
for port_name, (tfun, tcfg) in tasks.items()
|
|
1190
|
+
)
|
|
1191
|
+
for future in cf.as_completed(detected):
|
|
1192
|
+
key, settings, value = future.result()
|
|
1193
|
+
if key not in found:
|
|
1194
|
+
found[key] = (settings, [value])
|
|
1195
|
+
else:
|
|
1196
|
+
found[key][1].append(value)
|
|
1197
|
+
|
|
1198
|
+
display_found(key, value, padding)
|
|
1199
|
+
|
|
1200
|
+
return found
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
def auto_detect(args, configs, found):
|
|
1204
|
+
"""
|
|
1205
|
+
Detect devices attached to serial ports.
|
|
1206
|
+
|
|
1207
|
+
Run all tests for known equipment on all serial ports.
|
|
1208
|
+
|
|
1209
|
+
--------------------------------------------------------------------------
|
|
1210
|
+
args
|
|
1211
|
+
args : <class 'argparse.Namespace'>
|
|
1212
|
+
configs : dict
|
|
1213
|
+
found : dict
|
|
1214
|
+
--------------------------------------------------------------------------
|
|
1215
|
+
returns
|
|
1216
|
+
found : dict
|
|
1217
|
+
--------------------------------------------------------------------------
|
|
1218
|
+
"""
|
|
937
1219
|
ports = [
|
|
938
1220
|
com.device
|
|
939
1221
|
for com in stlp.comports()
|
|
@@ -968,6 +1250,43 @@ def main():
|
|
|
968
1250
|
}
|
|
969
1251
|
)
|
|
970
1252
|
|
|
1253
|
+
padding = max(map(len, tests))
|
|
1254
|
+
|
|
1255
|
+
# store creation platform in cache
|
|
1256
|
+
found = {'platform': sys.platform.lower()}
|
|
1257
|
+
|
|
1258
|
+
# detect devices on ports
|
|
1259
|
+
_ddop_pf = functools.partial(detect_device_on_port, tests=tests, configs=configs)
|
|
1260
|
+
with cf.ThreadPoolExecutor() as executor:
|
|
1261
|
+
detected = (executor.submit(_ddop_pf, port) for port in ports)
|
|
1262
|
+
for future in cf.as_completed(detected):
|
|
1263
|
+
key, settings, value = future.result()
|
|
1264
|
+
if key not in found:
|
|
1265
|
+
found[key] = (settings, [value])
|
|
1266
|
+
else:
|
|
1267
|
+
found[key][1].append(value)
|
|
1268
|
+
|
|
1269
|
+
display_found(key, value, padding)
|
|
1270
|
+
|
|
1271
|
+
return found
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
##############################################################################
|
|
1275
|
+
# main
|
|
1276
|
+
##############################################################################
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def main():
|
|
1280
|
+
"""
|
|
1281
|
+
detect devices present on the serial ports, that are attached via
|
|
1282
|
+
FTDI USB to RS232 adaptors
|
|
1283
|
+
"""
|
|
1284
|
+
args = check_arguments()
|
|
1285
|
+
|
|
1286
|
+
if args.info:
|
|
1287
|
+
find_usb_rs232_adaptors()
|
|
1288
|
+
return
|
|
1289
|
+
|
|
971
1290
|
# define serial port connection settings for each device
|
|
972
1291
|
# see https://pyserial.readthedocs.io/en/latest/pyserial_api.html
|
|
973
1292
|
common_configuration = {
|
|
@@ -1015,6 +1334,11 @@ def main():
|
|
|
1015
1334
|
'xonxoff': False,
|
|
1016
1335
|
'rtscts': True,
|
|
1017
1336
|
},
|
|
1337
|
+
'ult80': {
|
|
1338
|
+
'baudrate': 9600,
|
|
1339
|
+
'xonxoff': False,
|
|
1340
|
+
'rtscts': False,
|
|
1341
|
+
},
|
|
1018
1342
|
}
|
|
1019
1343
|
# create unions of common and device-specific configurations
|
|
1020
1344
|
configs = {
|
|
@@ -1022,30 +1346,28 @@ def main():
|
|
|
1022
1346
|
for device, device_specific_configuration in configs.items()
|
|
1023
1347
|
}
|
|
1024
1348
|
|
|
1025
|
-
padding = max(map(len, tests))
|
|
1026
|
-
|
|
1027
|
-
# store creation platform in cache
|
|
1028
1349
|
found = {'platform': sys.platform.lower()}
|
|
1029
1350
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if key not in found:
|
|
1037
|
-
found[key] = (settings, [value])
|
|
1038
|
-
else:
|
|
1039
|
-
found[key][1].append(value)
|
|
1040
|
-
|
|
1041
|
-
display_found(key, value, padding)
|
|
1351
|
+
if args.config:
|
|
1352
|
+
# config file guided process
|
|
1353
|
+
user_specified(args, configs, found)
|
|
1354
|
+
else:
|
|
1355
|
+
# attempt auto-detection of devices attached to serial ports
|
|
1356
|
+
found = auto_detect(args, configs, found)
|
|
1042
1357
|
|
|
1043
1358
|
# JSON output
|
|
1044
1359
|
if args.nocache:
|
|
1045
1360
|
print(f'\nJSON:\n\n{json.dumps(found)}')
|
|
1046
1361
|
else:
|
|
1047
|
-
|
|
1048
|
-
|
|
1362
|
+
try:
|
|
1363
|
+
common.configcache_write(found, common.DEVICE_CACHE)
|
|
1364
|
+
except PermissionError:
|
|
1365
|
+
print(
|
|
1366
|
+
f'\nERROR Could not write to {common.DEVICE_CACHE}: '
|
|
1367
|
+
'check file permissions'
|
|
1368
|
+
)
|
|
1369
|
+
else:
|
|
1370
|
+
print(f'\nWrote detected configuration to: {common.DEVICE_CACHE}')
|
|
1049
1371
|
|
|
1050
1372
|
|
|
1051
1373
|
##############################################################################
|