mmcb-rs232-avt 1.0.20__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.
@@ -10,8 +10,11 @@ If this turns out not to be the case, we can read the list of available FTDI
10
10
  serial port devices, then exclude any serial ports found in cache.json, then
11
11
  leave the user to choose which of the remaining serial ports to use.
12
12
 
13
- Refer to the following for RS232 command sequences:
14
- https://www.atecorp.com/atecorp/media/pdfs/data-sheets/neslab-ult-80-95_manual.pdf
13
+ Refer to the following for RS232 command sequences (pp.25-28):
14
+ https://www.atecorp.com/getmedia/038930d4-42ed-4f12-bfda-a37e08d48bac/
15
+ neslab-ult-80-95_manual_1.pdf
16
+ https://gitlab.ph.liv.ac.uk/avt/atlas-itk-pmmcb/-/blob/main/reference/
17
+ thermoneslab/neslab-ult-80-95_manual.pdf
15
18
 
16
19
  Also see 'setup loop' starting on page 10 to enable RS232.
17
20
 
@@ -62,24 +65,27 @@ Stor (store changes)
62
65
  """
63
66
 
64
67
  import argparse
65
- import sys
68
+ import fcntl
69
+ import threading
70
+ import weakref
66
71
 
67
72
  import serial
68
- import serial.tools.list_ports as stlp
69
73
 
70
- from mmcb_rs232 import common
74
+ from mmcbrs232 import common
71
75
 
72
76
 
73
77
  ##############################################################################
74
78
  # command line option handler
75
79
  ##############################################################################
76
80
 
77
- def check_arguments():
81
+
82
+ def check_arguments(command=None):
78
83
  """
79
84
  Handle command line options.
80
85
 
81
86
  --------------------------------------------------------------------------
82
- args : none
87
+ args
88
+ command : str
83
89
  --------------------------------------------------------------------------
84
90
  returns : class argparse.ArgumentParser
85
91
  --------------------------------------------------------------------------
@@ -102,50 +108,47 @@ def check_arguments():
102
108
  help='Set Setpoint (control point).',
103
109
  default=None)
104
110
 
105
- return parser.parse_args()
111
+ parser.add_argument(
112
+ '-q', '--quiet',
113
+ action='store_true',
114
+ help='Only output the requested raw value, and no other text.')
115
+
116
+ if command is None:
117
+ args = parser.parse_args()
118
+ else:
119
+ args = parser.parse_args(
120
+ command.strip().partition(' ')[-1].split(' ')
121
+ )
122
+
123
+
124
+ return args
106
125
 
107
126
 
108
127
  ##############################################################################
109
- # detect ult80 serial port and basic operation
128
+ # data structures
110
129
  ##############################################################################
111
130
 
112
- def find_ult80(port, config):
113
- """
114
- Return serial port configuration if the serial port was successfully
115
- opened.
116
131
 
117
- --------------------------------------------------------------------------
118
- args
119
- port : string
120
- port name
121
- config : dict
122
- serial port configuration
123
- --------------------------------------------------------------------------
124
- returns
125
- retval :
126
- settings : dict (if port could be opened) or None otherwise
127
- Note that the dictionary does not contain the serial port
128
- identifier.
129
- --------------------------------------------------------------------------
132
+ class Production:
133
+ """
134
+ Locks to support threaded operation in the underlying library code (1 lock
135
+ per serial port).
130
136
  """
131
- ser = serial.Serial()
132
- ser.apply_settings(config)
133
- ser.port = port
134
-
135
- try:
136
- ser.open()
137
- except (OSError, serial.SerialException):
138
- sys.exit(f'could not open port {port}, exiting.')
139
- else:
140
- settings = ser.get_settings()
141
- ser.close()
142
-
143
- return settings
144
137
 
138
+ def __init__(self, instrument_channels):
139
+ """
140
+ -----------------------------------------------------------------------
141
+ args
142
+ instrument_channels : list of <class 'mmcbrs232.common.Channel'>
143
+ -----------------------------------------------------------------------
144
+ returns : none
145
+ -----------------------------------------------------------------------
146
+ """
147
+ self.portaccess = {
148
+ port: threading.Lock()
149
+ for port in {channel.port for channel in instrument_channels}
150
+ }
145
151
 
146
- ##############################################################################
147
- # data structures
148
- ##############################################################################
149
152
 
150
153
  class Ult80:
151
154
  """
@@ -155,8 +158,62 @@ class Ult80:
155
158
  _address = 0x0001
156
159
  _nbits = 16
157
160
 
158
- def __init__(self, ser):
159
- self.ser = ser
161
+ _cached_chillers = common.cache_read({'chiller'})
162
+
163
+ _channels = []
164
+ for port, details in _cached_chillers.items():
165
+ (
166
+ _config,
167
+ _device_type,
168
+ _serial_number,
169
+ _model,
170
+ _manufacturer,
171
+ _device_channels,
172
+ _release_delay,
173
+ ) = details
174
+
175
+ for device_channel in _device_channels:
176
+ _channels.append(
177
+ common.Channel(
178
+ port,
179
+ _config,
180
+ _serial_number,
181
+ _model,
182
+ _manufacturer,
183
+ device_channel,
184
+ _device_type,
185
+ _release_delay,
186
+ None,
187
+ )
188
+ )
189
+
190
+ _pipeline = Production(_channels)
191
+
192
+ try:
193
+ _channel = _channels[0]
194
+ except IndexError:
195
+ pass
196
+
197
+ def __init__(self):
198
+ try:
199
+ self.ser = serial.Serial(port=self._channel.port)
200
+ except OSError:
201
+ self.init_failed = True
202
+ else:
203
+ self.init_failed = False
204
+ self._finalizer = weakref.finalize(self, self.ser.close)
205
+ self.ser.apply_settings(self._channel.config)
206
+ self.ser.reset_input_buffer()
207
+ self.ser.reset_output_buffer()
208
+
209
+ def remove(self):
210
+ """manual garbage collection: close serial port"""
211
+ self._finalizer()
212
+
213
+ @property
214
+ def removed(self):
215
+ """check (indirectly) if the serial port has been closed"""
216
+ return not self._finalizer.alive
160
217
 
161
218
  ##########################################################################
162
219
  # public functions
@@ -437,62 +494,49 @@ def main():
437
494
  """
438
495
  args = check_arguments()
439
496
 
440
- # obtain available ports
441
- all_ports = {com.device for com in stlp.comports() if common.rs232_port_is_valid(com)}
442
- if not all_ports:
443
- sys.exit('No usable serial ports found')
444
-
445
- # find ports already allocated to other devices
446
- cache = common.cache_read()
447
-
448
- # If a port has None in its settings field, then it was not identified by
449
- # detect.py, and can therefore still be considered for use by this
450
- # script.
451
- identified_ports_in_cache = {k for k, v in cache.items() if v[0] is not None}
452
-
453
- # usable ports for this script
454
- available_ports = all_ports - identified_ports_in_cache
497
+ with open(common.RS232_LOCK_GLOBAL, 'a') as lock_file:
455
498
 
456
- if len(available_ports) > 1:
457
- sys.exit(f'Too many ports to choose from: {available_ports}')
458
- elif not available_ports:
459
- sys.exit('No usable serial ports available')
499
+ # acquire the RS232 global lock
500
+ fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
460
501
 
461
- port = available_ports.pop()
462
- print(f'using port {port}')
463
- config = {'baudrate': 9600, 'bytesize': 8, 'parity': 'N', 'stopbits': 1,
464
- 'xonxoff': False, 'dsrdtr': False, 'rtscts': False, 'timeout': 1,
465
- 'write_timeout': 1, 'inter_byte_timeout': None}
502
+ # --------------------------------------------------------------------
466
503
 
467
- settings = find_ult80(port, config)
468
- if settings is None:
469
- sys.exit('Serial port could not be opened')
470
-
471
- with serial.Serial(port=port) as ser:
472
- ser.apply_settings(settings)
473
-
474
- ult80 = Ult80(ser)
504
+ ult80 = Ult80()
475
505
 
476
506
  if args.read_internal_temperature:
477
507
  rval = ult80.read_internal_temperature()
478
508
  if rval is None:
479
509
  print('no response from device')
480
510
  else:
481
- print(f'read_internal_temperature, returned value {rval}°C')
511
+ if args.quiet:
512
+ print(rval)
513
+ else:
514
+ print(f'read_internal_temperature, returned value {rval}°C')
482
515
 
483
516
  if args.read_setpoint:
484
517
  rval = ult80.read_setpoint_control_point()
485
518
  if rval is None:
486
519
  print('no response from device')
487
520
  else:
488
- print(f'read_setpoint_control_point, returned value {rval}°C')
521
+ if args.quiet:
522
+ print(rval)
523
+ else:
524
+ print(f'read_setpoint_control_point, returned value {rval}°C')
489
525
 
490
526
  if args.set_setpoint:
491
527
  rval = ult80.set_setpoint_control_point(float(args.set_setpoint[0]))
492
528
  if rval is None:
493
529
  print('no response from device')
494
530
  else:
495
- print(f'set_setpoint_control_point, returned value {rval}°C')
531
+ if args.quiet:
532
+ print(rval)
533
+ else:
534
+ print(f'set_setpoint_control_point, returned value {rval}°C')
535
+
536
+ # --------------------------------------------------------------------
537
+
538
+ # release the RS232 global lock
539
+ fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
496
540
 
497
541
 
498
542
  ##############################################################################
mmcbrs232/zhelpers.py ADDED
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ """
3
+ Helper module for example applications. Mimics ZeroMQ Guide's zhelpers.h.
4
+ """
5
+ from __future__ import print_function
6
+
7
+ import binascii
8
+ import os
9
+ from random import randint
10
+
11
+ import zmq
12
+
13
+ def socket_set_hwm(socket, hwm=-1):
14
+ """libzmq 2/3/4 compatible sethwm"""
15
+ try:
16
+ socket.sndhwm = socket.rcvhwm = hwm
17
+ except AttributeError:
18
+ socket.hwm = hwm
19
+
20
+
21
+ def dump(msg_or_socket):
22
+ """Receives all message parts from socket, printing each frame neatly"""
23
+ if isinstance(msg_or_socket, zmq.Socket):
24
+ # it's a socket, call on current message
25
+ msg = msg_or_socket.recv_multipart()
26
+ else:
27
+ msg = msg_or_socket
28
+ print("----------------------------------------")
29
+ for part in msg:
30
+ print("[%03d]" % len(part), end=' ')
31
+ is_text = True
32
+ try:
33
+ print(part.decode('ascii'))
34
+ except UnicodeDecodeError:
35
+ print(r"0x%s" % (binascii.hexlify(part).decode('ascii')))
36
+
37
+
38
+ def set_id(zsocket):
39
+ """Set simple random printable identity on socket"""
40
+ identity = u"%04x-%04x" % (randint(0, 0x10000), randint(0, 0x10000))
41
+ zsocket.setsockopt_string(zmq.IDENTITY, identity)
42
+
43
+
44
+ def zpipe(ctx):
45
+ """build inproc pipe for talking to threads
46
+
47
+ mimic pipe used in czmq zthread_fork.
48
+
49
+ Returns a pair of PAIRs connected via inproc
50
+ """
51
+ a = ctx.socket(zmq.PAIR)
52
+ b = ctx.socket(zmq.PAIR)
53
+ a.linger = b.linger = 0
54
+ a.hwm = b.hwm = 1
55
+ iface = "inproc://%s" % binascii.hexlify(os.urandom(8))
56
+ a.bind(iface)
57
+ b.connect(iface)
58
+ return a,b
mmcbrs232/zpsustat.py ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Instantaneous snapshot of a single power supply channel from a device
4
+ connected by a FTDI USB to RS232 adaptors. The value is obtained from the
5
+ ZeroMQ serial port server running on atldaq1.
6
+
7
+ E.g.
8
+
9
+ zpsustat --serial 103877 --channel 3 --sv
10
+
11
+ Use "zpsustat -h" for help
12
+
13
+ All power supplies supported by detect.py are usable by this script.
14
+ """
15
+
16
+ import itertools
17
+ import sys
18
+ import types
19
+
20
+ from mmcbrs232 import mdclientlib
21
+ from mmcbrs232.psustat import check_arguments
22
+
23
+
24
+ ##############################################################################
25
+ # main
26
+ ##############################################################################
27
+
28
+ def main():
29
+ """
30
+ Instantaneous snapshot of a single power supply channel from a device
31
+ connected by a FTDI USB to RS232 adaptors. The value is obtained from the
32
+ ZeroMQ serial port server running on atldaq1.
33
+ """
34
+ status = types.SimpleNamespace(success=0, unreserved_error_code=3)
35
+
36
+ settings = {
37
+ 'alias': None,
38
+ 'channel': None,
39
+ 'manufacturer': None,
40
+ 'model': None,
41
+ 'port': None,
42
+ 'serial': None,
43
+ 'time': None,
44
+ }
45
+ check_arguments(settings)
46
+
47
+ # handle ./ prefix, though this should not be present when this script is packaged
48
+ options = sys.argv[1:]
49
+ if not options:
50
+ print('please specify options sufficient to identify a single channel')
51
+ return status.unreserved_error_code
52
+
53
+ command = ' '.join(itertools.chain(['psustat'], options))
54
+
55
+ mdc = mdclientlib.Rs232()
56
+ try:
57
+ response = mdc._reply_value(mdc._issue_serial_command(command))
58
+ except TypeError:
59
+ print('No response')
60
+ return status.unreserved_error_code
61
+
62
+ if response is None:
63
+ print('No response')
64
+ return status.unreserved_error_code
65
+
66
+ print(response)
67
+ return status.success
68
+
69
+
70
+ ##############################################################################
71
+ if __name__ == '__main__':
72
+ sys.exit(main())
mmcb_rs232/dmm.py DELETED
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Read values from the Keithley DMM6500.
4
- """
5
-
6
- import argparse
7
- import itertools
8
- import sys
9
- import time
10
-
11
- from mmcb_rs232 import common
12
- from mmcb_rs232 import dmm_interface
13
-
14
-
15
- ##############################################################################
16
- # command line option handler
17
- ##############################################################################
18
-
19
-
20
- def check_arguments():
21
- """
22
- handle command line options
23
-
24
- --------------------------------------------------------------------------
25
- args : none
26
- --------------------------------------------------------------------------
27
- returns
28
- args : <class 'argparse.Namespace'>
29
- --------------------------------------------------------------------------
30
- """
31
- parser = argparse.ArgumentParser(
32
- description='Reads voltage or current from the Keithley DMM6500.'
33
- )
34
- parser.add_argument(
35
- '-p', '--plain',
36
- action='store_true',
37
- help='print the requested figure only')
38
- parser.add_argument(
39
- '-c', '--capacitance',
40
- action='store_true',
41
- help='Read capacitance (F)')
42
- parser.add_argument(
43
- '--currentac',
44
- action='store_true',
45
- help='Read AC current (A)')
46
- parser.add_argument(
47
- '-i', '--currentdc',
48
- action='store_true',
49
- help='Read DC current (A)')
50
- parser.add_argument(
51
- '-r', '--resistance',
52
- action='store_true',
53
- help='Read two-wire resistance (\u03a9)')
54
- parser.add_argument(
55
- '-t', '--temperature',
56
- action='store_true',
57
- help='Read temperature (\u00b0C)')
58
- parser.add_argument(
59
- '--voltageac',
60
- action='store_true',
61
- help='Read AC voltage (V)')
62
- parser.add_argument(
63
- '-v', '--voltagedc',
64
- action='store_true',
65
- help='Read DC voltage (V)')
66
-
67
- args = parser.parse_args()
68
-
69
- return args
70
-
71
-
72
- ##############################################################################
73
- # main
74
- ##############################################################################
75
-
76
-
77
- def main():
78
- """ Read values from the Keithley DMM6500 """
79
-
80
- args = check_arguments()
81
-
82
- dmm = dmm_interface.Dmm6500()
83
- if dmm.init_failed:
84
- print('could not initialise serial port')
85
- return 3
86
-
87
- configure = {
88
- 'capacitance': dmm.configure_read_capacitance,
89
- 'currentac': dmm.configure_read_ac_current,
90
- 'currentdc': dmm.configure_read_dc_current,
91
- 'resistance': dmm.configure_read_resistance,
92
- 'temperature': dmm.configure_read_temperature,
93
- 'voltagedc': dmm.configure_read_dc_voltage,
94
- 'voltageac': dmm.configure_read_ac_voltage,
95
- }
96
-
97
- # AC quantities sometimes return None on the first attempt
98
- retries = 3
99
-
100
- success = []
101
- params = []
102
- for function, required in vars(args).items():
103
- if not required or function=='plain':
104
- continue
105
-
106
- configure[function]()
107
-
108
- value = None
109
- for _ in itertools.repeat(None, retries):
110
- value = dmm.read_value()
111
- if value is not None:
112
- success.append(True)
113
- break
114
- time.sleep(0.1)
115
-
116
- if args.plain:
117
- print(value)
118
- else:
119
- print(f'{function}, {common.si_prefix(value)}, {value}')
120
-
121
- return 0 if any(success) else 3
122
-
123
-
124
- ##############################################################################
125
- if __name__ == '__main__':
126
- sys.exit(main())
@@ -1,62 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mmcb_rs232-avt
3
- Version: 1.0.20
4
- Summary: ATLAS ITK Pixels Multi-Module Cycling Box environmental monitoring/control (RS232)
5
- Home-page: https://gitlab.ph.liv.ac.uk/avt/atlas-itk-pmmcb-rs232
6
- Author: Alan Taylor
7
- Author-email: avt@hep.ph.liv.ac.uk
8
- Maintainer: Alan Taylor
9
- Maintainer-email: avt@hep.ph.liv.ac.uk
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Intended Audience :: Science/Research
13
- Classifier: Topic :: Scientific/Engineering :: Physics
14
- Classifier: Environment :: Console
15
- Classifier: Environment :: X11 Applications
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.6
19
- Classifier: Programming Language :: Python :: 3.7
20
- Classifier: Programming Language :: Python :: 3.8
21
- Classifier: Programming Language :: Python :: 3.9
22
- Classifier: Programming Language :: Python :: 3.10
23
- Classifier: Programming Language :: Python :: 3.11
24
- Classifier: Programming Language :: Python :: 3.12
25
- Classifier: Programming Language :: Python :: 3.13
26
- Classifier: Operating System :: POSIX :: Linux
27
- Classifier: Natural Language :: English
28
- Classifier: License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)
29
- Requires-Python: >=3.6
30
- Description-Content-Type: text/markdown
31
- Requires-Dist: numpy
32
- Requires-Dist: pandas
33
- Requires-Dist: matplotlib
34
- Requires-Dist: zmq
35
- Requires-Dist: pyserial==3.4.*
36
- Requires-Dist: tables
37
- Requires-Dist: yoctopuce
38
- Dynamic: author
39
- Dynamic: author-email
40
- Dynamic: classifier
41
- Dynamic: description
42
- Dynamic: description-content-type
43
- Dynamic: home-page
44
- Dynamic: maintainer
45
- Dynamic: maintainer-email
46
- Dynamic: requires-dist
47
- Dynamic: requires-python
48
- Dynamic: summary
49
-
50
- # Software support for the ATLAS ITK pixels multi-module cycling box (RS232 only)
51
-
52
- [![PyPI version](https://badge.fury.io/py/mmcb-rs232-avt.svg)](https://badge.fury.io/py/mmcb-rs232-avt)
53
-
54
- ![image](https://hep.ph.liv.ac.uk/~avt/pypi/logo.png)
55
-
56
- *Particle Physics, University of Liverpool, UK*
57
-
58
- ----
59
-
60
- This repository provides command line tools to monitor and control equipment in the *ATLAS ITK pixels multi-module cycling box* test setup. It is used in the test setups for a number of other projects.
61
-
62
- This repository mirrors a subset of files contained in the original [atlas-mmcb-avt](https://gitlab.ph.liv.ac.uk/avt/atlas-itk-pmmcb/) repository, the later remains the reference. This package contains only the RS232 laboratory control command line scripts, and excludes the Raspberry Pi specific I2C environmenal monitoring parts.
@@ -1,17 +0,0 @@
1
- mmcb_rs232/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mmcb_rs232/common.py,sha256=xI3Iqh_Y0iEvLLc-9bSQSHuTKBhSrY4AAMyV-X_dfvQ,99594
3
- mmcb_rs232/detect.py,sha256=NnJHsfcPl6Lk3dw1xKp3bVgIV5ug4atx-or95rKncLg,34888
4
- mmcb_rs232/dmm.py,sha256=6SUkm3Nf_oGC0LgtiSE8czFGwDw1Bjg6f2O95Ed7GGM,3511
5
- mmcb_rs232/dmm_interface.py,sha256=XrNVEsq6H9SXBx1RhQ_pVcR1m2CItco7vZDAe_P8Bm0,4700
6
- mmcb_rs232/iv.py,sha256=Mb6nXEyczX20PMLjG1ej9OpHXa1FEmVqwP4r2V4ICgk,115342
7
- mmcb_rs232/lexicon.py,sha256=x30iZ6pMZjdpdmwmZfhhX5MfMCZu4GH6gaPJyXIG_y0,24903
8
- mmcb_rs232/psuset.py,sha256=7kMg7q8DWaeESEMCHbq7WEFfbTh-FMUvKdP9pBBKLLQ,37513
9
- mmcb_rs232/psustat.py,sha256=5qK_51jnmT_DKeLZlerUULIUy2AJJKIkb4BlFCD7BLM,26668
10
- mmcb_rs232/psuwatch.py,sha256=tI_CK6XuxFA_aoAeeDA_b9pM9Ra30B2dh5-fCK8YlAM,21662
11
- mmcb_rs232/sequence.py,sha256=mZhVwqkzWVwIz7BKH3a7dUgYeUcJdzQKXIvxucsKCtk,17711
12
- mmcb_rs232/ult80.py,sha256=VYVKjGSJLKgSr4F4QABGyWnpZUmTvFmybohyP5Kq9BU,17665
13
- mmcb_rs232_avt-1.0.20.dist-info/METADATA,sha256=B-wM8R9dRDsKNUKY1TbfjYCU5WOSt8g_zpVMIO4HgLU,2590
14
- mmcb_rs232_avt-1.0.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- mmcb_rs232_avt-1.0.20.dist-info/entry_points.txt,sha256=QkJcHeTjO0v5yfgbY814Lm2NedyZ81A55nYqtDz1l8s,302
16
- mmcb_rs232_avt-1.0.20.dist-info/top_level.txt,sha256=R7MQdVXJyyGxoZ1lBG0-7RGElplTd0tsPKJzVjMqYVc,11
17
- mmcb_rs232_avt-1.0.20.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- [console_scripts]
2
- detect = mmcb_rs232.detect:main
3
- dmm = mmcb_rs232.dmm:main
4
- iv = mmcb_rs232.iv:main
5
- liveplot = mmcb_rs232.liveplot:main
6
- log2dat = mmcb_rs232.log2dat:main
7
- psuset = mmcb_rs232.psuset:main
8
- psustat = mmcb_rs232.psustat:main
9
- psuwatch = mmcb_rs232.psuwatch:main
10
- ult80 = mmcb_rs232.ult80:main
@@ -1 +0,0 @@
1
- mmcb_rs232
File without changes