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/psustat.py
ADDED
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Instantaneous snapshot of all power supply channels from devices connected
|
|
4
|
+
by FTDI USB to RS232 adaptors.
|
|
5
|
+
|
|
6
|
+
All power supplies supported by detect.py are usable by this script.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import collections
|
|
11
|
+
import contextlib
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
import threading
|
|
15
|
+
|
|
16
|
+
from mmcb import common
|
|
17
|
+
from mmcb import lexicon
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
##############################################################################
|
|
21
|
+
# command line option handler
|
|
22
|
+
##############################################################################
|
|
23
|
+
|
|
24
|
+
def check_arguments(settings):
|
|
25
|
+
"""
|
|
26
|
+
handle command line options
|
|
27
|
+
|
|
28
|
+
--------------------------------------------------------------------------
|
|
29
|
+
args : none
|
|
30
|
+
--------------------------------------------------------------------------
|
|
31
|
+
returns : none
|
|
32
|
+
--------------------------------------------------------------------------
|
|
33
|
+
"""
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
description='Provides an instantaneous snapshot of the\
|
|
36
|
+
configuration and status of power supply channels from all Keithley\
|
|
37
|
+
2410, 2614b; ISEG SHQ 222M, 224M; Agilent e3647a, e3634a; and Hameg\
|
|
38
|
+
(Rohde & Schwarz) HMP4040 power supplies connected by FTDI USB to\
|
|
39
|
+
RS232 adaptors.')
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
'--manufacturer', nargs=1, metavar='manufacturer',
|
|
42
|
+
choices=['agilent', 'hameg', 'iseg', 'keithley'],
|
|
43
|
+
help='PSU manufacturer.',
|
|
44
|
+
default=None)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
'--model', nargs=1, metavar='model',
|
|
47
|
+
choices=['2410', '2614b', 'e3634a', 'e3647a', 'hmp4040', 'shq'],
|
|
48
|
+
help='PSU model.',
|
|
49
|
+
default=None)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
'--serial', nargs=1, metavar='serial',
|
|
52
|
+
help='PSU serial number. A part of the serial may be supplied if it\
|
|
53
|
+
is unique amongst connected devices.',
|
|
54
|
+
default=None)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
'--channel', nargs=1, metavar='channel',
|
|
57
|
+
choices=['1', '2', '3', '4', 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D'],
|
|
58
|
+
help='PSU channel number. These can be specified numerically or\
|
|
59
|
+
alphabetically.',
|
|
60
|
+
default=None)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
'--port', nargs=1, metavar='port',
|
|
63
|
+
help='Serial port identifier. This is useful where multiple Agilent/\
|
|
64
|
+
Keysight/Hewlett-Packard power supplies are connected, since they do\
|
|
65
|
+
not provide a serial number over RS232. A part of the serial may be\
|
|
66
|
+
supplied if it is unique amongst connected devices.',
|
|
67
|
+
default=None)
|
|
68
|
+
|
|
69
|
+
group1 = parser.add_mutually_exclusive_group()
|
|
70
|
+
group1.add_argument(
|
|
71
|
+
'--sv', action='store_true',
|
|
72
|
+
help='NOT IMPLEMENTED YET. Read set voltage.\
|
|
73
|
+
Only valid if the user has specified a PSU channel.',
|
|
74
|
+
)
|
|
75
|
+
group1.add_argument(
|
|
76
|
+
'--mv', action='store_true',
|
|
77
|
+
help='Read measured voltage. Only valid if the user has specified a PSU channel.',
|
|
78
|
+
)
|
|
79
|
+
group1.add_argument(
|
|
80
|
+
'--si', action='store_true',
|
|
81
|
+
help='NOT IMPLEMENTED YET. Read set current.\
|
|
82
|
+
Only valid if the user has specified a PSU channel.',
|
|
83
|
+
)
|
|
84
|
+
group1.add_argument(
|
|
85
|
+
'--mi', action='store_true',
|
|
86
|
+
help='Read measured current. Only valid if the user has specified a PSU channel.',
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
args = parser.parse_args()
|
|
90
|
+
|
|
91
|
+
if args.serial:
|
|
92
|
+
settings['serial'] = args.serial[0]
|
|
93
|
+
|
|
94
|
+
if args.channel:
|
|
95
|
+
settings['channel'] = args.channel[0].lower()
|
|
96
|
+
|
|
97
|
+
if args.manufacturer:
|
|
98
|
+
settings['manufacturer'] = args.manufacturer[0]
|
|
99
|
+
|
|
100
|
+
if args.model:
|
|
101
|
+
settings['model'] = args.model[0]
|
|
102
|
+
|
|
103
|
+
if args.port:
|
|
104
|
+
settings['port'] = args.port[0]
|
|
105
|
+
|
|
106
|
+
select = [
|
|
107
|
+
settings['serial'],
|
|
108
|
+
settings['channel'],
|
|
109
|
+
settings['manufacturer'],
|
|
110
|
+
settings['model'],
|
|
111
|
+
settings['port'],
|
|
112
|
+
]
|
|
113
|
+
settings['filter'] = any(value is not None for value in select)
|
|
114
|
+
|
|
115
|
+
requests = collections.Counter([args.sv, args.mv, args.si, args.mi])
|
|
116
|
+
|
|
117
|
+
if settings['filter'] and requests[True] == 0:
|
|
118
|
+
print('select parameter to report with --sv, --mv, --si, --mi')
|
|
119
|
+
sys.exit()
|
|
120
|
+
|
|
121
|
+
settings['sv'] = args.sv
|
|
122
|
+
settings['mv'] = args.mv
|
|
123
|
+
settings['si'] = args.si
|
|
124
|
+
settings['mi'] = args.mi
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
##############################################################################
|
|
128
|
+
# utilities
|
|
129
|
+
##############################################################################
|
|
130
|
+
|
|
131
|
+
def hmp4040_constant_voltage_current_status(ser, pipeline, dev, tcolours):
|
|
132
|
+
"""
|
|
133
|
+
Query HMP4040 channel for constant voltage/current configuration.
|
|
134
|
+
|
|
135
|
+
--------------------------------------------------------------------------
|
|
136
|
+
args
|
|
137
|
+
ser : serial.Serial
|
|
138
|
+
reference for serial port
|
|
139
|
+
pipeline : instance of class Production
|
|
140
|
+
contains all the queues through which the production pipeline
|
|
141
|
+
processes communicate
|
|
142
|
+
dev : instance of class Channel
|
|
143
|
+
contains details of a device and its serial port
|
|
144
|
+
tcolours : class
|
|
145
|
+
contains ANSI colour escape sequences
|
|
146
|
+
--------------------------------------------------------------------------
|
|
147
|
+
returns : string
|
|
148
|
+
--------------------------------------------------------------------------
|
|
149
|
+
"""
|
|
150
|
+
assert dev.manufacturer == 'hameg', 'function only callable for Hameg PSU'
|
|
151
|
+
|
|
152
|
+
command_string = lexicon.power(dev.model, 'read channel mode', channel=dev.channel)
|
|
153
|
+
register = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
154
|
+
|
|
155
|
+
status_categories = {
|
|
156
|
+
# constant_voltage, constant_current
|
|
157
|
+
(False, False): 'neither constant voltage nor constant current',
|
|
158
|
+
(False, True): f'{tcolours.BOLD}{tcolours.FG_BRIGHT_RED}constant current{tcolours.ENDC}',
|
|
159
|
+
(True, False): f'{tcolours.BOLD}{tcolours.FG_BRIGHT_GREEN}constant voltage{tcolours.ENDC}',
|
|
160
|
+
(True, True): 'both constant voltage and constant current (!)',
|
|
161
|
+
None: 'could not be determined'}
|
|
162
|
+
|
|
163
|
+
configuration = None
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
regval = int(register)
|
|
167
|
+
except ValueError:
|
|
168
|
+
pass
|
|
169
|
+
else:
|
|
170
|
+
constant_curr = (regval & (1 << 0)) != 0
|
|
171
|
+
constant_volt = (regval & (1 << 1)) != 0
|
|
172
|
+
configuration = (constant_volt, constant_curr)
|
|
173
|
+
|
|
174
|
+
return status_categories[configuration]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def simple_current_limit(ser, pipeline, dev, eng_format=True):
|
|
178
|
+
"""
|
|
179
|
+
Query current limit information for HMP4040, 2410, 2614b.
|
|
180
|
+
|
|
181
|
+
--------------------------------------------------------------------------
|
|
182
|
+
args
|
|
183
|
+
ser : serial.Serial
|
|
184
|
+
reference for serial port
|
|
185
|
+
pipeline : instance of class Production
|
|
186
|
+
contains all the queues through which the production pipeline
|
|
187
|
+
processes communicate
|
|
188
|
+
dev : instance of class Channel
|
|
189
|
+
contains details of a device and its serial port
|
|
190
|
+
--------------------------------------------------------------------------
|
|
191
|
+
returns : string
|
|
192
|
+
--------------------------------------------------------------------------
|
|
193
|
+
"""
|
|
194
|
+
message = None
|
|
195
|
+
|
|
196
|
+
if dev.model in ('hmp4040', '2410', '2614b'):
|
|
197
|
+
command_string = lexicon.power(dev.model, 'get current limit', channel=dev.channel)
|
|
198
|
+
response = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
199
|
+
if eng_format:
|
|
200
|
+
with contextlib.suppress(ValueError):
|
|
201
|
+
message = f'{common.si_prefix(response)}A'
|
|
202
|
+
else:
|
|
203
|
+
with contextlib.suppress(ValueError):
|
|
204
|
+
message = float(response)
|
|
205
|
+
|
|
206
|
+
return message
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def simple_output_status(ser, pipeline, dev, tcolours):
|
|
210
|
+
"""
|
|
211
|
+
Return the on/off output status of the given power supply channel.
|
|
212
|
+
|
|
213
|
+
Values returned in variable output:
|
|
214
|
+
|
|
215
|
+
+------------------+-----------+---------------+---------------+
|
|
216
|
+
| dev.manufacturer | dev.model | output OFF | output ON |
|
|
217
|
+
+------------------+-----------+---------------+---------------+
|
|
218
|
+
| 'agilent' | 'e3634a' | '0' | '1' |
|
|
219
|
+
| 'agilent' | 'e3647a' | '0' | '1' |
|
|
220
|
+
| 'hameg' | 'hmp4040' | '0' | '1' |
|
|
221
|
+
| 'iseg' | 'shq' | '=OFF' | '=ON' |
|
|
222
|
+
| 'keithley' | '2410' | '0' | '1' |
|
|
223
|
+
| 'keithley' | '2614b' | '0.00000e+00' | '1.00000e+00' |
|
|
224
|
+
+------------------+-----------+---------------+---------------+
|
|
225
|
+
|
|
226
|
+
--------------------------------------------------------------------------
|
|
227
|
+
args
|
|
228
|
+
ser : serial.Serial
|
|
229
|
+
reference for serial port
|
|
230
|
+
pipeline : instance of class Production
|
|
231
|
+
contains all the queues through which the production pipeline
|
|
232
|
+
processes communicate
|
|
233
|
+
dev : instance of class Channel
|
|
234
|
+
contains details of a device and its serial port
|
|
235
|
+
tcolours : class
|
|
236
|
+
contains ANSI colour escape sequences
|
|
237
|
+
--------------------------------------------------------------------------
|
|
238
|
+
returns : string
|
|
239
|
+
--------------------------------------------------------------------------
|
|
240
|
+
"""
|
|
241
|
+
command_string = lexicon.power(dev.model, 'check output', channel=dev.channel)
|
|
242
|
+
output = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
243
|
+
|
|
244
|
+
status_categories = {
|
|
245
|
+
True: f'{tcolours.BOLD}on{tcolours.ENDC}',
|
|
246
|
+
False: 'off',
|
|
247
|
+
None: 'could not be determined'}
|
|
248
|
+
|
|
249
|
+
channel_on = None
|
|
250
|
+
|
|
251
|
+
if dev.manufacturer == 'iseg':
|
|
252
|
+
if any(x in output for x in ['=ON', '=OFF']):
|
|
253
|
+
channel_on = '=ON' in output
|
|
254
|
+
|
|
255
|
+
elif dev.manufacturer in {'agilent', 'hameg', 'keithley'}:
|
|
256
|
+
try:
|
|
257
|
+
outval = int(float(output))
|
|
258
|
+
except ValueError:
|
|
259
|
+
pass
|
|
260
|
+
else:
|
|
261
|
+
if outval in {0, 1}:
|
|
262
|
+
channel_on = outval == 1
|
|
263
|
+
|
|
264
|
+
return status_categories[channel_on]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def simple_interlock_status(ser, pipeline, dev, tcolours):
|
|
268
|
+
"""
|
|
269
|
+
Check status of power supply interlock.
|
|
270
|
+
|
|
271
|
+
This is per-PSU on Keithley, per-channel on ISEG.
|
|
272
|
+
|
|
273
|
+
--------------------------------------------------------------------------
|
|
274
|
+
args
|
|
275
|
+
ser : serial.Serial
|
|
276
|
+
reference for serial port
|
|
277
|
+
pipeline : instance of class Production
|
|
278
|
+
contains all the queues through which the production pipeline
|
|
279
|
+
processes communicate
|
|
280
|
+
dev : instance of class Channel
|
|
281
|
+
contains details of a device and its serial port
|
|
282
|
+
tcolours : class
|
|
283
|
+
contains ANSI colour escape sequences
|
|
284
|
+
--------------------------------------------------------------------------
|
|
285
|
+
returns : string
|
|
286
|
+
--------------------------------------------------------------------------
|
|
287
|
+
"""
|
|
288
|
+
assert dev.manufacturer not in {'agilent', 'hameg'},\
|
|
289
|
+
'function not callable for Agilent or Hameg PSU'
|
|
290
|
+
|
|
291
|
+
status_categories = {
|
|
292
|
+
True: 'active',
|
|
293
|
+
False: f'{tcolours.BOLD}inactive{tcolours.ENDC}',
|
|
294
|
+
None: 'could not be determined'}
|
|
295
|
+
|
|
296
|
+
interlock_set = None
|
|
297
|
+
|
|
298
|
+
command_string = lexicon.power(dev.model, 'check interlock', channel=dev.channel)
|
|
299
|
+
register = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
300
|
+
|
|
301
|
+
if dev.manufacturer == 'iseg':
|
|
302
|
+
# Interlocks are per-channel for ISEG SHQ.
|
|
303
|
+
interlock_set = '=INH' in register
|
|
304
|
+
else:
|
|
305
|
+
# Interlocks are per-PSU for Keithley.
|
|
306
|
+
try:
|
|
307
|
+
regval = int(float(register))
|
|
308
|
+
except ValueError:
|
|
309
|
+
pass
|
|
310
|
+
else:
|
|
311
|
+
if dev.model == '2614b':
|
|
312
|
+
# bit 11 (status.measurement.INTERLOCK) p.11-280 (648)
|
|
313
|
+
# Without hardware interlock: '0.00000e+00'
|
|
314
|
+
# with hardware interlock: '2.04800e+03'
|
|
315
|
+
interlock_set = regval & 2048 == 0
|
|
316
|
+
elif dev.model == '2410':
|
|
317
|
+
if regval in {0, 1}:
|
|
318
|
+
# p.18-9 (389)
|
|
319
|
+
# returns '0' (disabled - no restrictions) or '1' (enabled)
|
|
320
|
+
interlock_set = register == 0
|
|
321
|
+
|
|
322
|
+
return status_categories[interlock_set]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def simple_polarity_status(ser, pipeline, dev, tcolours):
|
|
326
|
+
"""
|
|
327
|
+
Returned string from 'check module status' command appears to be base 10.
|
|
328
|
+
|
|
329
|
+
e.g.
|
|
330
|
+
inhibit is bit 5 (0=inactive, 1=active),
|
|
331
|
+
polarity is bit 2 (0=negative, 1=positive)
|
|
332
|
+
|
|
333
|
+
For channel 1, with inhibit active (terminator removed from front
|
|
334
|
+
panel inhibit socket) and the polarity switch set to POS on the
|
|
335
|
+
rear panel, the 'S1' command returns '036'
|
|
336
|
+
|
|
337
|
+
e.g.
|
|
338
|
+
polarity set to positive: '004', negative: '000'
|
|
339
|
+
|
|
340
|
+
--------------------------------------------------------------------------
|
|
341
|
+
args
|
|
342
|
+
ser : serial.Serial
|
|
343
|
+
reference for serial port
|
|
344
|
+
pipeline : instance of class Production
|
|
345
|
+
contains all the queues through which the production pipeline
|
|
346
|
+
processes communicate
|
|
347
|
+
dev : instance of class Channel
|
|
348
|
+
contains details of a device and its serial port
|
|
349
|
+
tcolours : class
|
|
350
|
+
contains ANSI colour escape sequences
|
|
351
|
+
--------------------------------------------------------------------------
|
|
352
|
+
returns : string
|
|
353
|
+
--------------------------------------------------------------------------
|
|
354
|
+
"""
|
|
355
|
+
assert dev.model == 'shq', 'function only callable for ISEG SHQ PSU'
|
|
356
|
+
|
|
357
|
+
status_categories = {
|
|
358
|
+
True: 'positive',
|
|
359
|
+
False: 'negative',
|
|
360
|
+
None: f'{tcolours.BG_RED}{tcolours.FG_WHITE}could not be determined{tcolours.ENDC}'}
|
|
361
|
+
|
|
362
|
+
polarity_positive = None
|
|
363
|
+
|
|
364
|
+
command_string = lexicon.power(dev.model, 'check module status', channel=dev.channel)
|
|
365
|
+
register = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
366
|
+
|
|
367
|
+
with contextlib.suppress(ValueError):
|
|
368
|
+
polarity_positive = int(register) & 4 != 0
|
|
369
|
+
|
|
370
|
+
return status_categories[polarity_positive]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def power_supply_channel_status(settings, pipeline, psus, channels, tcolours):
|
|
374
|
+
"""
|
|
375
|
+
Establishes RS232 communications with power supplies (as required).
|
|
376
|
+
Checks status of channel outputs and interlocks (inhibits).
|
|
377
|
+
|
|
378
|
+
--------------------------------------------------------------------------
|
|
379
|
+
args
|
|
380
|
+
settings : dictionary
|
|
381
|
+
contains core information about the test environment
|
|
382
|
+
pipeline : instance of class Production
|
|
383
|
+
contains all the queues through which the production pipeline
|
|
384
|
+
processes communicate
|
|
385
|
+
psus : dict
|
|
386
|
+
{port: ({port_config}, device_type, device_serial_number), ...}
|
|
387
|
+
contents of the cache filtered by hvpsu category
|
|
388
|
+
channels : list
|
|
389
|
+
contains instances of class Channel, one for each
|
|
390
|
+
power supply channel
|
|
391
|
+
tcolours : class
|
|
392
|
+
contains ANSI colour escape sequences
|
|
393
|
+
--------------------------------------------------------------------------
|
|
394
|
+
returns
|
|
395
|
+
channels : list
|
|
396
|
+
no explicit return, mutable type amended in place
|
|
397
|
+
--------------------------------------------------------------------------
|
|
398
|
+
"""
|
|
399
|
+
port_used = collections.defaultdict(int)
|
|
400
|
+
|
|
401
|
+
# this function call may change the value of variable channels
|
|
402
|
+
# and returns a dict containing a mapping of ports to the serial ports
|
|
403
|
+
# which have been left open
|
|
404
|
+
spd = common.check_ports_accessible(psus, channels, close_after_check=False)
|
|
405
|
+
|
|
406
|
+
for dev in channels:
|
|
407
|
+
read_values = []
|
|
408
|
+
|
|
409
|
+
######################################################################
|
|
410
|
+
# identify channel
|
|
411
|
+
######################################################################
|
|
412
|
+
|
|
413
|
+
if not settings['filter']:
|
|
414
|
+
print()
|
|
415
|
+
print(dev)
|
|
416
|
+
|
|
417
|
+
# specify already opened serial port
|
|
418
|
+
ser = spd[dev.port]
|
|
419
|
+
|
|
420
|
+
# try to ensure consistent state
|
|
421
|
+
# clear FTDI output buffer state before sending
|
|
422
|
+
ser.reset_output_buffer()
|
|
423
|
+
|
|
424
|
+
# clear PSU state
|
|
425
|
+
if dev.model == '2614b':
|
|
426
|
+
command_string = lexicon.power(dev.model, 'terminator only',
|
|
427
|
+
channel=dev.channel)
|
|
428
|
+
common.send_command(pipeline, ser, dev, command_string)
|
|
429
|
+
|
|
430
|
+
# clear FTDI input buffer state
|
|
431
|
+
ser.reset_input_buffer()
|
|
432
|
+
|
|
433
|
+
# arbitrary settle time before proceeding
|
|
434
|
+
time.sleep(0.0)
|
|
435
|
+
|
|
436
|
+
# ensure serial port communication with PSU
|
|
437
|
+
# this is for ISEG SHQ only, on a per-PSU basis
|
|
438
|
+
if not port_used[dev.port]:
|
|
439
|
+
common.synchronise_psu(ser, pipeline, dev)
|
|
440
|
+
|
|
441
|
+
##################################################################
|
|
442
|
+
# determine interlock status
|
|
443
|
+
##################################################################
|
|
444
|
+
|
|
445
|
+
if not settings['filter']:
|
|
446
|
+
# this is per-PSU on Keithley, per-channel on ISEG
|
|
447
|
+
# still report it per-channel either way.
|
|
448
|
+
if dev.manufacturer not in {'agilent', 'hameg'}:
|
|
449
|
+
print(f'interlock status: {simple_interlock_status(ser, pipeline, dev, tcolours)}')
|
|
450
|
+
|
|
451
|
+
##################################################################
|
|
452
|
+
# determine output status
|
|
453
|
+
##################################################################
|
|
454
|
+
|
|
455
|
+
out_stat = simple_output_status(ser, pipeline, dev, tcolours)
|
|
456
|
+
set_volt = read_psu_set_voltage(pipeline, ser, dev)
|
|
457
|
+
|
|
458
|
+
if settings['filter']:
|
|
459
|
+
if settings['sv']:
|
|
460
|
+
print(set_volt)
|
|
461
|
+
else:
|
|
462
|
+
print(f'output status: {out_stat}')
|
|
463
|
+
|
|
464
|
+
if set_volt is None:
|
|
465
|
+
sv_text = 'could not be determined'
|
|
466
|
+
else:
|
|
467
|
+
sv_text = f'{common.si_prefix(set_volt)}V'
|
|
468
|
+
read_values.append(f'set voltage: {sv_text}')
|
|
469
|
+
|
|
470
|
+
##################################################################
|
|
471
|
+
# determine current limit
|
|
472
|
+
##################################################################
|
|
473
|
+
|
|
474
|
+
current_limit = simple_current_limit(ser, pipeline, dev)
|
|
475
|
+
if current_limit is not None:
|
|
476
|
+
if settings['filter']:
|
|
477
|
+
if settings['si']:
|
|
478
|
+
print(
|
|
479
|
+
simple_current_limit(ser, pipeline, dev, eng_format=False)
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
print(f'current limit: {current_limit}')
|
|
483
|
+
|
|
484
|
+
##################################################################
|
|
485
|
+
# report measured values
|
|
486
|
+
##################################################################
|
|
487
|
+
|
|
488
|
+
if 'on' in out_stat:
|
|
489
|
+
mvolt, mcurr = read_psu_vi(pipeline, ser, dev)
|
|
490
|
+
if mvolt is None:
|
|
491
|
+
mvi_text = 'could not be determined'
|
|
492
|
+
else:
|
|
493
|
+
if not settings['filter']:
|
|
494
|
+
mvi_text = f'{common.si_prefix(mvolt)}V, {common.si_prefix(mcurr)}A'
|
|
495
|
+
else:
|
|
496
|
+
if settings['mi']:
|
|
497
|
+
print(mcurr)
|
|
498
|
+
elif settings['mv']:
|
|
499
|
+
print(mvolt)
|
|
500
|
+
|
|
501
|
+
if not settings['filter']:
|
|
502
|
+
read_values.append(f'measured: {mvi_text}')
|
|
503
|
+
|
|
504
|
+
else:
|
|
505
|
+
if settings['filter']:
|
|
506
|
+
if settings['mi'] or settings['mv']:
|
|
507
|
+
print('None')
|
|
508
|
+
else:
|
|
509
|
+
print('None')
|
|
510
|
+
|
|
511
|
+
##################################################################
|
|
512
|
+
# determine polarity status
|
|
513
|
+
##################################################################
|
|
514
|
+
|
|
515
|
+
if not settings['filter']:
|
|
516
|
+
if dev.manufacturer == 'iseg':
|
|
517
|
+
print(f'polarity: {simple_polarity_status(ser, pipeline, dev, tcolours)}')
|
|
518
|
+
|
|
519
|
+
##################################################################
|
|
520
|
+
# determine constant voltage/current operation
|
|
521
|
+
##################################################################
|
|
522
|
+
|
|
523
|
+
if not settings['filter']:
|
|
524
|
+
if dev.manufacturer == 'hameg':
|
|
525
|
+
print(f'mode: {hmp4040_constant_voltage_current_status(ser, pipeline, dev, tcolours)}')
|
|
526
|
+
|
|
527
|
+
ser.reset_input_buffer()
|
|
528
|
+
ser.reset_output_buffer()
|
|
529
|
+
|
|
530
|
+
if not settings['filter']:
|
|
531
|
+
print(', '.join(read_values))
|
|
532
|
+
|
|
533
|
+
port_used[dev.port] += 1
|
|
534
|
+
|
|
535
|
+
# close all the serial ports left open by the call above to
|
|
536
|
+
# common.check_ports_accessible()
|
|
537
|
+
for serial_port in spd.values():
|
|
538
|
+
serial_port.close()
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def read_psu_vi(pipeline, ser, dev):
|
|
542
|
+
"""
|
|
543
|
+
read measured voltage and current from PSU
|
|
544
|
+
|
|
545
|
+
--------------------------------------------------------------------------
|
|
546
|
+
args
|
|
547
|
+
pipeline : instance of class Production
|
|
548
|
+
contains all the queues through which the production pipeline
|
|
549
|
+
processes communicate
|
|
550
|
+
ser : serial.Serial
|
|
551
|
+
reference for serial port
|
|
552
|
+
dev : instance of class Channel
|
|
553
|
+
contains details of a device and its serial port
|
|
554
|
+
--------------------------------------------------------------------------
|
|
555
|
+
returns
|
|
556
|
+
dev : instance of class Channel
|
|
557
|
+
contains details of a device and its serial port
|
|
558
|
+
readings : list
|
|
559
|
+
[(float, float)]
|
|
560
|
+
--------------------------------------------------------------------------
|
|
561
|
+
"""
|
|
562
|
+
if dev.manufacturer in {'keithley', 'iseg'}:
|
|
563
|
+
volt, curr = common.read_psu_measured_vi(pipeline, ser, dev)
|
|
564
|
+
elif dev.manufacturer in {'agilent', 'hameg', 'hewlett-packard'}:
|
|
565
|
+
if dev.model == 'e3634a':
|
|
566
|
+
command_string = lexicon.power(dev.model, 'set remote')
|
|
567
|
+
common.send_command(pipeline, ser, dev, command_string)
|
|
568
|
+
|
|
569
|
+
command_string = lexicon.power(dev.model, 'read voltage', channel=dev.channel)
|
|
570
|
+
measured_voltage = common.atomic_send_command_read_response(pipeline, ser,
|
|
571
|
+
dev, command_string)
|
|
572
|
+
|
|
573
|
+
command_string = lexicon.power(dev.model, 'read current', channel=dev.channel)
|
|
574
|
+
measured_current = common.atomic_send_command_read_response(pipeline, ser,
|
|
575
|
+
dev, command_string)
|
|
576
|
+
try:
|
|
577
|
+
volt = float(measured_voltage)
|
|
578
|
+
curr = float(measured_current)
|
|
579
|
+
except ValueError:
|
|
580
|
+
volt = curr = None
|
|
581
|
+
|
|
582
|
+
# ignore readings if output is off
|
|
583
|
+
if dev.manufacturer == 'hameg':
|
|
584
|
+
command_string = lexicon.power(dev.model, 'check output', channel=dev.channel)
|
|
585
|
+
output_status = common.atomic_send_command_read_response(pipeline, ser,
|
|
586
|
+
dev, command_string)
|
|
587
|
+
if output_status == '0':
|
|
588
|
+
volt = curr = None
|
|
589
|
+
|
|
590
|
+
else:
|
|
591
|
+
volt = curr = None
|
|
592
|
+
|
|
593
|
+
return volt, curr
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def read_psu_set_voltage(pipeline, ser, dev):
|
|
597
|
+
"""
|
|
598
|
+
Read the voltage that the PSU has been asked to output (rather than the
|
|
599
|
+
actual instantaneous voltage measured at the output terminals).
|
|
600
|
+
|
|
601
|
+
--------------------------------------------------------------------------
|
|
602
|
+
args
|
|
603
|
+
pipeline : instance of class Production
|
|
604
|
+
contains all the queues through which the production pipeline
|
|
605
|
+
processes communicate
|
|
606
|
+
ser : serial.Serial
|
|
607
|
+
reference for serial port
|
|
608
|
+
dev : instance of class Channel
|
|
609
|
+
contains details of a device and its serial port
|
|
610
|
+
--------------------------------------------------------------------------
|
|
611
|
+
returns
|
|
612
|
+
set_volt : float or None
|
|
613
|
+
float if the value could be read or None if it could not
|
|
614
|
+
--------------------------------------------------------------------------
|
|
615
|
+
"""
|
|
616
|
+
set_volt = None
|
|
617
|
+
|
|
618
|
+
command_string = lexicon.power(dev.model, 'read set voltage', channel=dev.channel)
|
|
619
|
+
local_buffer = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
620
|
+
|
|
621
|
+
if dev.manufacturer == 'iseg' and dev.model == 'shq':
|
|
622
|
+
set_volt = common.iseg_value_to_float(local_buffer)
|
|
623
|
+
|
|
624
|
+
elif dev.manufacturer == 'keithley' and dev.model in {'2410', '2614b'}:
|
|
625
|
+
if dev.model == '2410':
|
|
626
|
+
# e.g. '-5.000000E+00,-1.005998E-10,+9.910000E+37,+1.185742E+04,+2.150800E+04'
|
|
627
|
+
item = next(iter(local_buffer.split(',')))
|
|
628
|
+
else:
|
|
629
|
+
# e.g. '-5.00000e+00'
|
|
630
|
+
item = local_buffer
|
|
631
|
+
|
|
632
|
+
with contextlib.suppress(TypeError, ValueError):
|
|
633
|
+
set_volt = float(item)
|
|
634
|
+
|
|
635
|
+
elif dev.manufacturer == 'hameg':
|
|
636
|
+
with contextlib.suppress(ValueError):
|
|
637
|
+
set_volt = float(local_buffer)
|
|
638
|
+
|
|
639
|
+
return set_volt
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
##############################################################################
|
|
643
|
+
# main
|
|
644
|
+
##############################################################################
|
|
645
|
+
|
|
646
|
+
def main():
|
|
647
|
+
"""
|
|
648
|
+
Instantaneous snapshot of all power supply channels from devices
|
|
649
|
+
connected by FTDI USB to RS232 adaptors.
|
|
650
|
+
"""
|
|
651
|
+
settings = {
|
|
652
|
+
'alias': None,
|
|
653
|
+
'channel': None,
|
|
654
|
+
'manufacturer': None,
|
|
655
|
+
'model': None,
|
|
656
|
+
'port': None,
|
|
657
|
+
'serial': None,
|
|
658
|
+
'time': None,
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
check_arguments(settings)
|
|
662
|
+
|
|
663
|
+
##########################################################################
|
|
664
|
+
# read cache
|
|
665
|
+
##########################################################################
|
|
666
|
+
|
|
667
|
+
psus = common.cache_read(['hvpsu', 'lvpsu'], quiet=settings['filter'])
|
|
668
|
+
channels = common.ports_to_channels(settings, psus)
|
|
669
|
+
|
|
670
|
+
if settings['filter'] and not common.unique(settings, psus, channels):
|
|
671
|
+
print('could not identify a single power supply channel')
|
|
672
|
+
print('check use of --manufacturer, --model, --serial, --port, and --channel')
|
|
673
|
+
sys.exit()
|
|
674
|
+
|
|
675
|
+
##########################################################################
|
|
676
|
+
# set up resources for threads
|
|
677
|
+
##########################################################################
|
|
678
|
+
|
|
679
|
+
class Production:
|
|
680
|
+
"""
|
|
681
|
+
Queues and locks to support threaded operation.
|
|
682
|
+
|
|
683
|
+
RS232 will not tolerate concurrent access. portaccess is used to
|
|
684
|
+
prevent more than one thread trying to write to the same RS232 port at
|
|
685
|
+
the same time for multi-channel power supplies. For simplicity, locks
|
|
686
|
+
are created for all power supplies, even if they only have a single
|
|
687
|
+
channel.
|
|
688
|
+
"""
|
|
689
|
+
portaccess = {port: threading.Lock()
|
|
690
|
+
for port in {channel.port for channel in channels}}
|
|
691
|
+
|
|
692
|
+
pipeline = Production()
|
|
693
|
+
|
|
694
|
+
##########################################################################
|
|
695
|
+
# Check status of outputs and interlock (inhibit) on all power supplies
|
|
696
|
+
##########################################################################
|
|
697
|
+
|
|
698
|
+
power_supply_channel_status(
|
|
699
|
+
settings, pipeline, psus, channels, common.ANSIColours
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
##############################################################################
|
|
704
|
+
if __name__ == '__main__':
|
|
705
|
+
main()
|