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/psuwatch.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Monitors the voltage and current of power supplies connected by FTDI USB to
|
|
4
|
+
RS232 adaptors. Results are written to the screen and to a log file.
|
|
5
|
+
|
|
6
|
+
All power supplies supported by detect.py are usable by this script.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import collections
|
|
11
|
+
import concurrent.futures as cf
|
|
12
|
+
import functools
|
|
13
|
+
import logging
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
import serial
|
|
18
|
+
|
|
19
|
+
from mmcb import common
|
|
20
|
+
from mmcb import lexicon
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
##############################################################################
|
|
24
|
+
# utilities
|
|
25
|
+
##############################################################################
|
|
26
|
+
|
|
27
|
+
def format_reading(settings, tcolours, psu_info):
|
|
28
|
+
"""
|
|
29
|
+
format PSU serial number, voltage and current read from PSU
|
|
30
|
+
adding colour to current values that are over given thresholds
|
|
31
|
+
|
|
32
|
+
--------------------------------------------------------------------------
|
|
33
|
+
args
|
|
34
|
+
settings : dictionary
|
|
35
|
+
contains core information about the test environment
|
|
36
|
+
tcolours : class
|
|
37
|
+
contains ANSI colour escape sequences
|
|
38
|
+
psu_info : tuple (string, string, [(float, float), ...])
|
|
39
|
+
(psu_type, psu_serial_number, [(voltage, current), ])
|
|
40
|
+
e.g. ('hvpsu', '4343654', [(0.0, 5.143E-12), ...])
|
|
41
|
+
may contain readings for more than one power supply output
|
|
42
|
+
--------------------------------------------------------------------------
|
|
43
|
+
returns
|
|
44
|
+
retstr : string
|
|
45
|
+
--------------------------------------------------------------------------
|
|
46
|
+
"""
|
|
47
|
+
dev, readings = psu_info
|
|
48
|
+
|
|
49
|
+
if dev.category == 'hvpsu':
|
|
50
|
+
warning = settings['hv_warning']
|
|
51
|
+
critical = settings['hv_critical']
|
|
52
|
+
else:
|
|
53
|
+
warning = settings['lv_warning']
|
|
54
|
+
critical = settings['lv_critical']
|
|
55
|
+
|
|
56
|
+
colour_start = colour_end = output = ''
|
|
57
|
+
ident = dev.ident.replace(' ', '_')
|
|
58
|
+
|
|
59
|
+
for voltage, current in readings:
|
|
60
|
+
if voltage is not None and current is not None:
|
|
61
|
+
if warning is not None:
|
|
62
|
+
if abs(current) > warning:
|
|
63
|
+
colour_start = tcolours.BG_YELLOW
|
|
64
|
+
colour_end = tcolours.ENDC
|
|
65
|
+
if critical is not None:
|
|
66
|
+
if abs(current) > critical:
|
|
67
|
+
colour_start = tcolours.BG_RED + tcolours.FG_WHITE
|
|
68
|
+
colour_end = tcolours.ENDC
|
|
69
|
+
output += (f' '
|
|
70
|
+
f'{common.si_prefix(voltage, compact=False).rjust(9)}V '
|
|
71
|
+
f'{colour_start}'
|
|
72
|
+
f'{common.si_prefix(current, compact=False).rjust(9)}A'
|
|
73
|
+
f'{colour_end}')
|
|
74
|
+
else:
|
|
75
|
+
colour_start = tcolours.BG_RED + tcolours.FG_WHITE
|
|
76
|
+
colour_end = tcolours.ENDC
|
|
77
|
+
output += f' {colour_start}None{colour_end}'
|
|
78
|
+
|
|
79
|
+
return f'{ident}{output}'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
##############################################################################
|
|
83
|
+
# command line option handler
|
|
84
|
+
##############################################################################
|
|
85
|
+
|
|
86
|
+
def check_duration(val):
|
|
87
|
+
"""
|
|
88
|
+
time between samples in seconds
|
|
89
|
+
|
|
90
|
+
--------------------------------------------------------------------------
|
|
91
|
+
args
|
|
92
|
+
val : int
|
|
93
|
+
the runtime of read_psu_vi is typically around 1 second
|
|
94
|
+
so sensible values range from 2 to perhaps 60 seconds
|
|
95
|
+
--------------------------------------------------------------------------
|
|
96
|
+
returns
|
|
97
|
+
val : int
|
|
98
|
+
--------------------------------------------------------------------------
|
|
99
|
+
"""
|
|
100
|
+
val = int(val)
|
|
101
|
+
if not 0 <= val <= 60:
|
|
102
|
+
raise argparse.ArgumentTypeError(
|
|
103
|
+
f'{val}: '
|
|
104
|
+
'should be a positive numeric value between 0 and 60 (seconds)')
|
|
105
|
+
return val
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def check_arguments(settings):
|
|
109
|
+
"""
|
|
110
|
+
handle command line options
|
|
111
|
+
|
|
112
|
+
--------------------------------------------------------------------------
|
|
113
|
+
args
|
|
114
|
+
settings : dictionary
|
|
115
|
+
contains core information about the test environment
|
|
116
|
+
--------------------------------------------------------------------------
|
|
117
|
+
returns : none
|
|
118
|
+
--------------------------------------------------------------------------
|
|
119
|
+
"""
|
|
120
|
+
parser = argparse.ArgumentParser(
|
|
121
|
+
description='Monitors the voltage and current of all Keithley 2410,\
|
|
122
|
+
2614b; ISEG SHQ 222M, 224M; Agilent e3647a, e3634a; and Hameg\
|
|
123
|
+
(Rohde & Schwarz) HMP4040 power supplies connected by\
|
|
124
|
+
FTDI USB to RS232 adaptors.')
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
'--alias', nargs=1, metavar='filename',
|
|
127
|
+
help='By default, serial numbers read from the power supplies over\
|
|
128
|
+
RS232 are used to identify them in the logs. This option allows a CSV\
|
|
129
|
+
file containing aliases to be read in, where each line consists of a\
|
|
130
|
+
serial number then a textual description, separated by a comma\
|
|
131
|
+
e.g. 4120021,LVSC03 red. These aliases are then substituted for the\
|
|
132
|
+
serial numbers in the logs and in filenames as appropriate. Comments\
|
|
133
|
+
starting with a hash "#" are allowed.',
|
|
134
|
+
default=None)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
'-t', '--time', nargs=1, metavar='seconds',
|
|
137
|
+
help='Time between samples in seconds. if this option is not used,\
|
|
138
|
+
the script will default to sampling as fast as possible,\
|
|
139
|
+
around one sample per second.',
|
|
140
|
+
type=check_duration, default=[1])
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
'--whv', nargs=2, metavar=('threshold', 'threshold'),
|
|
143
|
+
help='For high voltage PSUs, the threshold values above which current\
|
|
144
|
+
readings will be displayed with coloured warning highlights.\
|
|
145
|
+
Values above the lower threshold will be shown in yellow, values\
|
|
146
|
+
above the upper threshold will be shown in red. Values can be\
|
|
147
|
+
specified with either scientific (10e-12) or engineering notation\
|
|
148
|
+
(10p)',
|
|
149
|
+
type=common.check_current, default=(None, None))
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
'--wlv', nargs=2, metavar=('threshold', 'threshold'),
|
|
152
|
+
help='For low voltage PSUs, the threshold values above which current\
|
|
153
|
+
readings will be displayed with coloured warning highlights.\
|
|
154
|
+
Values above the lower threshold will be shown in yellow, values\
|
|
155
|
+
above the upper threshold will be shown in red. Values can be\
|
|
156
|
+
specified with either scientific (10e-12) or engineering notation\
|
|
157
|
+
(10p)',
|
|
158
|
+
type=common.check_current, default=(None, None))
|
|
159
|
+
|
|
160
|
+
args = parser.parse_args()
|
|
161
|
+
|
|
162
|
+
settings['time'] = args.time[0]
|
|
163
|
+
|
|
164
|
+
th1, th2 = args.whv
|
|
165
|
+
if th1 is not None and th2 is not None:
|
|
166
|
+
if th1 > th2:
|
|
167
|
+
th1, th2 = th2, th1
|
|
168
|
+
settings['hv_warning'] = th1
|
|
169
|
+
settings['hv_critical'] = th2
|
|
170
|
+
|
|
171
|
+
th1, th2 = args.wlv
|
|
172
|
+
if th1 is not None and th2 is not None:
|
|
173
|
+
if th1 > th2:
|
|
174
|
+
th1, th2 = th2, th1
|
|
175
|
+
settings['lv_warning'] = th1
|
|
176
|
+
settings['lv_critical'] = th2
|
|
177
|
+
|
|
178
|
+
if args.alias:
|
|
179
|
+
filename = args.alias[0]
|
|
180
|
+
common.read_aliases(settings, filename)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
##############################################################################
|
|
184
|
+
# get data from devices on serial ports
|
|
185
|
+
##############################################################################
|
|
186
|
+
|
|
187
|
+
def read_psu_vi(dev, pipeline):
|
|
188
|
+
"""
|
|
189
|
+
read voltage and current from PSU
|
|
190
|
+
|
|
191
|
+
--------------------------------------------------------------------------
|
|
192
|
+
args
|
|
193
|
+
dev : instance of class Channel
|
|
194
|
+
contains details of a device and its serial port
|
|
195
|
+
pipeline : instance of class Production
|
|
196
|
+
contains all the queues through which the production pipeline
|
|
197
|
+
processes communicate
|
|
198
|
+
--------------------------------------------------------------------------
|
|
199
|
+
returns
|
|
200
|
+
dev : instance of class Channel
|
|
201
|
+
contains details of a device and its serial port
|
|
202
|
+
readings : list
|
|
203
|
+
[(float, float)]
|
|
204
|
+
--------------------------------------------------------------------------
|
|
205
|
+
"""
|
|
206
|
+
with serial.Serial(port=dev.port) as ser:
|
|
207
|
+
ser.apply_settings(dev.config)
|
|
208
|
+
if dev.manufacturer in {'keithley', 'iseg'}:
|
|
209
|
+
volt, curr = common.read_psu_measured_vi(pipeline, ser, dev)
|
|
210
|
+
elif dev.manufacturer in {'agilent', 'hameg', 'hewlett-packard'}:
|
|
211
|
+
if dev.model == 'e3634a':
|
|
212
|
+
command_string = lexicon.power(dev.model, 'set remote')
|
|
213
|
+
common.send_command(pipeline, ser, dev, command_string)
|
|
214
|
+
|
|
215
|
+
command_string = lexicon.power(dev.model, 'read voltage', channel=dev.channel)
|
|
216
|
+
measured_voltage = common.atomic_send_command_read_response(pipeline, ser,
|
|
217
|
+
dev, command_string)
|
|
218
|
+
|
|
219
|
+
command_string = lexicon.power(dev.model, 'read current', channel=dev.channel)
|
|
220
|
+
measured_current = common.atomic_send_command_read_response(pipeline, ser,
|
|
221
|
+
dev, command_string)
|
|
222
|
+
try:
|
|
223
|
+
volt = float(measured_voltage)
|
|
224
|
+
curr = float(measured_current)
|
|
225
|
+
except ValueError:
|
|
226
|
+
volt = curr = None
|
|
227
|
+
|
|
228
|
+
# ignore readings if output is off
|
|
229
|
+
if dev.manufacturer == 'hameg':
|
|
230
|
+
command_string = lexicon.power(dev.model, 'check output', channel=dev.channel)
|
|
231
|
+
output_status = common.atomic_send_command_read_response(pipeline, ser,
|
|
232
|
+
dev, command_string)
|
|
233
|
+
if output_status == '0':
|
|
234
|
+
volt = curr = None
|
|
235
|
+
|
|
236
|
+
else:
|
|
237
|
+
logging.error('unknown power supply')
|
|
238
|
+
volt = curr = None
|
|
239
|
+
|
|
240
|
+
readings = [(volt, curr)]
|
|
241
|
+
|
|
242
|
+
return dev, readings
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
##############################################################################
|
|
246
|
+
# moved from common
|
|
247
|
+
##############################################################################
|
|
248
|
+
|
|
249
|
+
def initial_power_supply_check(settings, pipeline, psus, channels, psuset=False):
|
|
250
|
+
"""
|
|
251
|
+
Establishes RS232 communications with power supplies (as required).
|
|
252
|
+
Checks status of channel outputs and interlocks (inhibits).
|
|
253
|
+
|
|
254
|
+
--------------------------------------------------------------------------
|
|
255
|
+
args
|
|
256
|
+
settings : dictionary
|
|
257
|
+
contains core information about the test environment
|
|
258
|
+
pipeline : instance of class Production
|
|
259
|
+
contains all the queues through which the production pipeline
|
|
260
|
+
processes communicate
|
|
261
|
+
psus : dict
|
|
262
|
+
{port: ({port_config}, device_type, device_serial_number), ...}
|
|
263
|
+
contents of the cache filtered by hvpsu category
|
|
264
|
+
channels : list
|
|
265
|
+
contains instances of class Channel, one for each
|
|
266
|
+
power supply channel
|
|
267
|
+
psuset : bool
|
|
268
|
+
selects the error message depending on the caller
|
|
269
|
+
--------------------------------------------------------------------------
|
|
270
|
+
returns
|
|
271
|
+
channels : list
|
|
272
|
+
no explicit return, mutable type amended in place
|
|
273
|
+
--------------------------------------------------------------------------
|
|
274
|
+
"""
|
|
275
|
+
port_used = collections.defaultdict(int)
|
|
276
|
+
outstat = []
|
|
277
|
+
intstat = []
|
|
278
|
+
polstat = []
|
|
279
|
+
|
|
280
|
+
if settings['debug'] is None:
|
|
281
|
+
common.check_ports_accessible(psus, channels)
|
|
282
|
+
|
|
283
|
+
for dev in channels.copy():
|
|
284
|
+
# message = f'enabled: {common._lookup_decorative(settings["alias"], dev)}'
|
|
285
|
+
# common.log_with_colour(logging.INFO, message)
|
|
286
|
+
|
|
287
|
+
with serial.Serial(port=dev.port) as ser:
|
|
288
|
+
ser.apply_settings(dev.config)
|
|
289
|
+
|
|
290
|
+
# try to ensure consistent state
|
|
291
|
+
# clear FTDI output buffer state before sending
|
|
292
|
+
ser.reset_output_buffer()
|
|
293
|
+
# clear PSU state
|
|
294
|
+
if dev.model == '2614b':
|
|
295
|
+
command_string = lexicon.power(dev.model, 'terminator only',
|
|
296
|
+
channel=dev.channel)
|
|
297
|
+
common.send_command(pipeline, ser, dev, command_string)
|
|
298
|
+
# clear FTDI input buffer state
|
|
299
|
+
ser.reset_input_buffer()
|
|
300
|
+
# arbitrary settle time before proceeding
|
|
301
|
+
time.sleep(0.5)
|
|
302
|
+
|
|
303
|
+
# ensure serial port communication with PSU
|
|
304
|
+
# this is for ISEG SHQ only, on a per-PSU basis
|
|
305
|
+
if not port_used[dev.port]:
|
|
306
|
+
common.synchronise_psu(ser, pipeline, dev)
|
|
307
|
+
|
|
308
|
+
# check power supply output
|
|
309
|
+
# but don't do this if called from psuset.py, since the user
|
|
310
|
+
# of that script may be issuing a command to turn a
|
|
311
|
+
# power supply channel output on
|
|
312
|
+
if not psuset:
|
|
313
|
+
tmp_stat = report_output_status(ser, pipeline, dev)
|
|
314
|
+
if tmp_stat:
|
|
315
|
+
channels.remove(dev)
|
|
316
|
+
# outstat.append(report_output_status(ser, pipeline, dev))
|
|
317
|
+
|
|
318
|
+
# check interlock
|
|
319
|
+
# this is per-PSU on Keithley, per-channel on ISEG
|
|
320
|
+
if ((not port_used[dev.port] or dev.manufacturer == 'iseg')
|
|
321
|
+
and dev.manufacturer not in {'agilent', 'hameg'}):
|
|
322
|
+
intstat.append(common._report_interlock_status(ser, pipeline, dev))
|
|
323
|
+
|
|
324
|
+
if dev.manufacturer == 'iseg':
|
|
325
|
+
polstat.append(common._report_polarity_status(settings, ser, pipeline, dev, psuset))
|
|
326
|
+
|
|
327
|
+
ser.reset_input_buffer()
|
|
328
|
+
ser.reset_output_buffer()
|
|
329
|
+
|
|
330
|
+
port_used[dev.port] += 1
|
|
331
|
+
|
|
332
|
+
if any(outstat):
|
|
333
|
+
message = 'all power supply outputs must be on to proceed'
|
|
334
|
+
common.log_with_colour(logging.ERROR, message)
|
|
335
|
+
channels.clear()
|
|
336
|
+
|
|
337
|
+
if any(intstat):
|
|
338
|
+
message = 'all power supply interlocks must be inactive to proceed'
|
|
339
|
+
common.log_with_colour(logging.ERROR, message)
|
|
340
|
+
channels.clear()
|
|
341
|
+
|
|
342
|
+
if any(polstat):
|
|
343
|
+
if psuset:
|
|
344
|
+
message = 'set voltage should agree with polarity switch to proceed'
|
|
345
|
+
common.log_with_colour(logging.ERROR, message)
|
|
346
|
+
else:
|
|
347
|
+
message = 'all polarity switches must agree with --forwardbias to proceed'
|
|
348
|
+
common.log_with_colour(logging.ERROR, message)
|
|
349
|
+
|
|
350
|
+
channels.clear()
|
|
351
|
+
else:
|
|
352
|
+
message = '--debug: any connected power supplies will be ignored'
|
|
353
|
+
log_with_colour(logging.WARNING, message)
|
|
354
|
+
message = '--debug: IV data will generated internally'
|
|
355
|
+
log_with_colour(logging.WARNING, message)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def report_output_status(ser, pipeline, dev):
|
|
359
|
+
"""
|
|
360
|
+
Check that the output for the given power supply channel is on.
|
|
361
|
+
|
|
362
|
+
While the outputs may be switched on/off over RS232 for all the supported
|
|
363
|
+
power supplies except ISEG SHQ, this is generally used as a check to make
|
|
364
|
+
sure the user has configured the test environment correctly.
|
|
365
|
+
|
|
366
|
+
Values returned in variable output:
|
|
367
|
+
|
|
368
|
+
+------------------+-----------+---------------+---------------+
|
|
369
|
+
| dev.manufacturer | dev.model | output OFF | output ON |
|
|
370
|
+
+------------------+-----------+---------------+---------------+
|
|
371
|
+
| 'agilent' | 'e3634a' | '0' | '1' |
|
|
372
|
+
| 'agilent' | 'e3647a' | '0' | '1' |
|
|
373
|
+
| 'hameg' | 'hmp4040' | '0' | '1' |
|
|
374
|
+
| 'iseg' | 'shq' | '=OFF' | '=ON' |
|
|
375
|
+
| 'keithley' | '2410' | '0' | '1' |
|
|
376
|
+
| 'keithley' | '2614b' | '0.00000e+00' | '1.00000e+00' |
|
|
377
|
+
+------------------+-----------+---------------+---------------+
|
|
378
|
+
|
|
379
|
+
--------------------------------------------------------------------------
|
|
380
|
+
args
|
|
381
|
+
ser : serial.Serial
|
|
382
|
+
reference for serial port
|
|
383
|
+
pipeline : instance of class Production
|
|
384
|
+
contains all the queues through which the production pipeline
|
|
385
|
+
processes communicate
|
|
386
|
+
dev : instance of class Channel
|
|
387
|
+
contains details of a device and its serial port
|
|
388
|
+
--------------------------------------------------------------------------
|
|
389
|
+
returns
|
|
390
|
+
fail : bool
|
|
391
|
+
True if the output was found to be off, False otherwise
|
|
392
|
+
--------------------------------------------------------------------------
|
|
393
|
+
"""
|
|
394
|
+
fail = False
|
|
395
|
+
|
|
396
|
+
command_string = lexicon.power(dev.model, 'check output', channel=dev.channel)
|
|
397
|
+
output = common.atomic_send_command_read_response(pipeline, ser, dev, command_string)
|
|
398
|
+
|
|
399
|
+
if dev.manufacturer == 'iseg':
|
|
400
|
+
if '=ON' not in output:
|
|
401
|
+
# log_with_colour(logging.WARNING, f'{dev.ident}, output off')
|
|
402
|
+
fail = True
|
|
403
|
+
|
|
404
|
+
elif dev.manufacturer in {'agilent', 'hameg', 'keithley'}:
|
|
405
|
+
try:
|
|
406
|
+
outval = int(float(output))
|
|
407
|
+
except ValueError:
|
|
408
|
+
message = f'{dev.ident}, problem checking output'
|
|
409
|
+
log_with_colour(logging.ERROR, message)
|
|
410
|
+
fail = True
|
|
411
|
+
else:
|
|
412
|
+
if outval in {0, 1}:
|
|
413
|
+
if outval == 0:
|
|
414
|
+
# message = f'{dev.ident}, output off'
|
|
415
|
+
# log_with_colour(logging.WARNING, message)
|
|
416
|
+
fail = True
|
|
417
|
+
else:
|
|
418
|
+
message = f'{dev.ident}, problem checking output'
|
|
419
|
+
log_with_colour(logging.ERROR, message)
|
|
420
|
+
fail = True
|
|
421
|
+
|
|
422
|
+
return fail
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
##############################################################################
|
|
426
|
+
# main
|
|
427
|
+
##############################################################################
|
|
428
|
+
|
|
429
|
+
def main():
|
|
430
|
+
"""
|
|
431
|
+
monitor the voltage and current on power supplies contained in the cache file
|
|
432
|
+
created by detect.py
|
|
433
|
+
"""
|
|
434
|
+
# date and time to the nearest second when the script was started
|
|
435
|
+
session = common.timestamp_to_utc(time.time())
|
|
436
|
+
|
|
437
|
+
# initialise
|
|
438
|
+
settings = {
|
|
439
|
+
'alias': None,
|
|
440
|
+
'debug': None,
|
|
441
|
+
'time': None,
|
|
442
|
+
'hv_warning': None,
|
|
443
|
+
'hv_critical': None,
|
|
444
|
+
'lv_warning': None,
|
|
445
|
+
'lv_critical': None}
|
|
446
|
+
|
|
447
|
+
##########################################################################
|
|
448
|
+
# read command line arguments
|
|
449
|
+
##########################################################################
|
|
450
|
+
|
|
451
|
+
check_arguments(settings)
|
|
452
|
+
|
|
453
|
+
##########################################################################
|
|
454
|
+
# enable logging to file
|
|
455
|
+
##########################################################################
|
|
456
|
+
|
|
457
|
+
log = f'{session}_psuwatch.log'
|
|
458
|
+
logging.basicConfig(
|
|
459
|
+
filename=log,
|
|
460
|
+
level=logging.INFO,
|
|
461
|
+
format='%(asctime)s : %(levelname)s : %(message)s')
|
|
462
|
+
|
|
463
|
+
# enable logging to screen
|
|
464
|
+
console = logging.StreamHandler()
|
|
465
|
+
console.setLevel(logging.INFO)
|
|
466
|
+
formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(message)s')
|
|
467
|
+
|
|
468
|
+
console.setFormatter(formatter)
|
|
469
|
+
logging.getLogger('').addHandler(console)
|
|
470
|
+
|
|
471
|
+
# set logging time to UTC to match session timestamp
|
|
472
|
+
logging.Formatter.converter = time.gmtime
|
|
473
|
+
|
|
474
|
+
##########################################################################
|
|
475
|
+
# read cache
|
|
476
|
+
##########################################################################
|
|
477
|
+
|
|
478
|
+
# record the details of the power supplies being watched in the log
|
|
479
|
+
psus = common.cache_read(['hvpsu', 'lvpsu'], uselog=True)
|
|
480
|
+
channels = common.ports_to_channels(settings, psus)
|
|
481
|
+
|
|
482
|
+
##########################################################################
|
|
483
|
+
# set up resources for threads
|
|
484
|
+
##########################################################################
|
|
485
|
+
|
|
486
|
+
class Production:
|
|
487
|
+
"""
|
|
488
|
+
Queues and locks to support threaded operation.
|
|
489
|
+
|
|
490
|
+
RS232 will not tolerate concurrent access. portaccess is used to
|
|
491
|
+
prevent more than one thread trying to write to the same RS232 port at
|
|
492
|
+
the same time for multi-channel power supplies. For simplicity, locks
|
|
493
|
+
are created for all power supplies, even if they only have a single
|
|
494
|
+
channel.
|
|
495
|
+
"""
|
|
496
|
+
portaccess = {
|
|
497
|
+
port: threading.Lock()
|
|
498
|
+
for port in {channel.port for channel in channels}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
pipeline = Production()
|
|
502
|
+
|
|
503
|
+
##########################################################################
|
|
504
|
+
# Check status of outputs and interlock (inhibit) on all power supplies
|
|
505
|
+
##########################################################################
|
|
506
|
+
|
|
507
|
+
initial_power_supply_check(settings, pipeline, psus, channels)
|
|
508
|
+
|
|
509
|
+
##########################################################################
|
|
510
|
+
# monitor power supply channels
|
|
511
|
+
##########################################################################
|
|
512
|
+
|
|
513
|
+
_rpvi_pf = functools.partial(read_psu_vi, pipeline=pipeline)
|
|
514
|
+
|
|
515
|
+
if channels:
|
|
516
|
+
sample_period_seconds = settings['time']
|
|
517
|
+
timestamp_mono = time.monotonic()
|
|
518
|
+
|
|
519
|
+
while True:
|
|
520
|
+
data = []
|
|
521
|
+
with cf.ThreadPoolExecutor() as executor:
|
|
522
|
+
psu_reading = (executor.submit(_rpvi_pf, channel) for channel in channels)
|
|
523
|
+
for future in cf.as_completed(psu_reading):
|
|
524
|
+
data.append(future.result())
|
|
525
|
+
|
|
526
|
+
# this will sort by serial number then channel order
|
|
527
|
+
data.sort()
|
|
528
|
+
|
|
529
|
+
# determine text and colour to print for each reading
|
|
530
|
+
line = (format_reading(settings, common.ANSIColours, psu_info) for psu_info in data)
|
|
531
|
+
logging.info(' | '.join(line))
|
|
532
|
+
|
|
533
|
+
timestamp_mono = common.rate_limit(
|
|
534
|
+
timestamp_mono, sample_period_seconds
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
##############################################################################
|
|
539
|
+
if __name__ == '__main__':
|
|
540
|
+
main()
|