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.
@@ -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 mmcb_rs232 import common
32
- from mmcb_rs232 import lexicon
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
- '--nocache',
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 from list of detected devices',
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='Keithley 2614b may not be detected if tested after DMM 6500'
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
- return parser.parse_args()
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
- # 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()
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
- ser.close()
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
- # 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()
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
- ser.close()
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
- # 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()
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
- # 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()
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
- # 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()
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
- # 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()
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
- # 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()
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').lower().split(',')
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
- ser.close()
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
- # main
1119
+ # autodetect or user specified
927
1120
  ##############################################################################
928
1121
 
929
1122
 
930
- def main():
1123
+ def user_specified(args, configs, found):
931
1124
  """
932
- detect devices present on the serial ports, that are attached via
933
- FTDI USB to RS232 adaptors
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
- args = check_arguments()
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
- # 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)
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
- common.configcache_write(found, common.DEVICE_CACHE)
1048
- print(f'\nWrote detected configuration to: {common.DEVICE_CACHE}')
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
  ##############################################################################