mmcb-rs232-avt 1.0.14__py3-none-any.whl → 1.0.18__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/__init__.py +0 -0
- mmcb_rs232/common.py +2605 -0
- mmcb_rs232/detect.py +1053 -0
- mmcb_rs232/dmm.py +126 -0
- mmcb_rs232/dmm_interface.py +162 -0
- mmcb_rs232/iv.py +2868 -0
- mmcb_rs232/lexicon.py +580 -0
- mmcb_rs232/psuset.py +938 -0
- mmcb_rs232/psustat.py +705 -0
- mmcb_rs232/psuwatch.py +540 -0
- mmcb_rs232/sequence.py +483 -0
- mmcb_rs232/ult80.py +500 -0
- {mmcb_rs232_avt-1.0.14.dist-info → mmcb_rs232_avt-1.0.18.dist-info}/METADATA +1 -1
- mmcb_rs232_avt-1.0.18.dist-info/RECORD +17 -0
- mmcb_rs232_avt-1.0.18.dist-info/top_level.txt +1 -0
- mmcb_rs232_avt-1.0.14.dist-info/RECORD +0 -5
- mmcb_rs232_avt-1.0.14.dist-info/top_level.txt +0 -1
- {mmcb_rs232_avt-1.0.14.dist-info → mmcb_rs232_avt-1.0.18.dist-info}/WHEEL +0 -0
- {mmcb_rs232_avt-1.0.14.dist-info → mmcb_rs232_avt-1.0.18.dist-info}/entry_points.txt +0 -0
mmcb_rs232/detect.py
ADDED
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Detect devices present on the serial ports, that are attached via
|
|
4
|
+
FTDI USB to RS232 adaptors. This will write a cache file that can
|
|
5
|
+
be used by the other utility scripts.
|
|
6
|
+
|
|
7
|
+
The script will recognise the following devices, and more than one device of
|
|
8
|
+
each type can be detected:
|
|
9
|
+
|
|
10
|
+
controller : Newport ESP300, MM4006 and SMC100
|
|
11
|
+
lvpsu : Agilent E3647A, E3634A, Hameg (Rohde & Schwarz) HMP4040
|
|
12
|
+
hvpsu : Keithley 2410, 2614b; ISEG SHQ 222M, 224M
|
|
13
|
+
|
|
14
|
+
The terminator should set to be the same on all devices to make sure each
|
|
15
|
+
device can cleanly reject commands intended for other devices. <CRLF> is
|
|
16
|
+
expected.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import collections
|
|
21
|
+
import concurrent.futures as cf
|
|
22
|
+
import functools
|
|
23
|
+
import itertools
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
import time
|
|
27
|
+
|
|
28
|
+
import serial
|
|
29
|
+
import serial.tools.list_ports as stlp
|
|
30
|
+
|
|
31
|
+
from mmcb import common
|
|
32
|
+
from mmcb import lexicon
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
##############################################################################
|
|
36
|
+
# constants
|
|
37
|
+
##############################################################################
|
|
38
|
+
|
|
39
|
+
DEBUG = False
|
|
40
|
+
|
|
41
|
+
##############################################################################
|
|
42
|
+
# command line option handler
|
|
43
|
+
##############################################################################
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check_arguments():
|
|
47
|
+
"""
|
|
48
|
+
handle command line options
|
|
49
|
+
|
|
50
|
+
--------------------------------------------------------------------------
|
|
51
|
+
args : none
|
|
52
|
+
--------------------------------------------------------------------------
|
|
53
|
+
returns
|
|
54
|
+
args : <class 'argparse.Namespace'>
|
|
55
|
+
e.g. Namespace(assume=False, nocache=False)
|
|
56
|
+
--------------------------------------------------------------------------
|
|
57
|
+
"""
|
|
58
|
+
parser = argparse.ArgumentParser(
|
|
59
|
+
description='A diagnostic tool to detect devices attached to a\
|
|
60
|
+
computer\'s RS232 serial ports via FTDI USB to RS232 adaptors.\
|
|
61
|
+
Devices that are recognised are:\
|
|
62
|
+
Newport ESP300, MM4006 and SMC100 stage controllers;\
|
|
63
|
+
Hameg (Rohde & Schwarz) HMP4040, Keithley 2410/2614B,\
|
|
64
|
+
ISEG SHQ 222M/224M and Agilent E3647A/E3634A PSUs.\
|
|
65
|
+
Writes a cache file containing detected devices that can be used\
|
|
66
|
+
by other scripts in this suite.'
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
'--nocache',
|
|
70
|
+
action='store_true',
|
|
71
|
+
help='do not create cache file from list of detected devices',
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
'--delay_dmm6500',
|
|
75
|
+
action='store_true',
|
|
76
|
+
help='Keithley 2614b may not be detected if tested after DMM 6500'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return parser.parse_args()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
##############################################################################
|
|
83
|
+
# detect devices on serial ports
|
|
84
|
+
##############################################################################
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def find_controller(port, config):
|
|
88
|
+
"""
|
|
89
|
+
Check if an ESP300 or mm4006 is present on the given serial port.
|
|
90
|
+
|
|
91
|
+
Note that the device's error-FIFO is manually cleared to ensure further
|
|
92
|
+
communications can be processed successfully.
|
|
93
|
+
|
|
94
|
+
from reference/ESP300.pdf page 3-7 (page 47 of the PDF):
|
|
95
|
+
|
|
96
|
+
"A controller command (or a sequence of commands) has to be terminated with
|
|
97
|
+
a carriage return character. However, responses from the controller are
|
|
98
|
+
always terminated by a carriage return/line feed combination. This setting
|
|
99
|
+
may not be changed."
|
|
100
|
+
|
|
101
|
+
Newport MM4006 responds to "VE" with:
|
|
102
|
+
"ve mm4006 controller version 7.01 date 04-02-2003"
|
|
103
|
+
mm4006 ignores rs232 commands when user is in the config menu.
|
|
104
|
+
|
|
105
|
+
Note that this function is called quite late in the detection process. By
|
|
106
|
+
the time it is called, this script will already have sent any esp300 or
|
|
107
|
+
mm4006 present on the specified serial port multiple commands it would
|
|
108
|
+
find incomprehensible, even if they were sent at a matching baud rate.
|
|
109
|
+
Hence, the need to clear the ESP300's error FIFO before detection can
|
|
110
|
+
commence.
|
|
111
|
+
|
|
112
|
+
--------------------------------------------------------------------------
|
|
113
|
+
args
|
|
114
|
+
port : string
|
|
115
|
+
port name
|
|
116
|
+
config : dict
|
|
117
|
+
serial port configuration
|
|
118
|
+
--------------------------------------------------------------------------
|
|
119
|
+
returns
|
|
120
|
+
retval : tuple
|
|
121
|
+
(category : string,
|
|
122
|
+
settings : dict,
|
|
123
|
+
(port : string,
|
|
124
|
+
manufacturer : string,
|
|
125
|
+
model : string,
|
|
126
|
+
serial_number : string,
|
|
127
|
+
detected : bool,
|
|
128
|
+
channels : list,
|
|
129
|
+
release_delay : float))
|
|
130
|
+
--------------------------------------------------------------------------
|
|
131
|
+
"""
|
|
132
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
133
|
+
ser = serial.Serial()
|
|
134
|
+
ser.apply_settings(config)
|
|
135
|
+
ser.port = port
|
|
136
|
+
|
|
137
|
+
# initialise and open serial port
|
|
138
|
+
try:
|
|
139
|
+
ser.open()
|
|
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()
|
|
145
|
+
|
|
146
|
+
# manually empty ESP30X's 10 item error FIFO
|
|
147
|
+
# the mm4006 - like the smc100 - only holds the last error
|
|
148
|
+
#
|
|
149
|
+
# FIXME
|
|
150
|
+
#
|
|
151
|
+
# The commands differ: TE? (esp300), TE (mm4006).
|
|
152
|
+
# This does not seem to cause problems with the mm4006, however check
|
|
153
|
+
# how the mm4006 handles this. The esp300 needs to have the FIFO
|
|
154
|
+
# cleared; perhaps the mm4006 is more tolerant in its error handling
|
|
155
|
+
# or it allows (undocumented) use of a trailing '?' for queries.
|
|
156
|
+
#
|
|
157
|
+
# Also investigate possibility of early termination to (slightly)
|
|
158
|
+
# reduce detection time:
|
|
159
|
+
#
|
|
160
|
+
# ESP300.pdf, p. 3-139
|
|
161
|
+
# TE? returns '0' if no error
|
|
162
|
+
#
|
|
163
|
+
# MM4K6_701_E3.pdf, p. 3.143
|
|
164
|
+
# TE returns 'TE@' if no error
|
|
165
|
+
esp300_read_error = lexicon.motion('esp300', 'read error')
|
|
166
|
+
esp300_fifo_size = 10
|
|
167
|
+
for _ in itertools.repeat(None, esp300_fifo_size):
|
|
168
|
+
ser.write(esp300_read_error)
|
|
169
|
+
try:
|
|
170
|
+
ser.readline()
|
|
171
|
+
except serial.SerialException:
|
|
172
|
+
print(f'cannot access serial port {port}')
|
|
173
|
+
ser.close()
|
|
174
|
+
return retval
|
|
175
|
+
|
|
176
|
+
# command string is identical for esp300 and mm4006
|
|
177
|
+
ser.write(lexicon.motion('esp300', 'read controller version'))
|
|
178
|
+
|
|
179
|
+
# get response
|
|
180
|
+
try:
|
|
181
|
+
controller = ser.readline().decode('utf-8').strip().lower()
|
|
182
|
+
except UnicodeDecodeError:
|
|
183
|
+
ser.close()
|
|
184
|
+
else:
|
|
185
|
+
channels = ['']
|
|
186
|
+
release_delay = None
|
|
187
|
+
|
|
188
|
+
# we might have an ESP300 or ESP301 controller/driver
|
|
189
|
+
if 'esp30' in controller:
|
|
190
|
+
manufacturer = 'newport'
|
|
191
|
+
model = controller.partition(' ')[0]
|
|
192
|
+
serial_number = ''
|
|
193
|
+
detected = True
|
|
194
|
+
settings = ser.get_settings()
|
|
195
|
+
retval = (
|
|
196
|
+
'controller',
|
|
197
|
+
settings,
|
|
198
|
+
(
|
|
199
|
+
port,
|
|
200
|
+
manufacturer,
|
|
201
|
+
model,
|
|
202
|
+
serial_number,
|
|
203
|
+
detected,
|
|
204
|
+
channels,
|
|
205
|
+
release_delay,
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
elif 'mm4006' in controller:
|
|
209
|
+
manufacturer = 'newport'
|
|
210
|
+
model = controller.split()[1]
|
|
211
|
+
serial_number = ''
|
|
212
|
+
detected = True
|
|
213
|
+
settings = ser.get_settings()
|
|
214
|
+
retval = (
|
|
215
|
+
'controller',
|
|
216
|
+
settings,
|
|
217
|
+
(
|
|
218
|
+
port,
|
|
219
|
+
manufacturer,
|
|
220
|
+
model,
|
|
221
|
+
serial_number,
|
|
222
|
+
detected,
|
|
223
|
+
channels,
|
|
224
|
+
release_delay,
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# no device detected
|
|
229
|
+
ser.close()
|
|
230
|
+
|
|
231
|
+
return retval
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def find_controller_smc(port, config):
|
|
235
|
+
"""
|
|
236
|
+
Check if an SMC100 controller is present on the given serial port.
|
|
237
|
+
|
|
238
|
+
The SMC100 controller has non-configurable RS232 settings so handle it
|
|
239
|
+
separately in this function rather than making find_controller()
|
|
240
|
+
overly long.
|
|
241
|
+
|
|
242
|
+
Note that the device's error-FIFO is manually cleared to ensure further
|
|
243
|
+
communications can be processed successfully.
|
|
244
|
+
|
|
245
|
+
Newport SMC100 responds to 1VE with:
|
|
246
|
+
"1VE SMC_CC - Controller-driver version 3. 1. 2"
|
|
247
|
+
|
|
248
|
+
ASSUME that we only have one SMC controller, and it is at address 1.
|
|
249
|
+
|
|
250
|
+
--------------------------------------------------------------------------
|
|
251
|
+
args
|
|
252
|
+
port : string
|
|
253
|
+
port name
|
|
254
|
+
config : dict
|
|
255
|
+
serial port configuration
|
|
256
|
+
--------------------------------------------------------------------------
|
|
257
|
+
returns
|
|
258
|
+
retval : tuple
|
|
259
|
+
(category : string,
|
|
260
|
+
settings : dict,
|
|
261
|
+
(port : string,
|
|
262
|
+
manufacturer : string,
|
|
263
|
+
model : string,
|
|
264
|
+
serial_number : string,
|
|
265
|
+
detected : bool,
|
|
266
|
+
channels : list,
|
|
267
|
+
release_delay : float))
|
|
268
|
+
--------------------------------------------------------------------------
|
|
269
|
+
"""
|
|
270
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
271
|
+
ser = serial.Serial()
|
|
272
|
+
ser.apply_settings(config)
|
|
273
|
+
ser.port = port
|
|
274
|
+
|
|
275
|
+
# initialise and open serial port
|
|
276
|
+
try:
|
|
277
|
+
ser.open()
|
|
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()
|
|
283
|
+
|
|
284
|
+
# the SMC100 retains a record of the last error only
|
|
285
|
+
ser.write(lexicon.motion('smc', 'read error', identifier='1'))
|
|
286
|
+
try:
|
|
287
|
+
ser.readline()
|
|
288
|
+
except serial.SerialException:
|
|
289
|
+
print(f'cannot access serial port {port}')
|
|
290
|
+
ser.close()
|
|
291
|
+
return retval
|
|
292
|
+
|
|
293
|
+
ser.write(lexicon.motion('smc', 'read controller version', identifier='1'))
|
|
294
|
+
|
|
295
|
+
# get response
|
|
296
|
+
try:
|
|
297
|
+
controller = ser.readline().decode('utf-8').strip().lower()
|
|
298
|
+
except UnicodeDecodeError:
|
|
299
|
+
ser.close()
|
|
300
|
+
else:
|
|
301
|
+
if 'smc' in controller:
|
|
302
|
+
release_delay = None
|
|
303
|
+
channels = ['']
|
|
304
|
+
manufacturer = 'newport'
|
|
305
|
+
model = 'smc'
|
|
306
|
+
serial_number = ''
|
|
307
|
+
detected = True
|
|
308
|
+
settings = ser.get_settings()
|
|
309
|
+
retval = (
|
|
310
|
+
'controller',
|
|
311
|
+
settings,
|
|
312
|
+
(
|
|
313
|
+
port,
|
|
314
|
+
manufacturer,
|
|
315
|
+
model,
|
|
316
|
+
serial_number,
|
|
317
|
+
detected,
|
|
318
|
+
channels,
|
|
319
|
+
release_delay,
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# no device detected
|
|
324
|
+
ser.close()
|
|
325
|
+
|
|
326
|
+
return retval
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def find_lvpsu(port, config):
|
|
330
|
+
"""
|
|
331
|
+
check if any of these devices are present on the given serial port:
|
|
332
|
+
|
|
333
|
+
Keysight/Agilent E3647A (dual output)
|
|
334
|
+
Hewlett-Packard/Agilent E3634A (single output)
|
|
335
|
+
|
|
336
|
+
notes for this type of PSU:
|
|
337
|
+
|
|
338
|
+
a null-modem/crossover adaptor needs to be added to the serial cable
|
|
339
|
+
|
|
340
|
+
test commands using miniterm rather than putty, e.g.
|
|
341
|
+
|
|
342
|
+
[avt@pc048057 dev]$ miniterm.py --port=/dev/ttyUSB1
|
|
343
|
+
--- Miniterm on /dev/ttyUSB1: 9600,8,N,1 ---
|
|
344
|
+
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
|
345
|
+
Agilent Technologies,E3647A,0,1.7-5.0-1.0
|
|
346
|
+
|
|
347
|
+
When using Keysight Connection Expert 2018 on Windows, the Flow control
|
|
348
|
+
setting in Windows Device Manger seems to have no effect. Hardware, None,
|
|
349
|
+
or Xon / Xoff all seem to work.
|
|
350
|
+
|
|
351
|
+
in essence, ignore what the manual says about remote mode and dsrstr:
|
|
352
|
+
|
|
353
|
+
(1) do not put the PSU into remote mode. Keysight Connection Expert
|
|
354
|
+
2018 doesn't do this on Windows, and it seems to cause problems here
|
|
355
|
+
(2) do not use dsrdtr
|
|
356
|
+
|
|
357
|
+
response for Keysight E3634A:
|
|
358
|
+
|
|
359
|
+
[avt@pc048057 utilities]$ miniterm.py --port=/dev/ttyUSB0
|
|
360
|
+
--- Miniterm on /dev/ttyUSB0: 9600,8,N,1 ---
|
|
361
|
+
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
|
362
|
+
HEWLETT-PACKARD,E3634A,0,2.4-6.1-2.1
|
|
363
|
+
|
|
364
|
+
SYST:VERS? yields the SCPI version e.g. 1997.0
|
|
365
|
+
YYYY.V where the Y represents the year of the version,
|
|
366
|
+
and the V represents the version number for that year.
|
|
367
|
+
|
|
368
|
+
--------------------------------------------------------------------------
|
|
369
|
+
args
|
|
370
|
+
port : string
|
|
371
|
+
port name
|
|
372
|
+
config : dict
|
|
373
|
+
serial port configuration
|
|
374
|
+
--------------------------------------------------------------------------
|
|
375
|
+
returns
|
|
376
|
+
retval : tuple
|
|
377
|
+
(category : string,
|
|
378
|
+
settings : dict,
|
|
379
|
+
(port : string,
|
|
380
|
+
manufacturer : string,
|
|
381
|
+
model : string,
|
|
382
|
+
serial_number : string,
|
|
383
|
+
detected : bool,
|
|
384
|
+
channels : list,
|
|
385
|
+
release_delay : float))
|
|
386
|
+
--------------------------------------------------------------------------
|
|
387
|
+
"""
|
|
388
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
389
|
+
ser = serial.Serial()
|
|
390
|
+
ser.apply_settings(config)
|
|
391
|
+
ser.port = port
|
|
392
|
+
|
|
393
|
+
# initialise and open serial port
|
|
394
|
+
try:
|
|
395
|
+
ser.open()
|
|
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()
|
|
401
|
+
|
|
402
|
+
# ask device to identify itself
|
|
403
|
+
ser.write(lexicon.power('e3647a', 'identify'))
|
|
404
|
+
|
|
405
|
+
# attempt to read back response
|
|
406
|
+
try:
|
|
407
|
+
response = ser.readline()
|
|
408
|
+
except (serial.SerialException, AttributeError):
|
|
409
|
+
print(f'cannot access serial port {port}')
|
|
410
|
+
else:
|
|
411
|
+
response = response.lower()
|
|
412
|
+
if b'agilent' in response or b'hewlett-packard' in response:
|
|
413
|
+
parts = response.decode('utf-8').split(',')
|
|
414
|
+
manufacturer = 'agilent'
|
|
415
|
+
model = parts[1].strip()
|
|
416
|
+
serial_number = ''
|
|
417
|
+
detected = True
|
|
418
|
+
settings = ser.get_settings()
|
|
419
|
+
|
|
420
|
+
if model in {'e3647a', 'e3634a'}:
|
|
421
|
+
if model == 'e3647a':
|
|
422
|
+
release_delay = 0.01
|
|
423
|
+
channels = ['1', '2']
|
|
424
|
+
else:
|
|
425
|
+
release_delay = None
|
|
426
|
+
channels = ['']
|
|
427
|
+
|
|
428
|
+
retval = (
|
|
429
|
+
'lvpsu',
|
|
430
|
+
settings,
|
|
431
|
+
(
|
|
432
|
+
port,
|
|
433
|
+
manufacturer,
|
|
434
|
+
model,
|
|
435
|
+
serial_number,
|
|
436
|
+
detected,
|
|
437
|
+
channels,
|
|
438
|
+
release_delay,
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
print(f'lvpsu: unrecognised model {model}')
|
|
443
|
+
|
|
444
|
+
ser.close()
|
|
445
|
+
|
|
446
|
+
return retval
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def find_lvpsu_hmp4040(port, config):
|
|
450
|
+
"""
|
|
451
|
+
check if any of these devices are present on the given serial port:
|
|
452
|
+
|
|
453
|
+
Rohde & Schwarz HMP4040
|
|
454
|
+
|
|
455
|
+
response to *IDN?:
|
|
456
|
+
|
|
457
|
+
macbook:utilities avt$ python3 -m serial.tools.miniterm
|
|
458
|
+
|
|
459
|
+
--- Available ports:
|
|
460
|
+
--- 1: /dev/cu.Bluetooth-Incoming-Port 'n/a'
|
|
461
|
+
--- 2: /dev/cu.SSDC 'n/a'
|
|
462
|
+
--- 3: /dev/cu.usbserial-AH06DY15 'FT232R USB UART'
|
|
463
|
+
--- Enter port index or full name: 3
|
|
464
|
+
--- Miniterm on /dev/cu.usbserial-AH06DY15 9600,8,N,1 ---
|
|
465
|
+
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
|
466
|
+
HAMEG,HMP4040,103781,HW50020001/SW2.51
|
|
467
|
+
|
|
468
|
+
Some models also identify as ROHDE&SCHWARZ instead of HAMEG.
|
|
469
|
+
|
|
470
|
+
(pve) pi@raspberrypi $ python3 -m serial.tools.miniterm
|
|
471
|
+
|
|
472
|
+
--- Available ports:
|
|
473
|
+
--- 1: /dev/ttyAMA0 'ttyAMA0'
|
|
474
|
+
--- 2: /dev/ttyUSB0 'FT232R USB UART - FT232R USB UART'
|
|
475
|
+
--- Enter port index or full name: 2
|
|
476
|
+
--- Miniterm on /dev/ttyUSB0 9600,8,N,1 ---
|
|
477
|
+
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
|
478
|
+
ROHDE&SCHWARZ,HMP4040,120224,HW50020003/SW2.70
|
|
479
|
+
|
|
480
|
+
--------------------------------------------------------------------------
|
|
481
|
+
args
|
|
482
|
+
port : string
|
|
483
|
+
port name
|
|
484
|
+
config : dict
|
|
485
|
+
serial port configuration
|
|
486
|
+
--------------------------------------------------------------------------
|
|
487
|
+
returns
|
|
488
|
+
retval : tuple
|
|
489
|
+
(category : string,
|
|
490
|
+
settings : dict,
|
|
491
|
+
(port : string,
|
|
492
|
+
manufacturer : string,
|
|
493
|
+
model : string,
|
|
494
|
+
serial_number : string,
|
|
495
|
+
detected : bool,
|
|
496
|
+
channels : list,
|
|
497
|
+
release_delay : float))
|
|
498
|
+
--------------------------------------------------------------------------
|
|
499
|
+
"""
|
|
500
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
501
|
+
ser = serial.Serial()
|
|
502
|
+
ser.apply_settings(config)
|
|
503
|
+
ser.port = port
|
|
504
|
+
|
|
505
|
+
# initialise and open serial port
|
|
506
|
+
try:
|
|
507
|
+
ser.open()
|
|
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()
|
|
513
|
+
|
|
514
|
+
# ask device to identify itself
|
|
515
|
+
ser.write(lexicon.power('hmp4040', 'identify'))
|
|
516
|
+
|
|
517
|
+
# attempt to read back response
|
|
518
|
+
try:
|
|
519
|
+
response = ser.readline()
|
|
520
|
+
except (serial.SerialException, AttributeError):
|
|
521
|
+
print(f'cannot access serial port {port}')
|
|
522
|
+
else:
|
|
523
|
+
response = response.lower()
|
|
524
|
+
if b'hameg' in response or b'rohde' in response:
|
|
525
|
+
release_delay = 0.026
|
|
526
|
+
parts = response.decode('utf-8').split(',')
|
|
527
|
+
manufacturer = 'hameg'
|
|
528
|
+
model = parts[1].strip()
|
|
529
|
+
serial_number = parts[2].strip()
|
|
530
|
+
detected = True
|
|
531
|
+
settings = ser.get_settings()
|
|
532
|
+
channels = ['1', '2', '3', '4']
|
|
533
|
+
retval = (
|
|
534
|
+
'lvpsu',
|
|
535
|
+
settings,
|
|
536
|
+
(
|
|
537
|
+
port,
|
|
538
|
+
manufacturer,
|
|
539
|
+
model,
|
|
540
|
+
serial_number,
|
|
541
|
+
detected,
|
|
542
|
+
channels,
|
|
543
|
+
release_delay,
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
ser.close()
|
|
548
|
+
|
|
549
|
+
return retval
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def find_hvpsu(port, config):
|
|
553
|
+
"""
|
|
554
|
+
check if one of the following power supplies is present on the given
|
|
555
|
+
serial port
|
|
556
|
+
|
|
557
|
+
Keithley 2410
|
|
558
|
+
ISEG SHQ 222M
|
|
559
|
+
ISEG SHQ 224M
|
|
560
|
+
|
|
561
|
+
this function also successfully identifies:
|
|
562
|
+
Keithley 6517B electrometer / high resistance meter
|
|
563
|
+
|
|
564
|
+
Keithley 2410 response to *IDN?
|
|
565
|
+
KEITHLEY INSTRUMENTS INC.,MODEL 2410,4343654,C34 Sep 21 2016 15:30:00/A02 /K/M
|
|
566
|
+
--------------------------------------------------------------------------
|
|
567
|
+
args
|
|
568
|
+
port : string
|
|
569
|
+
port name
|
|
570
|
+
config : dict
|
|
571
|
+
serial port configuration
|
|
572
|
+
--------------------------------------------------------------------------
|
|
573
|
+
returns
|
|
574
|
+
retval : tuple
|
|
575
|
+
(category : string,
|
|
576
|
+
settings : dict,
|
|
577
|
+
(port : string,
|
|
578
|
+
manufacturer : string,
|
|
579
|
+
model : string,
|
|
580
|
+
serial_number : string,
|
|
581
|
+
detected : bool,
|
|
582
|
+
channels : list,
|
|
583
|
+
release_delay : float))
|
|
584
|
+
--------------------------------------------------------------------------
|
|
585
|
+
"""
|
|
586
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
587
|
+
ser = serial.Serial()
|
|
588
|
+
ser.apply_settings(config)
|
|
589
|
+
ser.port = port
|
|
590
|
+
|
|
591
|
+
# initialise and open serial port
|
|
592
|
+
try:
|
|
593
|
+
ser.open()
|
|
594
|
+
except (OSError, serial.SerialException):
|
|
595
|
+
sys.exit(f'could not open port {port}, exiting.')
|
|
596
|
+
else:
|
|
597
|
+
ser.reset_input_buffer()
|
|
598
|
+
ser.reset_output_buffer()
|
|
599
|
+
ser.write(lexicon.power('2410', 'identify'))
|
|
600
|
+
|
|
601
|
+
# attempt to read back response
|
|
602
|
+
try:
|
|
603
|
+
response = ser.readline().lower()
|
|
604
|
+
except serial.SerialException:
|
|
605
|
+
print(f'cannot access serial port {port}')
|
|
606
|
+
else:
|
|
607
|
+
if b'keithley' in response:
|
|
608
|
+
release_delay = None
|
|
609
|
+
parts = response.decode('utf-8').split(',')
|
|
610
|
+
manufacturer = parts[0].partition(' ')[0]
|
|
611
|
+
model = parts[1].rpartition(' ')[-1]
|
|
612
|
+
serial_number = parts[2]
|
|
613
|
+
detected = True
|
|
614
|
+
channels = ['']
|
|
615
|
+
settings = ser.get_settings()
|
|
616
|
+
retval = (
|
|
617
|
+
'hvpsu',
|
|
618
|
+
settings,
|
|
619
|
+
(
|
|
620
|
+
port,
|
|
621
|
+
manufacturer,
|
|
622
|
+
model,
|
|
623
|
+
serial_number,
|
|
624
|
+
detected,
|
|
625
|
+
channels,
|
|
626
|
+
release_delay,
|
|
627
|
+
),
|
|
628
|
+
)
|
|
629
|
+
else:
|
|
630
|
+
# check for ISEG SHQ dual-channel power supplies
|
|
631
|
+
|
|
632
|
+
ser.reset_input_buffer()
|
|
633
|
+
ser.reset_output_buffer()
|
|
634
|
+
time.sleep(0.5)
|
|
635
|
+
|
|
636
|
+
# send CRLF first for sync
|
|
637
|
+
ser.write(lexicon.power('shq', 'synchronise'))
|
|
638
|
+
ser.readline() # consume local echo
|
|
639
|
+
ser.readline() # consume local echo
|
|
640
|
+
ser.readline() # consume response to command in buffer
|
|
641
|
+
|
|
642
|
+
# '#' command: read device identifier
|
|
643
|
+
# device identifier does not contain a model number:
|
|
644
|
+
# serial number;software release;Vout max;Iout max
|
|
645
|
+
# e.g. ISEG SHQ 224M returns '484216;3.09;4000V;3mA'
|
|
646
|
+
# e.g. ISEG SHQ 222M returns '484230;3.10;2000V;6mA'
|
|
647
|
+
ser.write(lexicon.power('shq', 'identify'))
|
|
648
|
+
ser.readline() # consume local echo
|
|
649
|
+
response = ser.readline()
|
|
650
|
+
parts = [x.strip() for x in response.decode('utf-8').split(';')]
|
|
651
|
+
if len(parts) == 4:
|
|
652
|
+
release_delay = 0.01
|
|
653
|
+
manufacturer = 'iseg'
|
|
654
|
+
model = 'shq'
|
|
655
|
+
serial_number = parts[0]
|
|
656
|
+
detected = True
|
|
657
|
+
channels = ['1', '2']
|
|
658
|
+
settings = ser.get_settings()
|
|
659
|
+
retval = (
|
|
660
|
+
'hvpsu',
|
|
661
|
+
settings,
|
|
662
|
+
(
|
|
663
|
+
port,
|
|
664
|
+
manufacturer,
|
|
665
|
+
model,
|
|
666
|
+
serial_number,
|
|
667
|
+
detected,
|
|
668
|
+
channels,
|
|
669
|
+
release_delay,
|
|
670
|
+
),
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
ser.close()
|
|
674
|
+
|
|
675
|
+
return retval
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def find_hvpsu_2614b(port, config):
|
|
679
|
+
"""
|
|
680
|
+
check if a Keithley 2614b is present on the given serial port
|
|
681
|
+
|
|
682
|
+
note that the language used for this generation of Keithley is quite
|
|
683
|
+
different from earlier models
|
|
684
|
+
|
|
685
|
+
e.g.
|
|
686
|
+
|
|
687
|
+
d=localnode.description
|
|
688
|
+
print(d)
|
|
689
|
+
|
|
690
|
+
returns:
|
|
691
|
+
|
|
692
|
+
Keithley Instruments SMU 2614B - 4428182
|
|
693
|
+
|
|
694
|
+
--------------------------------------------------------------------------
|
|
695
|
+
args
|
|
696
|
+
port : string
|
|
697
|
+
port name
|
|
698
|
+
config : dict
|
|
699
|
+
serial port configuration
|
|
700
|
+
--------------------------------------------------------------------------
|
|
701
|
+
returns
|
|
702
|
+
retval : tuple
|
|
703
|
+
(category : string,
|
|
704
|
+
settings : dict,
|
|
705
|
+
(port : string,
|
|
706
|
+
manufacturer : string,
|
|
707
|
+
model : string,
|
|
708
|
+
serial_number : string,
|
|
709
|
+
detected : bool,
|
|
710
|
+
channels : list,
|
|
711
|
+
release_delay : float))
|
|
712
|
+
--------------------------------------------------------------------------
|
|
713
|
+
"""
|
|
714
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
715
|
+
ser = serial.Serial()
|
|
716
|
+
ser.apply_settings(config)
|
|
717
|
+
ser.port = port
|
|
718
|
+
|
|
719
|
+
# initialise and open serial port
|
|
720
|
+
try:
|
|
721
|
+
ser.open()
|
|
722
|
+
except (OSError, serial.SerialException):
|
|
723
|
+
sys.exit(f'could not open port {port}, exiting.')
|
|
724
|
+
else:
|
|
725
|
+
ser.reset_input_buffer()
|
|
726
|
+
ser.reset_output_buffer()
|
|
727
|
+
ser.write(lexicon.power('2614b', 'identify'))
|
|
728
|
+
|
|
729
|
+
# attempt to read back response
|
|
730
|
+
try:
|
|
731
|
+
response = ser.readline().lower()
|
|
732
|
+
except serial.SerialException:
|
|
733
|
+
print(f'cannot access serial port {port}')
|
|
734
|
+
else:
|
|
735
|
+
if b'keithley' in response:
|
|
736
|
+
try:
|
|
737
|
+
release_delay = 0.01
|
|
738
|
+
parts = response.decode('utf-8').split()
|
|
739
|
+
manufacturer = parts[0]
|
|
740
|
+
model = parts[3]
|
|
741
|
+
serial_number = parts[5]
|
|
742
|
+
detected = True
|
|
743
|
+
channels = ['a', 'b']
|
|
744
|
+
settings = ser.get_settings()
|
|
745
|
+
retval = (
|
|
746
|
+
'hvpsu',
|
|
747
|
+
settings,
|
|
748
|
+
(
|
|
749
|
+
port,
|
|
750
|
+
manufacturer,
|
|
751
|
+
model,
|
|
752
|
+
serial_number,
|
|
753
|
+
detected,
|
|
754
|
+
channels,
|
|
755
|
+
release_delay,
|
|
756
|
+
),
|
|
757
|
+
)
|
|
758
|
+
except IndexError:
|
|
759
|
+
pass
|
|
760
|
+
|
|
761
|
+
ser.close()
|
|
762
|
+
|
|
763
|
+
return retval
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def find_dmm_6500(port, config):
|
|
767
|
+
"""
|
|
768
|
+
check if a Keithley DMM6500 is present on the given serial port
|
|
769
|
+
|
|
770
|
+
*IDN? returns:
|
|
771
|
+
KEITHLEY INSTRUMENTS,MODEL DMM6500,04592428,1.7.12b
|
|
772
|
+
|
|
773
|
+
--------------------------------------------------------------------------
|
|
774
|
+
args
|
|
775
|
+
port : string
|
|
776
|
+
port name
|
|
777
|
+
config : dict
|
|
778
|
+
serial port configuration
|
|
779
|
+
--------------------------------------------------------------------------
|
|
780
|
+
returns
|
|
781
|
+
retval : tuple
|
|
782
|
+
(category : string,
|
|
783
|
+
settings : dict,
|
|
784
|
+
(port : string,
|
|
785
|
+
manufacturer : string,
|
|
786
|
+
model : string,
|
|
787
|
+
serial_number : string,
|
|
788
|
+
detected : bool,
|
|
789
|
+
channels : list,
|
|
790
|
+
release_delay : float))
|
|
791
|
+
--------------------------------------------------------------------------
|
|
792
|
+
"""
|
|
793
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
794
|
+
ser = serial.Serial()
|
|
795
|
+
ser.apply_settings(config)
|
|
796
|
+
ser.port = port
|
|
797
|
+
|
|
798
|
+
# initialise and open serial port
|
|
799
|
+
try:
|
|
800
|
+
ser.open()
|
|
801
|
+
except (OSError, serial.SerialException):
|
|
802
|
+
sys.exit(f'could not open port {port}, exiting.')
|
|
803
|
+
else:
|
|
804
|
+
ser.reset_input_buffer()
|
|
805
|
+
ser.reset_output_buffer()
|
|
806
|
+
ser.write(lexicon.instrument('dmm6500', 'identify'))
|
|
807
|
+
|
|
808
|
+
# attempt to read back response
|
|
809
|
+
try:
|
|
810
|
+
response = ser.readline().lower()
|
|
811
|
+
except serial.SerialException:
|
|
812
|
+
print(f'cannot access serial port {port}')
|
|
813
|
+
else:
|
|
814
|
+
if b'keithley' in response:
|
|
815
|
+
try:
|
|
816
|
+
release_delay = 0.01
|
|
817
|
+
# ['KEITHLEY INSTRUMENTS', 'MODEL DMM6500', '04592428', '1.7.12b']
|
|
818
|
+
parts = response.decode('utf-8').lower().split(',')
|
|
819
|
+
manufacturer = parts[0].split()[0]
|
|
820
|
+
model = parts[1].split()[-1]
|
|
821
|
+
serial_number = parts[2]
|
|
822
|
+
detected = True
|
|
823
|
+
channels = ['']
|
|
824
|
+
settings = ser.get_settings()
|
|
825
|
+
retval = (
|
|
826
|
+
'instrument',
|
|
827
|
+
settings,
|
|
828
|
+
(
|
|
829
|
+
port,
|
|
830
|
+
manufacturer,
|
|
831
|
+
model,
|
|
832
|
+
serial_number,
|
|
833
|
+
detected,
|
|
834
|
+
channels,
|
|
835
|
+
release_delay,
|
|
836
|
+
),
|
|
837
|
+
)
|
|
838
|
+
except IndexError:
|
|
839
|
+
pass
|
|
840
|
+
|
|
841
|
+
ser.close()
|
|
842
|
+
|
|
843
|
+
return retval
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def detect_device_on_port(port, tests, configs):
|
|
847
|
+
"""
|
|
848
|
+
try all tests on the given port in a bid to identify which category of
|
|
849
|
+
device is present
|
|
850
|
+
|
|
851
|
+
--------------------------------------------------------------------------
|
|
852
|
+
args
|
|
853
|
+
port : string
|
|
854
|
+
port name
|
|
855
|
+
tests : dict
|
|
856
|
+
contains the function to call for each category of device
|
|
857
|
+
configs : dict of dicts
|
|
858
|
+
{device_type: serial_port_configuration, ...}
|
|
859
|
+
--------------------------------------------------------------------------
|
|
860
|
+
returns
|
|
861
|
+
retval : tuple
|
|
862
|
+
(category : string,
|
|
863
|
+
settings : dict,
|
|
864
|
+
(port : string,
|
|
865
|
+
manufacturer : string,
|
|
866
|
+
model : string,
|
|
867
|
+
serial_number : string,
|
|
868
|
+
detected : bool,
|
|
869
|
+
channels : list,
|
|
870
|
+
release_delay : float))
|
|
871
|
+
--------------------------------------------------------------------------
|
|
872
|
+
"""
|
|
873
|
+
retval = (None, None, (port, None, None, None, None, None, None))
|
|
874
|
+
|
|
875
|
+
for test in tests:
|
|
876
|
+
if DEBUG:
|
|
877
|
+
print(f'checking {port} for {test}')
|
|
878
|
+
|
|
879
|
+
retval = tests[test](port, configs[test])
|
|
880
|
+
if retval[0] is not None:
|
|
881
|
+
break
|
|
882
|
+
|
|
883
|
+
# macOS seems to process serial port events more swiftly than CentOS
|
|
884
|
+
# and Windows 7: need to give the devices enough time to respond
|
|
885
|
+
# between requests
|
|
886
|
+
time.sleep(0.3)
|
|
887
|
+
|
|
888
|
+
return retval
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
##############################################################################
|
|
892
|
+
# format detail for detected device
|
|
893
|
+
##############################################################################
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def display_found(device_type, value, padding):
|
|
897
|
+
"""
|
|
898
|
+
print details for a device identified on a serial port
|
|
899
|
+
|
|
900
|
+
--------------------------------------------------------------------------
|
|
901
|
+
args
|
|
902
|
+
device_type : string
|
|
903
|
+
e.g. 'hvpsu'
|
|
904
|
+
value : tuple (string, string, string, string, bool)
|
|
905
|
+
e.g. (port, manufacturer, model, serial_number, detected_flag)
|
|
906
|
+
padding : int
|
|
907
|
+
the length of the longest device type
|
|
908
|
+
--------------------------------------------------------------------------
|
|
909
|
+
returns : none
|
|
910
|
+
--------------------------------------------------------------------------
|
|
911
|
+
"""
|
|
912
|
+
port, manufacturer, model, serial_number, *_ = value
|
|
913
|
+
|
|
914
|
+
if device_type is None:
|
|
915
|
+
print(f'{"None".rjust(padding)} on {port}: (no device identified)')
|
|
916
|
+
else:
|
|
917
|
+
details = [d for d in (manufacturer, model) if d]
|
|
918
|
+
if serial_number:
|
|
919
|
+
details.append(f's.no. {serial_number}')
|
|
920
|
+
|
|
921
|
+
suffix = ' '.join(details)
|
|
922
|
+
print(f'{device_type.rjust(padding)} on {port}: {suffix}')
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
##############################################################################
|
|
926
|
+
# main
|
|
927
|
+
##############################################################################
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def main():
|
|
931
|
+
"""
|
|
932
|
+
detect devices present on the serial ports, that are attached via
|
|
933
|
+
FTDI USB to RS232 adaptors
|
|
934
|
+
"""
|
|
935
|
+
args = check_arguments()
|
|
936
|
+
|
|
937
|
+
ports = [
|
|
938
|
+
com.device
|
|
939
|
+
for com in stlp.comports()
|
|
940
|
+
if common.rs232_port_is_valid(com)
|
|
941
|
+
]
|
|
942
|
+
if not ports:
|
|
943
|
+
all_ports = ', '.join(com.device for com in stlp.comports())
|
|
944
|
+
sys.exit(f'No usable serial ports found: {all_ports}')
|
|
945
|
+
|
|
946
|
+
if args.delay_dmm6500:
|
|
947
|
+
tests = collections.OrderedDict(
|
|
948
|
+
{
|
|
949
|
+
'lvpsu': find_lvpsu,
|
|
950
|
+
'lvpsu_hmp4040': find_lvpsu_hmp4040,
|
|
951
|
+
'hvpsu': find_hvpsu,
|
|
952
|
+
'hvpsu_2614b': find_hvpsu_2614b,
|
|
953
|
+
'dmm_6500': find_dmm_6500,
|
|
954
|
+
'controller': find_controller,
|
|
955
|
+
'controller_smc': find_controller_smc,
|
|
956
|
+
}
|
|
957
|
+
)
|
|
958
|
+
else:
|
|
959
|
+
tests = collections.OrderedDict(
|
|
960
|
+
{
|
|
961
|
+
'dmm_6500': find_dmm_6500,
|
|
962
|
+
'lvpsu': find_lvpsu,
|
|
963
|
+
'lvpsu_hmp4040': find_lvpsu_hmp4040,
|
|
964
|
+
'hvpsu': find_hvpsu,
|
|
965
|
+
'hvpsu_2614b': find_hvpsu_2614b,
|
|
966
|
+
'controller': find_controller,
|
|
967
|
+
'controller_smc': find_controller_smc,
|
|
968
|
+
}
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
# define serial port connection settings for each device
|
|
972
|
+
# see https://pyserial.readthedocs.io/en/latest/pyserial_api.html
|
|
973
|
+
common_configuration = {
|
|
974
|
+
'bytesize': serial.EIGHTBITS,
|
|
975
|
+
'parity': serial.PARITY_NONE,
|
|
976
|
+
'stopbits': serial.STOPBITS_ONE,
|
|
977
|
+
'dsrdtr': False,
|
|
978
|
+
'timeout': 1,
|
|
979
|
+
'write_timeout': 1,
|
|
980
|
+
'inter_byte_timeout': None,
|
|
981
|
+
}
|
|
982
|
+
configs = {
|
|
983
|
+
'hvpsu': {
|
|
984
|
+
'baudrate': 9600,
|
|
985
|
+
'xonxoff': False,
|
|
986
|
+
'rtscts': False,
|
|
987
|
+
},
|
|
988
|
+
'hvpsu_2614b': {
|
|
989
|
+
'baudrate': 57600,
|
|
990
|
+
'xonxoff': False,
|
|
991
|
+
'rtscts': False,
|
|
992
|
+
},
|
|
993
|
+
'dmm_6500': {
|
|
994
|
+
'baudrate': 38400,
|
|
995
|
+
'xonxoff': False,
|
|
996
|
+
'rtscts': False,
|
|
997
|
+
},
|
|
998
|
+
'controller': {
|
|
999
|
+
'baudrate': 19200,
|
|
1000
|
+
'xonxoff': False,
|
|
1001
|
+
'rtscts': False,
|
|
1002
|
+
},
|
|
1003
|
+
'controller_smc': {
|
|
1004
|
+
'baudrate': 57600,
|
|
1005
|
+
'xonxoff': True,
|
|
1006
|
+
'rtscts': False,
|
|
1007
|
+
},
|
|
1008
|
+
'lvpsu': {
|
|
1009
|
+
'baudrate': 9600,
|
|
1010
|
+
'xonxoff': False,
|
|
1011
|
+
'rtscts': False,
|
|
1012
|
+
},
|
|
1013
|
+
'lvpsu_hmp4040': {
|
|
1014
|
+
'baudrate': 9600,
|
|
1015
|
+
'xonxoff': False,
|
|
1016
|
+
'rtscts': True,
|
|
1017
|
+
},
|
|
1018
|
+
}
|
|
1019
|
+
# create unions of common and device-specific configurations
|
|
1020
|
+
configs = {
|
|
1021
|
+
device: {**device_specific_configuration, **common_configuration}
|
|
1022
|
+
for device, device_specific_configuration in configs.items()
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
padding = max(map(len, tests))
|
|
1026
|
+
|
|
1027
|
+
# store creation platform in cache
|
|
1028
|
+
found = {'platform': sys.platform.lower()}
|
|
1029
|
+
|
|
1030
|
+
# detect devices on ports
|
|
1031
|
+
_ddop_pf = functools.partial(detect_device_on_port, tests=tests, configs=configs)
|
|
1032
|
+
with cf.ThreadPoolExecutor() as executor:
|
|
1033
|
+
detected = (executor.submit(_ddop_pf, port) for port in ports)
|
|
1034
|
+
for future in cf.as_completed(detected):
|
|
1035
|
+
key, settings, value = future.result()
|
|
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)
|
|
1042
|
+
|
|
1043
|
+
# JSON output
|
|
1044
|
+
if args.nocache:
|
|
1045
|
+
print(f'\nJSON:\n\n{json.dumps(found)}')
|
|
1046
|
+
else:
|
|
1047
|
+
common.configcache_write(found, common.DEVICE_CACHE)
|
|
1048
|
+
print(f'\nWrote detected configuration to: {common.DEVICE_CACHE}')
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
##############################################################################
|
|
1052
|
+
if __name__ == '__main__':
|
|
1053
|
+
main()
|