mmcb-rs232-avt 1.0.19__py3-none-any.whl → 1.1.37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mmcb_rs232_avt-1.1.37.dist-info/METADATA +210 -0
- mmcb_rs232_avt-1.1.37.dist-info/RECORD +26 -0
- {mmcb_rs232_avt-1.0.19.dist-info → mmcb_rs232_avt-1.1.37.dist-info}/WHEEL +1 -1
- mmcb_rs232_avt-1.1.37.dist-info/entry_points.txt +12 -0
- mmcb_rs232_avt-1.1.37.dist-info/top_level.txt +1 -0
- mmcbrs232/MDP.py +35 -0
- mmcbrs232/broker_and_workers.py +582 -0
- {mmcb_rs232 → mmcbrs232}/common.py +57 -35
- {mmcb_rs232 → mmcbrs232}/detect.py +459 -137
- mmcbrs232/dmm.py +220 -0
- {mmcb_rs232 → mmcbrs232}/dmm_interface.py +25 -4
- {mmcb_rs232 → mmcbrs232}/iv.py +36 -10
- {mmcb_rs232 → mmcbrs232}/lexicon.py +12 -0
- mmcbrs232/liveplot.py +335 -0
- mmcbrs232/mdbroker.py +311 -0
- mmcbrs232/mdcliapi.py +110 -0
- mmcbrs232/mdclientlib.py +266 -0
- mmcbrs232/mdwrkapi.py +183 -0
- {mmcb_rs232 → mmcbrs232}/psuset.py +96 -64
- {mmcb_rs232 → mmcbrs232}/psustat.py +197 -16
- {mmcb_rs232 → mmcbrs232}/psuwatch.py +13 -8
- {mmcb_rs232 → mmcbrs232}/sequence.py +2 -3
- {mmcb_rs232 → mmcbrs232}/ult80.py +123 -79
- mmcbrs232/zhelpers.py +58 -0
- mmcbrs232/zpsustat.py +72 -0
- mmcb_rs232/dmm.py +0 -126
- mmcb_rs232_avt-1.0.19.dist-info/METADATA +0 -62
- mmcb_rs232_avt-1.0.19.dist-info/RECORD +0 -17
- mmcb_rs232_avt-1.0.19.dist-info/entry_points.txt +0 -10
- mmcb_rs232_avt-1.0.19.dist-info/top_level.txt +0 -1
- {mmcb_rs232 → mmcbrs232}/__init__.py +0 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Manage access to serial ports for MMCB PID control loops which are running
|
|
4
|
+
asynchronously, and will compete for access to serial ports.
|
|
5
|
+
|
|
6
|
+
There is one PSU/instrument for each serial port, so use one worker per port
|
|
7
|
+
to serialise requests since an individual RS232 serial port will not tolerate
|
|
8
|
+
concurrent access. Concurrent access to different serial ports is allowable.
|
|
9
|
+
|
|
10
|
+
The clients on the left hand side represent PID loop monitoring/control. A
|
|
11
|
+
broker routes traffic between the clients and workers.
|
|
12
|
+
|
|
13
|
+
This script runs the broker and spawns one worker per serial port as based on
|
|
14
|
+
the cached equipment configuration.
|
|
15
|
+
|
|
16
|
+
+--------+ +--------------+
|
|
17
|
+
| | | worker |
|
|
18
|
+
| o----| /dev/ttyUSB0 |
|
|
19
|
+
| | +--------------+
|
|
20
|
+
+----------+ | |
|
|
21
|
+
| client 1 o----o | +--------------+
|
|
22
|
+
+----------+ | | | worker |
|
|
23
|
+
| o----| /dev/ttyUSB1 |
|
|
24
|
+
+----------+ | | +--------------+
|
|
25
|
+
| client 2 +----o broker |
|
|
26
|
+
+----------+ | | ......
|
|
27
|
+
| |
|
|
28
|
+
+----------+ | | +--------------+
|
|
29
|
+
| client 3 +----o | | worker |
|
|
30
|
+
+----------+ | o----| /dev/ttyUSB6 |
|
|
31
|
+
+--------+ +--------------+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import fcntl
|
|
36
|
+
import multiprocessing as mp
|
|
37
|
+
import queue
|
|
38
|
+
import threading
|
|
39
|
+
import time
|
|
40
|
+
|
|
41
|
+
import serial
|
|
42
|
+
import zmq
|
|
43
|
+
|
|
44
|
+
from mmcbrs232 import common
|
|
45
|
+
from mmcbrs232 import mdbroker
|
|
46
|
+
from mmcbrs232 import psuset
|
|
47
|
+
from mmcbrs232 import psustat
|
|
48
|
+
from mmcbrs232 import ult80 as chiller
|
|
49
|
+
from mmcbrs232 import mdwrkapi
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
###############################################################################
|
|
53
|
+
# command line handler
|
|
54
|
+
###############################################################################
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_arguments():
|
|
58
|
+
"""
|
|
59
|
+
handle command line options
|
|
60
|
+
|
|
61
|
+
--------------------------------------------------------------------------
|
|
62
|
+
args : none
|
|
63
|
+
--------------------------------------------------------------------------
|
|
64
|
+
returns
|
|
65
|
+
settings : dictionary
|
|
66
|
+
contains core information about the test environment
|
|
67
|
+
--------------------------------------------------------------------------
|
|
68
|
+
"""
|
|
69
|
+
parser = argparse.ArgumentParser(
|
|
70
|
+
description=(
|
|
71
|
+
'ZeroMQ server (broker and workers) for the ATLAS Pixels '
|
|
72
|
+
'multi-module cycling box serial port devices. This server '
|
|
73
|
+
'manages concurrent access to serial port resources for all '
|
|
74
|
+
'attached clients. Commands line scripts emulated are psuset, '
|
|
75
|
+
'psustat and ult80. WARNING: ANY attached client has the authority '
|
|
76
|
+
'to change ANY power supply or chiller parameter at ANY time. '
|
|
77
|
+
'Clients (PID control loops) running concurrently must ensure that '
|
|
78
|
+
'they only modify their own resources, and not those of other '
|
|
79
|
+
'clients.'
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
'-v', '--verbose',
|
|
84
|
+
action='store_true',
|
|
85
|
+
help='Enable logging.'
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
'-n', '--nolock',
|
|
89
|
+
action='store_true',
|
|
90
|
+
help=(
|
|
91
|
+
'Disable the use of the global rs232 lock. '
|
|
92
|
+
'For safety this script will use the global rs232 lock by default. '
|
|
93
|
+
'This lock is a coarse measure that should prevent concurrent '
|
|
94
|
+
'serial port access, even to different ports, by this server and '
|
|
95
|
+
'other command line scripts that may access serial port resources. '
|
|
96
|
+
'When running multiple concurrent asynchronous PID control '
|
|
97
|
+
'scripts, using -n allows optimal sharing of serial port resources '
|
|
98
|
+
'between those scripts.'
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return parser.parse_args()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
###############################################################################
|
|
106
|
+
# support for commands: psuset, psustat, ult80
|
|
107
|
+
###############################################################################
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def default():
|
|
111
|
+
"""
|
|
112
|
+
---------------------------------------------------------------------------
|
|
113
|
+
args : none
|
|
114
|
+
---------------------------------------------------------------------------
|
|
115
|
+
returns : dict
|
|
116
|
+
---------------------------------------------------------------------------
|
|
117
|
+
"""
|
|
118
|
+
return {
|
|
119
|
+
'alias': None,
|
|
120
|
+
'channel': None,
|
|
121
|
+
'current_limit': None,
|
|
122
|
+
'debug': None,
|
|
123
|
+
'decimal_places': 2,
|
|
124
|
+
'forwardbias': False,
|
|
125
|
+
'immediate': False,
|
|
126
|
+
'manufacturer': None,
|
|
127
|
+
'model': None,
|
|
128
|
+
'on': None,
|
|
129
|
+
'peltier': False,
|
|
130
|
+
'port': None,
|
|
131
|
+
'quiet': False,
|
|
132
|
+
'rear': None,
|
|
133
|
+
'reset': False,
|
|
134
|
+
'serial': None,
|
|
135
|
+
'settlingtime': 0.5,
|
|
136
|
+
'verbose': None,
|
|
137
|
+
'voltage': None,
|
|
138
|
+
'voltspersecond': 10
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def psuset_communicate(settings, pipeline, channel, setpsu):
|
|
143
|
+
"""
|
|
144
|
+
Send command to power supply, do not perform checking of set values..
|
|
145
|
+
|
|
146
|
+
---------------------------------------------------------------------------
|
|
147
|
+
args
|
|
148
|
+
settings : dict
|
|
149
|
+
pipeline : class Pipeline
|
|
150
|
+
channel : instance of class Channel
|
|
151
|
+
setpsu : dict
|
|
152
|
+
lookup table containing functions to call for high/low-voltage PSUs
|
|
153
|
+
---------------------------------------------------------------------------
|
|
154
|
+
returns
|
|
155
|
+
success : bool
|
|
156
|
+
---------------------------------------------------------------------------
|
|
157
|
+
"""
|
|
158
|
+
with serial.Serial(port=channel.port) as ser:
|
|
159
|
+
ser.apply_settings(channel.config)
|
|
160
|
+
ser.reset_input_buffer()
|
|
161
|
+
ser.reset_output_buffer()
|
|
162
|
+
success = setpsu[channel.category](
|
|
163
|
+
settings, pipeline, ser, channel, slow=False
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return success
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def handle_psuset_request(pipeline, command, psus, channels):
|
|
170
|
+
"""
|
|
171
|
+
Process psuset command using the existing psuset code.
|
|
172
|
+
"""
|
|
173
|
+
# common.unique only removes items from the top level dict/list but does
|
|
174
|
+
# not change the contents, hence the shallow copy.
|
|
175
|
+
local_psus = psus.copy()
|
|
176
|
+
local_channels = channels.copy()
|
|
177
|
+
|
|
178
|
+
# -------------------------------------------------------------------------
|
|
179
|
+
# parse psuset command, parameters returned in settings
|
|
180
|
+
# -------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
settings = default()
|
|
183
|
+
psuset.check_arguments(settings, command)
|
|
184
|
+
|
|
185
|
+
# -------------------------------------------------------------------------
|
|
186
|
+
# basic filtering
|
|
187
|
+
# -------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
single = common.unique(settings, local_psus, local_channels)
|
|
190
|
+
|
|
191
|
+
if not single:
|
|
192
|
+
# could not identify single PSU channel from supplied arguments
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
channel = local_channels[0]
|
|
197
|
+
except IndexError:
|
|
198
|
+
# supplied arguments could not be matched to any PSU channel
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
# -------------------------------------------------------------------------
|
|
202
|
+
# single PSU channel identified: communicate with PSU
|
|
203
|
+
# -------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
setpsu = {'lvpsu': psuset.configure_lvpsu, 'hvpsu': psuset.configure_hvpsu}
|
|
206
|
+
|
|
207
|
+
return global_lock_wrapper(
|
|
208
|
+
pipeline.use_global_serial_port_lock,
|
|
209
|
+
lambda: psuset_communicate(settings, pipeline, channel, setpsu)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def handle_psustat_request(pipeline, command, psus, channels):
|
|
214
|
+
"""
|
|
215
|
+
Mutable variables command and psus must never be changed.
|
|
216
|
+
|
|
217
|
+
--------------------------------------------------------------------------
|
|
218
|
+
args
|
|
219
|
+
pipeline :
|
|
220
|
+
command : str
|
|
221
|
+
psus : dict
|
|
222
|
+
channels : list
|
|
223
|
+
t0 : float
|
|
224
|
+
--------------------------------------------------------------------------
|
|
225
|
+
returns
|
|
226
|
+
--------------------------------------------------------------------------
|
|
227
|
+
"""
|
|
228
|
+
# common.unique only removes items from the top level dict/list but does
|
|
229
|
+
# not change the contents, hence the shallow copy.
|
|
230
|
+
local_psus = psus.copy()
|
|
231
|
+
local_channels = channels.copy()
|
|
232
|
+
|
|
233
|
+
settings = {
|
|
234
|
+
'alias': None,
|
|
235
|
+
'channel': None,
|
|
236
|
+
'manufacturer': None,
|
|
237
|
+
'model': None,
|
|
238
|
+
'port': None,
|
|
239
|
+
'serial': None,
|
|
240
|
+
'time': None,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# this call returns configuration in settings
|
|
244
|
+
psustat.check_arguments(settings, command)
|
|
245
|
+
|
|
246
|
+
# does the user-supplied command identify a single PSU channel?
|
|
247
|
+
single = common.unique(settings, local_psus, local_channels)
|
|
248
|
+
|
|
249
|
+
# If filter is False, the supplied command did not attempt to identify a
|
|
250
|
+
# single channel, and therefore should be ignored. FIXME improve this test
|
|
251
|
+
if settings['filter'] and not single:
|
|
252
|
+
print('PSUSTAT rejected')
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
return global_lock_wrapper(
|
|
256
|
+
pipeline.use_global_serial_port_lock,
|
|
257
|
+
lambda: psustat.power_supply_channel_status(
|
|
258
|
+
settings, pipeline, local_psus, local_channels, common.ANSIColours
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def ult80_communicate(args):
|
|
264
|
+
"""
|
|
265
|
+
Send command to the ULT80 chiller.
|
|
266
|
+
|
|
267
|
+
--------------------------------------------------------------------------
|
|
268
|
+
args
|
|
269
|
+
args : <class 'argparse.Namespace'>
|
|
270
|
+
--------------------------------------------------------------------------
|
|
271
|
+
returns
|
|
272
|
+
rval : float or None
|
|
273
|
+
--------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
ult80 = chiller.Ult80()
|
|
277
|
+
rval = None
|
|
278
|
+
|
|
279
|
+
if args.read_internal_temperature:
|
|
280
|
+
rval = ult80.read_internal_temperature()
|
|
281
|
+
elif args.read_setpoint:
|
|
282
|
+
rval = ult80.read_setpoint_control_point()
|
|
283
|
+
elif args.set_setpoint:
|
|
284
|
+
rval = ult80.set_setpoint_control_point(float(args.set_setpoint[0]))
|
|
285
|
+
|
|
286
|
+
return rval
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def handle_ult80_request(pipeline, command, psus, channels):
|
|
290
|
+
"""
|
|
291
|
+
Process psuset command using the existing psuset code.
|
|
292
|
+
|
|
293
|
+
Protect the serial port interaction using the global serial port lock if
|
|
294
|
+
requested.
|
|
295
|
+
|
|
296
|
+
--------------------------------------------------------------------------
|
|
297
|
+
args
|
|
298
|
+
pipeline : class Production
|
|
299
|
+
command : str
|
|
300
|
+
e.g. 'ult80 -i', 'ult80 -r' or 'ult80 -s <value>'
|
|
301
|
+
psus : dict
|
|
302
|
+
Details of the PSU for this serial port (this dict will only have
|
|
303
|
+
a single entry)
|
|
304
|
+
channels : list of class Channel
|
|
305
|
+
details of the PSU channels for this serial port
|
|
306
|
+
--------------------------------------------------------------------------
|
|
307
|
+
returns
|
|
308
|
+
--------------------------------------------------------------------------
|
|
309
|
+
"""
|
|
310
|
+
return global_lock_wrapper(
|
|
311
|
+
pipeline.use_global_serial_port_lock,
|
|
312
|
+
lambda: ult80_communicate(chiller.check_arguments(command))
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
###############################################################################
|
|
317
|
+
# workers
|
|
318
|
+
###############################################################################
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def sp_worker(pipeline, port, psus, channels):
|
|
322
|
+
"""
|
|
323
|
+
Serial port worker for a single specific serial port.
|
|
324
|
+
|
|
325
|
+
This will not exit until it receives something from the broker.
|
|
326
|
+
|
|
327
|
+
psuset, lvpsu
|
|
328
|
+
configure_lvpsu(settings, pipeline, ser, dev)
|
|
329
|
+
order: reset, current_limit, output on/off, voltage
|
|
330
|
+
psuset, hvpsu
|
|
331
|
+
configure_hvpsu(settings, pipeline, ser, dev)
|
|
332
|
+
order: reset, current_limit
|
|
333
|
+
|
|
334
|
+
---------------------------------------------------------------------------
|
|
335
|
+
args
|
|
336
|
+
pipeline : class Pipeline
|
|
337
|
+
port : string
|
|
338
|
+
e.g. '/dev/ttyUSB0'
|
|
339
|
+
psus : dict
|
|
340
|
+
Details of the PSU for this serial port (this dict will only have
|
|
341
|
+
a single entry)
|
|
342
|
+
channels : list of class Channel
|
|
343
|
+
details of the PSU channels for this serial port
|
|
344
|
+
---------------------------------------------------------------------------
|
|
345
|
+
returns : none
|
|
346
|
+
---------------------------------------------------------------------------
|
|
347
|
+
"""
|
|
348
|
+
worker = mdwrkapi.MajorDomoWorker(
|
|
349
|
+
'tcp://localhost:5556', bytes(port, encoding='utf-8'), pipeline.verbose
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
lut = {
|
|
353
|
+
'psuset': handle_psuset_request,
|
|
354
|
+
'psustat': handle_psustat_request,
|
|
355
|
+
'ult80': handle_ult80_request,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
reply = None
|
|
359
|
+
while True:
|
|
360
|
+
try:
|
|
361
|
+
pipeline.terminate[port].get_nowait()
|
|
362
|
+
except queue.Empty:
|
|
363
|
+
pass
|
|
364
|
+
else:
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
#######################################################################
|
|
368
|
+
# request : list
|
|
369
|
+
# e.g. [b'psuset -10 --limit 0.005 --on --channel 1 --serial 103807']
|
|
370
|
+
#######################################################################
|
|
371
|
+
|
|
372
|
+
request = worker.recv(reply)
|
|
373
|
+
if request is None:
|
|
374
|
+
break
|
|
375
|
+
|
|
376
|
+
#######################################################################
|
|
377
|
+
# process and generate reply
|
|
378
|
+
#######################################################################
|
|
379
|
+
|
|
380
|
+
command = next(r.decode() for r in request)
|
|
381
|
+
cli = command.split(' ')[0].strip()
|
|
382
|
+
|
|
383
|
+
t0 = time.monotonic()
|
|
384
|
+
try:
|
|
385
|
+
rval = lut[cli](pipeline, command, psus, channels)
|
|
386
|
+
except KeyError:
|
|
387
|
+
print('unknown command')
|
|
388
|
+
rval = None
|
|
389
|
+
|
|
390
|
+
reply = [
|
|
391
|
+
bytes(
|
|
392
|
+
f'{command} : {rval} ({time.monotonic() - t0:.6f} s)',
|
|
393
|
+
encoding='utf-8'
|
|
394
|
+
)
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
###############################################################################
|
|
399
|
+
# broker
|
|
400
|
+
###############################################################################
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def sp_broker(pipeline):
|
|
404
|
+
"""
|
|
405
|
+
Serial port majordomo broker.
|
|
406
|
+
|
|
407
|
+
---------------------------------------------------------------------------
|
|
408
|
+
args
|
|
409
|
+
pipeline : class Pipeline
|
|
410
|
+
---------------------------------------------------------------------------
|
|
411
|
+
returns : none
|
|
412
|
+
---------------------------------------------------------------------------
|
|
413
|
+
"""
|
|
414
|
+
broker = mdbroker.MajorDomoBroker(pipeline.verbose)
|
|
415
|
+
try:
|
|
416
|
+
broker.bind('tcp://*:5556')
|
|
417
|
+
except zmq.error.ZMQError:
|
|
418
|
+
print('broker could not bind to port, server may already be running')
|
|
419
|
+
else:
|
|
420
|
+
broker.mediate()
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
###############################################################################
|
|
424
|
+
# provision for conditional use of rs232 global lock
|
|
425
|
+
###############################################################################
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def global_lock_wrapper(wrap, func):
|
|
429
|
+
"""
|
|
430
|
+
Conditionally apply the global serial port lock.
|
|
431
|
+
|
|
432
|
+
The lock is enabled by default and makes this server play nicely with
|
|
433
|
+
other command line scripts that access serial ports directly. However, this
|
|
434
|
+
prevents any exploitation of safe concurrency, and the mean response latency
|
|
435
|
+
may substantially increase depending on the access pattern.
|
|
436
|
+
|
|
437
|
+
---------------------------------------------------------------------------
|
|
438
|
+
args
|
|
439
|
+
wrap : bool
|
|
440
|
+
func : callable
|
|
441
|
+
---------------------------------------------------------------------------
|
|
442
|
+
returns
|
|
443
|
+
rval : returned result from func()
|
|
444
|
+
---------------------------------------------------------------------------
|
|
445
|
+
"""
|
|
446
|
+
rval = None
|
|
447
|
+
|
|
448
|
+
if wrap:
|
|
449
|
+
with open(common.RS232_LOCK_GLOBAL, 'a') as lock_file:
|
|
450
|
+
|
|
451
|
+
# acquire the RS232 global lock
|
|
452
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
|
453
|
+
|
|
454
|
+
rval = func()
|
|
455
|
+
|
|
456
|
+
# release the RS232 global lock
|
|
457
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
458
|
+
else:
|
|
459
|
+
rval = func()
|
|
460
|
+
|
|
461
|
+
return rval
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
###############################################################################
|
|
465
|
+
# main
|
|
466
|
+
###############################################################################
|
|
467
|
+
|
|
468
|
+
def main():
|
|
469
|
+
"""
|
|
470
|
+
"""
|
|
471
|
+
args = check_arguments()
|
|
472
|
+
settings = default()
|
|
473
|
+
|
|
474
|
+
###########################################################################
|
|
475
|
+
# read hardware configuration from cache
|
|
476
|
+
#
|
|
477
|
+
# psus = {
|
|
478
|
+
# '/dev/ttyUSB6': (
|
|
479
|
+
# {
|
|
480
|
+
# 'baudrate': 9600, 'bytesize': 8, 'parity': 'N',
|
|
481
|
+
# 'stopbits': 1,'xonxoff': False, 'dsrdtr': False,
|
|
482
|
+
# 'rtscts': False, 'timeout': 1, 'write_timeout': 1,
|
|
483
|
+
# 'inter_byte_timeout': None
|
|
484
|
+
# },
|
|
485
|
+
# 'chiller', None, 'ult80', 'thermoneslab',[''], 0.01
|
|
486
|
+
# ),
|
|
487
|
+
# ...
|
|
488
|
+
# }
|
|
489
|
+
#
|
|
490
|
+
# channels = [
|
|
491
|
+
# Channel(
|
|
492
|
+
# port="/dev/ttyUSB3",
|
|
493
|
+
# config={
|
|
494
|
+
# 'baudrate': 9600, 'bytesize': 8, 'parity': 'N',
|
|
495
|
+
# 'stopbits': 1,
|
|
496
|
+
# 'xonxoff': False, 'dsrdtr': False, 'rtscts': True,
|
|
497
|
+
# 'timeout': 1, 'write_timeout': 1,
|
|
498
|
+
# 'inter_byte_timeout': None
|
|
499
|
+
# },
|
|
500
|
+
# serial_number="103807", model="hmp4040", manufacturer="hameg",
|
|
501
|
+
# channel="1", category="lvpsu", release_delay=0.026,
|
|
502
|
+
# ident="hmp4040 103807 1"
|
|
503
|
+
# ),
|
|
504
|
+
# ...
|
|
505
|
+
# ]
|
|
506
|
+
#
|
|
507
|
+
###########################################################################
|
|
508
|
+
|
|
509
|
+
psus = common.cache_read(['hvpsu', 'lvpsu', 'chiller'])
|
|
510
|
+
channels = common.ports_to_channels(settings, psus)
|
|
511
|
+
ports = sorted(psus)
|
|
512
|
+
|
|
513
|
+
###########################################################################
|
|
514
|
+
# set up hardware
|
|
515
|
+
###########################################################################
|
|
516
|
+
|
|
517
|
+
class Pipeline:
|
|
518
|
+
terminate = {port: mp.Queue() for port in ports}
|
|
519
|
+
verbose = args.verbose
|
|
520
|
+
portaccess = {
|
|
521
|
+
port: threading.Lock()
|
|
522
|
+
for port in ports
|
|
523
|
+
}
|
|
524
|
+
use_global_serial_port_lock = not args.nolock
|
|
525
|
+
|
|
526
|
+
pipeline = Pipeline()
|
|
527
|
+
|
|
528
|
+
###########################################################################
|
|
529
|
+
# Check serial ports as listed in the cache are reachable. For each port
|
|
530
|
+
# clear buffers and perform basic setup.
|
|
531
|
+
###########################################################################
|
|
532
|
+
|
|
533
|
+
global_lock_wrapper(
|
|
534
|
+
pipeline.use_global_serial_port_lock,
|
|
535
|
+
lambda: common.initial_power_supply_check(
|
|
536
|
+
settings, pipeline, psus, channels, True, False,
|
|
537
|
+
)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
###########################################################################
|
|
541
|
+
# set up broker and one worker per serial port
|
|
542
|
+
###########################################################################
|
|
543
|
+
|
|
544
|
+
workers = {}
|
|
545
|
+
for port in ports:
|
|
546
|
+
|
|
547
|
+
psu_for_port = {k: v for k, v in psus.items() if k == port}
|
|
548
|
+
channels_for_port = [c for c in channels if c.port == port]
|
|
549
|
+
|
|
550
|
+
workers[port] = threading.Thread(
|
|
551
|
+
target=sp_worker,
|
|
552
|
+
args=(
|
|
553
|
+
pipeline, port, psu_for_port, channels_for_port,
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
broker = threading.Thread(target=sp_broker, args=(pipeline, ), daemon=True)
|
|
558
|
+
|
|
559
|
+
###########################################################################
|
|
560
|
+
# launch threads
|
|
561
|
+
###########################################################################
|
|
562
|
+
|
|
563
|
+
broker.start()
|
|
564
|
+
|
|
565
|
+
for worker in workers.values():
|
|
566
|
+
worker.start()
|
|
567
|
+
|
|
568
|
+
###########################################################################
|
|
569
|
+
# terminate threads
|
|
570
|
+
#
|
|
571
|
+
# Kill the worker threads one-by-one. The broker daemon thread will exit
|
|
572
|
+
# automatically when it's the only thing left running.
|
|
573
|
+
###########################################################################
|
|
574
|
+
|
|
575
|
+
# for port, worker in workers.items():
|
|
576
|
+
# pipeline.terminate[port].put(None)
|
|
577
|
+
# worker.join()
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
###############################################################################
|
|
581
|
+
if __name__ == '__main__':
|
|
582
|
+
main()
|