mmcb-rs232-avt 1.0.20__py3-none-any.whl → 1.1.38__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.38.dist-info/METADATA +210 -0
- mmcb_rs232_avt-1.1.38.dist-info/RECORD +26 -0
- {mmcb_rs232_avt-1.0.20.dist-info → mmcb_rs232_avt-1.1.38.dist-info}/WHEEL +1 -1
- mmcb_rs232_avt-1.1.38.dist-info/entry_points.txt +12 -0
- mmcb_rs232_avt-1.1.38.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 +40 -14
- {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.20.dist-info/METADATA +0 -62
- mmcb_rs232_avt-1.0.20.dist-info/RECORD +0 -17
- mmcb_rs232_avt-1.0.20.dist-info/entry_points.txt +0 -10
- mmcb_rs232_avt-1.0.20.dist-info/top_level.txt +0 -1
- {mmcb_rs232 → mmcbrs232}/__init__.py +0 -0
mmcbrs232/mdcliapi.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Majordomo Protocol Client API, Python version.
|
|
2
|
+
|
|
3
|
+
Implements the MDP/Worker spec at http:#rfc.zeromq.org/spec:7.
|
|
4
|
+
|
|
5
|
+
Author: Min RK <benjaminrk@gmail.com>
|
|
6
|
+
Based on Java example by Arkadiusz Orzechowski
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
import zmq
|
|
12
|
+
|
|
13
|
+
from mmcbrs232 import MDP
|
|
14
|
+
from mmcbrs232 import zhelpers
|
|
15
|
+
|
|
16
|
+
class MajorDomoClient(object):
|
|
17
|
+
"""Majordomo Protocol Client API, Python version.
|
|
18
|
+
|
|
19
|
+
Implements the MDP/Worker spec at http:#rfc.zeromq.org/spec:7.
|
|
20
|
+
"""
|
|
21
|
+
broker = None
|
|
22
|
+
ctx = None
|
|
23
|
+
client = None
|
|
24
|
+
poller = None
|
|
25
|
+
# ms
|
|
26
|
+
timeout = 20000
|
|
27
|
+
retries = 5
|
|
28
|
+
verbose = False
|
|
29
|
+
|
|
30
|
+
def __init__(self, broker, verbose=False):
|
|
31
|
+
self.broker = broker
|
|
32
|
+
self.verbose = verbose
|
|
33
|
+
self.ctx = zmq.Context()
|
|
34
|
+
self.poller = zmq.Poller()
|
|
35
|
+
logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S",
|
|
36
|
+
level=logging.INFO)
|
|
37
|
+
self.reconnect_to_broker()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def reconnect_to_broker(self):
|
|
41
|
+
"""Connect or reconnect to broker"""
|
|
42
|
+
if self.client:
|
|
43
|
+
self.poller.unregister(self.client)
|
|
44
|
+
self.client.close()
|
|
45
|
+
self.client = self.ctx.socket(zmq.REQ)
|
|
46
|
+
|
|
47
|
+
self.client.setsockopt(zmq.SNDHWM, 1000)
|
|
48
|
+
self.client.setsockopt(zmq.SNDBUF, 1000)
|
|
49
|
+
self.client.setsockopt(zmq.RCVHWM, 1000)
|
|
50
|
+
self.client.setsockopt(zmq.RCVBUF, 1000)
|
|
51
|
+
self.client.set_hwm(10000)
|
|
52
|
+
|
|
53
|
+
self.client.linger = 0
|
|
54
|
+
self.client.connect(self.broker)
|
|
55
|
+
self.poller.register(self.client, zmq.POLLIN)
|
|
56
|
+
if self.verbose:
|
|
57
|
+
logging.info("I: connecting to broker at %s...", self.broker)
|
|
58
|
+
|
|
59
|
+
def send(self, service, request):
|
|
60
|
+
"""Send request to broker and get reply by hook or crook.
|
|
61
|
+
|
|
62
|
+
Takes ownership of request message and destroys it when sent.
|
|
63
|
+
Returns the reply message or None if there was no reply.
|
|
64
|
+
"""
|
|
65
|
+
if not isinstance(request, list):
|
|
66
|
+
request = [request]
|
|
67
|
+
request = [MDP.C_CLIENT, service] + request
|
|
68
|
+
if self.verbose:
|
|
69
|
+
logging.warn("I: send request to '%s' service: ", service)
|
|
70
|
+
zhelpers.dump(request)
|
|
71
|
+
reply = None
|
|
72
|
+
|
|
73
|
+
retries = self.retries
|
|
74
|
+
while retries > 0:
|
|
75
|
+
self.client.send_multipart(request)
|
|
76
|
+
try:
|
|
77
|
+
items = self.poller.poll(self.timeout)
|
|
78
|
+
except KeyboardInterrupt:
|
|
79
|
+
break # interrupted
|
|
80
|
+
|
|
81
|
+
if items:
|
|
82
|
+
msg = self.client.recv_multipart()
|
|
83
|
+
if self.verbose:
|
|
84
|
+
logging.info("I: received reply:")
|
|
85
|
+
zhelpers.dump(msg)
|
|
86
|
+
|
|
87
|
+
# Don't try to handle errors, just assert noisily
|
|
88
|
+
assert len(msg) >= 3
|
|
89
|
+
|
|
90
|
+
header = msg.pop(0)
|
|
91
|
+
assert MDP.C_CLIENT == header
|
|
92
|
+
|
|
93
|
+
reply_service = msg.pop(0)
|
|
94
|
+
assert service == reply_service
|
|
95
|
+
|
|
96
|
+
reply = msg
|
|
97
|
+
break
|
|
98
|
+
else:
|
|
99
|
+
if retries:
|
|
100
|
+
logging.warn("W: no reply, reconnecting...")
|
|
101
|
+
self.reconnect_to_broker()
|
|
102
|
+
else:
|
|
103
|
+
logging.warn("W: permanent error, abandoning")
|
|
104
|
+
break
|
|
105
|
+
retries -= 1
|
|
106
|
+
|
|
107
|
+
return reply
|
|
108
|
+
|
|
109
|
+
def destroy(self):
|
|
110
|
+
self.context.destroy()
|
mmcbrs232/mdclientlib.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Client library to help integrate the ZeroMQ serial port server into the PID
|
|
4
|
+
test scripts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import logging
|
|
9
|
+
import platform
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from importlib import metadata
|
|
13
|
+
except (ImportError, ModuleNotFoundError):
|
|
14
|
+
try:
|
|
15
|
+
import importlib_metadata as metadata
|
|
16
|
+
except (ImportError, ModuleNotFoundError):
|
|
17
|
+
py_version = platform.python_version()
|
|
18
|
+
if py_version < '3.8':
|
|
19
|
+
logging.warning(
|
|
20
|
+
'Cannot import importlib, version information will NOT be available.'
|
|
21
|
+
)
|
|
22
|
+
logging.warning(
|
|
23
|
+
'Unsupported Python version (%s), upgrade to Python 3.8 or newer.',
|
|
24
|
+
py_version,
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
logging.error('Cannot import importlib')
|
|
28
|
+
|
|
29
|
+
from mmcbrs232 import mdcliapi
|
|
30
|
+
from mmcbrs232 import common
|
|
31
|
+
from mmcbrs232 import psuset
|
|
32
|
+
from mmcbrs232 import psustat
|
|
33
|
+
|
|
34
|
+
# read the package version number as originally specified in setup.py
|
|
35
|
+
try:
|
|
36
|
+
__version__ = metadata.version('mmcb-rs232-avt')
|
|
37
|
+
except NameError:
|
|
38
|
+
# end-user is probably using a Python version < 3.8
|
|
39
|
+
__version__ = 'unknown'
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
###############################################################################
|
|
43
|
+
# support
|
|
44
|
+
###############################################################################
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def default():
|
|
48
|
+
"""
|
|
49
|
+
---------------------------------------------------------------------------
|
|
50
|
+
args : none
|
|
51
|
+
---------------------------------------------------------------------------
|
|
52
|
+
returns : dict
|
|
53
|
+
---------------------------------------------------------------------------
|
|
54
|
+
"""
|
|
55
|
+
return {
|
|
56
|
+
'alias': None,
|
|
57
|
+
'channel': None,
|
|
58
|
+
'current_limit': None,
|
|
59
|
+
'debug': None,
|
|
60
|
+
'decimal_places': 2,
|
|
61
|
+
'forwardbias': False,
|
|
62
|
+
'immediate': False,
|
|
63
|
+
'manufacturer': None,
|
|
64
|
+
'model': None,
|
|
65
|
+
'on': None,
|
|
66
|
+
'peltier': False,
|
|
67
|
+
'port': None,
|
|
68
|
+
'quiet': False,
|
|
69
|
+
'rear': None,
|
|
70
|
+
'reset': False,
|
|
71
|
+
'serial': None,
|
|
72
|
+
'settlingtime': 0.5,
|
|
73
|
+
'verbose': None,
|
|
74
|
+
'voltage': None,
|
|
75
|
+
'voltspersecond': 10
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
###############################################################################
|
|
80
|
+
# data structures
|
|
81
|
+
#
|
|
82
|
+
# model command lines from the PID scripts
|
|
83
|
+
###############################################################################
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Rs232:
|
|
87
|
+
"""
|
|
88
|
+
Handle serial port interactions
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# read hardware configuration
|
|
92
|
+
psus = common.cache_read(['hvpsu', 'lvpsu', 'chiller'])
|
|
93
|
+
settings = default()
|
|
94
|
+
channels = common.ports_to_channels(settings, psus)
|
|
95
|
+
ports = sorted(psus)
|
|
96
|
+
|
|
97
|
+
# create client
|
|
98
|
+
client = mdcliapi.MajorDomoClient('tcp://localhost:5556', verbose=False)
|
|
99
|
+
|
|
100
|
+
############################################################################
|
|
101
|
+
# support
|
|
102
|
+
############################################################################
|
|
103
|
+
|
|
104
|
+
def _get_serial_port_name(self, command):
|
|
105
|
+
"""
|
|
106
|
+
get serial port name from command (psuset, psustat, ult80)
|
|
107
|
+
"""
|
|
108
|
+
local_psus = self.psus.copy()
|
|
109
|
+
local_channels = self.channels.copy()
|
|
110
|
+
|
|
111
|
+
cli = command.split(' ')[0].strip()
|
|
112
|
+
|
|
113
|
+
if cli == 'ult80':
|
|
114
|
+
# obtain from device cache since there are no channels for the ULT80
|
|
115
|
+
rval = [
|
|
116
|
+
serial_port_name
|
|
117
|
+
for serial_port_name, details in self.psus.items()
|
|
118
|
+
if details[1] == 'chiller'
|
|
119
|
+
][0]
|
|
120
|
+
|
|
121
|
+
elif cli == 'psuset':
|
|
122
|
+
settings = default()
|
|
123
|
+
psuset.check_arguments(settings, command)
|
|
124
|
+
single = common.unique(settings, local_psus, local_channels)
|
|
125
|
+
|
|
126
|
+
rval = next(iter(local_psus)) if single else None
|
|
127
|
+
|
|
128
|
+
elif cli == 'psustat':
|
|
129
|
+
settings = {
|
|
130
|
+
'alias': None,
|
|
131
|
+
'channel': None,
|
|
132
|
+
'manufacturer': None,
|
|
133
|
+
'model': None,
|
|
134
|
+
'port': None,
|
|
135
|
+
'serial': None,
|
|
136
|
+
'time': None,
|
|
137
|
+
}
|
|
138
|
+
psustat.check_arguments(settings, command)
|
|
139
|
+
single = common.unique(settings, local_psus, local_channels)
|
|
140
|
+
|
|
141
|
+
if settings['filter'] and not single:
|
|
142
|
+
rval = None
|
|
143
|
+
else:
|
|
144
|
+
rval = next(iter(local_psus))
|
|
145
|
+
else:
|
|
146
|
+
rval = None
|
|
147
|
+
|
|
148
|
+
return rval
|
|
149
|
+
|
|
150
|
+
def _issue_serial_command(self, command):
|
|
151
|
+
"""
|
|
152
|
+
"""
|
|
153
|
+
serial_port_name = self._get_serial_port_name(command)
|
|
154
|
+
reply = None
|
|
155
|
+
if serial_port_name is None:
|
|
156
|
+
return reply
|
|
157
|
+
|
|
158
|
+
request = bytes(command, encoding='utf-8')
|
|
159
|
+
|
|
160
|
+
with contextlib.suppress(KeyboardInterrupt):
|
|
161
|
+
reply = self.client.send(
|
|
162
|
+
bytes(serial_port_name, encoding='utf-8'), request
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return reply
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _reply_value(reply):
|
|
169
|
+
"""
|
|
170
|
+
Extract value from returned string; discard latency figure.
|
|
171
|
+
|
|
172
|
+
------------------------------------------------------------------------
|
|
173
|
+
args
|
|
174
|
+
reply : list of str
|
|
175
|
+
e.g. [b'ult80 -i : None (1.008616 s)']
|
|
176
|
+
------------------------------------------------------------------------
|
|
177
|
+
returns : float, bool or None
|
|
178
|
+
------------------------------------------------------------------------
|
|
179
|
+
"""
|
|
180
|
+
value_str, *_ = reply[0].decode().partition(':')[-1].partition('(')
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
value = float(value_str)
|
|
184
|
+
except ValueError:
|
|
185
|
+
value = {'True': True, 'False': False}.get(value_str.strip())
|
|
186
|
+
|
|
187
|
+
return value
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
############################################################################
|
|
191
|
+
# obtain readings
|
|
192
|
+
############################################################################
|
|
193
|
+
|
|
194
|
+
def get_ult80_temps(self):
|
|
195
|
+
"""
|
|
196
|
+
------------------------------------------------------------------------
|
|
197
|
+
args : none
|
|
198
|
+
------------------------------------------------------------------------
|
|
199
|
+
returns : float, float
|
|
200
|
+
(internal_temperature, set point)
|
|
201
|
+
------------------------------------------------------------------------
|
|
202
|
+
"""
|
|
203
|
+
return (
|
|
204
|
+
self._reply_value(self._issue_serial_command('ult80 -r')),
|
|
205
|
+
self._reply_value(self._issue_serial_command('ult80 -i')),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def get_pt_v(self, psu_id, channel):
|
|
209
|
+
"""
|
|
210
|
+
HMP4040 set voltage
|
|
211
|
+
"""
|
|
212
|
+
return self._reply_value(
|
|
213
|
+
self._issue_serial_command(
|
|
214
|
+
f'psustat --model hmp4040 --channel {channel} --serial {psu_id} --sv'
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def get_pt_i(self, psu_id, channel):
|
|
219
|
+
"""
|
|
220
|
+
HMP4040 measured current
|
|
221
|
+
"""
|
|
222
|
+
return self._reply_value(
|
|
223
|
+
self._issue_serial_command(
|
|
224
|
+
f'psustat --model hmp4040 --channel {channel} --serial {psu_id} --mi'
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def get_pt_sv_fast(self, psu_id, channel):
|
|
229
|
+
"""
|
|
230
|
+
HMP4040 get channel set voltage, tailored for ZeroMQ serial port server.
|
|
231
|
+
"""
|
|
232
|
+
return self._reply_value(
|
|
233
|
+
self._issue_serial_command(
|
|
234
|
+
f'psustat --model hmp4040 --channel {channel} --serial {psu_id} --sv --zserv'
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def get_pt_mi_fast(self, psu_id, channel):
|
|
239
|
+
"""
|
|
240
|
+
HMP4040 get channel measured current, tailored for ZeroMQ serial port server.
|
|
241
|
+
"""
|
|
242
|
+
return self._reply_value(
|
|
243
|
+
self._issue_serial_command(
|
|
244
|
+
f'psustat --model hmp4040 --channel {channel} --serial {psu_id} --mi --zserv'
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def psu_set(self, psu_id, channel, voltage, current):
|
|
249
|
+
"""
|
|
250
|
+
channel on
|
|
251
|
+
"""
|
|
252
|
+
return self._reply_value(
|
|
253
|
+
self._issue_serial_command(
|
|
254
|
+
f'psuset {voltage} --limit {current} --on --channel {channel} --serial {psu_id}'
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def psu_unset(self, psu_id, channel, voltage, current):
|
|
259
|
+
"""
|
|
260
|
+
channel off
|
|
261
|
+
"""
|
|
262
|
+
return self._reply_value(
|
|
263
|
+
self._issue_serial_command(
|
|
264
|
+
f'psuset {voltage} --limit {current} --off --channel {channel} --serial {psu_id}'
|
|
265
|
+
)
|
|
266
|
+
)
|
mmcbrs232/mdwrkapi.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Majordomo Protocol Worker API, Python version
|
|
2
|
+
|
|
3
|
+
Implements the MDP/Worker spec at http:#rfc.zeromq.org/spec:7.
|
|
4
|
+
|
|
5
|
+
Author: Min RK <benjaminrk@gmail.com>
|
|
6
|
+
Based on Java example by Arkadiusz Orzechowski
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
import zmq
|
|
12
|
+
|
|
13
|
+
from mmcbrs232 import MDP
|
|
14
|
+
from mmcbrs232 import zhelpers
|
|
15
|
+
|
|
16
|
+
class MajorDomoWorker(object):
|
|
17
|
+
"""Majordomo Protocol Worker API, Python version
|
|
18
|
+
|
|
19
|
+
Implements the MDP/Worker spec at http:#rfc.zeromq.org/spec:7.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# HEARTBEAT_LIVENESS = 3 # 3-5 is reasonable
|
|
23
|
+
HEARTBEAT_LIVENESS = 5 # 3-5 is reasonable
|
|
24
|
+
|
|
25
|
+
broker = None
|
|
26
|
+
ctx = None
|
|
27
|
+
service = None
|
|
28
|
+
|
|
29
|
+
worker = None # Socket to broker
|
|
30
|
+
heartbeat_at = 0 # When to send HEARTBEAT (relative to time.time(), so in seconds)
|
|
31
|
+
liveness = 0 # How many attempts left
|
|
32
|
+
heartbeat = 2500 # Heartbeat delay, msecs
|
|
33
|
+
reconnect = 2500 # Reconnect delay, msecs
|
|
34
|
+
# heartbeat = 5000 # Heartbeat delay, msecs
|
|
35
|
+
# reconnect = 5000 # Reconnect delay, msecs
|
|
36
|
+
|
|
37
|
+
# Internal state
|
|
38
|
+
expect_reply = False # False only at start
|
|
39
|
+
|
|
40
|
+
# timeout = 2500 # poller timeout
|
|
41
|
+
timeout = 5000 # poller timeout
|
|
42
|
+
|
|
43
|
+
verbose = False # Print activity to stdout
|
|
44
|
+
|
|
45
|
+
# Return address, if any
|
|
46
|
+
reply_to = None
|
|
47
|
+
|
|
48
|
+
def __init__(self, broker, service, verbose=False):
|
|
49
|
+
self.broker = broker
|
|
50
|
+
self.service = service
|
|
51
|
+
self.verbose = verbose
|
|
52
|
+
self.ctx = zmq.Context()
|
|
53
|
+
self.poller = zmq.Poller()
|
|
54
|
+
logging.basicConfig(format="%(asctime)s %(message)s",
|
|
55
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
56
|
+
level=logging.INFO)
|
|
57
|
+
self.reconnect_to_broker()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reconnect_to_broker(self):
|
|
61
|
+
"""Connect or reconnect to broker"""
|
|
62
|
+
if self.worker:
|
|
63
|
+
self.poller.unregister(self.worker)
|
|
64
|
+
self.worker.close()
|
|
65
|
+
self.worker = self.ctx.socket(zmq.DEALER)
|
|
66
|
+
|
|
67
|
+
self.worker.setsockopt(zmq.SNDHWM, 1000)
|
|
68
|
+
self.worker.setsockopt(zmq.SNDBUF, 1000)
|
|
69
|
+
self.worker.setsockopt(zmq.RCVHWM, 1000)
|
|
70
|
+
self.worker.setsockopt(zmq.RCVBUF, 1000)
|
|
71
|
+
self.worker.set_hwm(10000)
|
|
72
|
+
|
|
73
|
+
self.worker.linger = 0
|
|
74
|
+
self.worker.connect(self.broker)
|
|
75
|
+
self.poller.register(self.worker, zmq.POLLIN)
|
|
76
|
+
if self.verbose:
|
|
77
|
+
logging.info("I: connecting to broker at %s...", self.broker)
|
|
78
|
+
|
|
79
|
+
# Register service with broker
|
|
80
|
+
self.send_to_broker(MDP.W_READY, self.service, [])
|
|
81
|
+
|
|
82
|
+
# If liveness hits zero, queue is considered disconnected
|
|
83
|
+
self.liveness = self.HEARTBEAT_LIVENESS
|
|
84
|
+
self.heartbeat_at = time.time() + 1e-3 * self.heartbeat
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def send_to_broker(self, command, option=None, msg=None):
|
|
88
|
+
"""Send message to broker.
|
|
89
|
+
|
|
90
|
+
If no msg is provided, creates one internally
|
|
91
|
+
"""
|
|
92
|
+
if msg is None:
|
|
93
|
+
msg = []
|
|
94
|
+
elif not isinstance(msg, list):
|
|
95
|
+
msg = [msg]
|
|
96
|
+
|
|
97
|
+
if option:
|
|
98
|
+
msg = [option] + msg
|
|
99
|
+
|
|
100
|
+
msg = [b'', MDP.W_WORKER, command] + msg
|
|
101
|
+
if self.verbose:
|
|
102
|
+
logging.info("I: sending %s to broker", command)
|
|
103
|
+
zhelpers.dump(msg)
|
|
104
|
+
self.worker.send_multipart(msg)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def recv(self, reply=None):
|
|
108
|
+
"""Send reply, if any, to broker and wait for next request."""
|
|
109
|
+
# Format and send the reply if we were provided one
|
|
110
|
+
assert reply is not None or not self.expect_reply
|
|
111
|
+
|
|
112
|
+
if reply is not None:
|
|
113
|
+
assert self.reply_to is not None
|
|
114
|
+
reply = [self.reply_to, b''] + reply
|
|
115
|
+
self.send_to_broker(MDP.W_REPLY, msg=reply)
|
|
116
|
+
|
|
117
|
+
self.expect_reply = True
|
|
118
|
+
|
|
119
|
+
while True:
|
|
120
|
+
# Poll socket for a reply, with timeout
|
|
121
|
+
try:
|
|
122
|
+
items = self.poller.poll(self.timeout)
|
|
123
|
+
except KeyboardInterrupt:
|
|
124
|
+
break # Interrupted
|
|
125
|
+
|
|
126
|
+
if items:
|
|
127
|
+
msg = self.worker.recv_multipart()
|
|
128
|
+
if self.verbose:
|
|
129
|
+
logging.info("I: received message from broker: ")
|
|
130
|
+
zhelpers.dump(msg)
|
|
131
|
+
|
|
132
|
+
self.liveness = self.HEARTBEAT_LIVENESS
|
|
133
|
+
# Don't try to handle errors, just assert noisily
|
|
134
|
+
assert len(msg) >= 3
|
|
135
|
+
|
|
136
|
+
empty = msg.pop(0)
|
|
137
|
+
assert empty == b''
|
|
138
|
+
|
|
139
|
+
header = msg.pop(0)
|
|
140
|
+
assert header == MDP.W_WORKER
|
|
141
|
+
|
|
142
|
+
command = msg.pop(0)
|
|
143
|
+
if command == MDP.W_REQUEST:
|
|
144
|
+
# We should pop and save as many addresses as there are
|
|
145
|
+
# up to a null part, but for now, just save one...
|
|
146
|
+
self.reply_to = msg.pop(0)
|
|
147
|
+
# pop empty
|
|
148
|
+
empty = msg.pop(0)
|
|
149
|
+
assert empty == b''
|
|
150
|
+
|
|
151
|
+
return msg # We have a request to process
|
|
152
|
+
elif command == MDP.W_HEARTBEAT:
|
|
153
|
+
# Do nothing for heartbeats
|
|
154
|
+
pass
|
|
155
|
+
elif command == MDP.W_DISCONNECT:
|
|
156
|
+
self.reconnect_to_broker()
|
|
157
|
+
else :
|
|
158
|
+
logging.error("E: invalid input message: ")
|
|
159
|
+
zhelpers.dump(msg)
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
self.liveness -= 1
|
|
163
|
+
if self.liveness == 0:
|
|
164
|
+
if self.verbose:
|
|
165
|
+
logging.warn("W: disconnected from broker - retrying...")
|
|
166
|
+
try:
|
|
167
|
+
time.sleep(1e-3*self.reconnect)
|
|
168
|
+
except KeyboardInterrupt:
|
|
169
|
+
break
|
|
170
|
+
self.reconnect_to_broker()
|
|
171
|
+
|
|
172
|
+
# Send HEARTBEAT if it's time
|
|
173
|
+
if time.time() > self.heartbeat_at:
|
|
174
|
+
self.send_to_broker(MDP.W_HEARTBEAT)
|
|
175
|
+
self.heartbeat_at = time.time() + 1e-3*self.heartbeat
|
|
176
|
+
|
|
177
|
+
logging.warn("W: interrupt received, killing worker...")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def destroy(self):
|
|
182
|
+
# context.destroy depends on pyzmq >= 2.1.10
|
|
183
|
+
self.ctx.destroy(0)
|