class MqAsyncReq(object): """Class for the MDP client side. Thin asynchronous encapsulation of a zmq.REQ socket. Provides a :func:`request` method with optional timeout. Objects of this class are ment to be integrated into the asynchronous IOLoop of pyzmq. :param context: the ZeroMQ context to create the socket in. :type context: zmq.Context :param endpoint: the enpoint to connect to. :type endpoint: str :param service: the service the client should use :type service: str """ _proto_version = b'MDPC01' def __init__(self, context, service): """Initialize the MDPClient. """ if ("domogik.common.configloader" in sys.modules): cfg = Loader('mq').load() confi = dict(cfg[1]) self.endpoint = "tcp://{0}:{1}".format(config['ip'], config['req_rep_port']) else: ip = Parameter.objects.get(key='mq-ip') port = Parameter.objects.get(key='mq-req_rep_port') self.endpoint = "tcp://{0}:{1}".format(ip.value, port.value) socket = ZmqSocket(context, zmq.REQ) ioloop = IOLoop.instance() self.service = service self.stream = ZMQStream(socket, ioloop) self.stream.on_recv(self._on_message) self.can_send = True self._proto_prefix = [ PROTO_VERSION, service] self._tmo = None self.timed_out = False socket.connect(self.endpoint) return def shutdown(self): """Method to deactivate the client connection completely. Will delete the stream and the underlying socket. .. warning:: The instance MUST not be used after :func:`shutdown` has been called. :rtype: None """ if not self.stream: return self.stream.socket.setsockopt(zmq.LINGER, 0) self.stream.socket.close() self.stream.close() self.stream = None return def request(self, msg, timeout=None): """Send the given message. :param msg: message parts to send. :type msg: list of str :param timeout: time to wait in milliseconds. :type timeout: int :rtype None: """ if not self.can_send: raise InvalidStateError() if type(msg) in (bytes, str): msg = [msg] # prepare full message to_send = self._proto_prefix[:] to_send.extend(msg) self.stream.send_multipart(to_send) self.can_send = False if timeout: self._start_timeout(timeout) return def _on_timeout(self): """Helper called after timeout. """ self.timed_out = True self._tmo = None self.on_timeout() return def _start_timeout(self, timeout): """Helper for starting the timeout. :param timeout: the time to wait in milliseconds. :type timeout: int """ self._tmo = DelayedCallback(self._on_timeout, timeout) self._tmo.start() return def _on_message(self, msg): """Helper method called on message receive. :param msg: list of message parts. :type msg: list of str """ if self._tmo: # disable timout self._tmo.stop() self._tmo = None # setting state before invoking on_message, so we can request from there self.can_send = True self.on_message(msg) return def on_message(self, msg): """Public method called when a message arrived. .. note:: Does nothing. Should be overloaded! """ pass def on_timeout(self): """Public method called when a timeout occured. .. note:: Does nothing. Should be overloaded! """ pass
class MDPClient(object): """Class for the MDP client side. Thin asynchronous encapsulation of a zmq.REQ socket. Provides a :func:`request` method with optional timeout. Objects of this class are ment to be integrated into the asynchronous IOLoop of pyzmq. :param context: the ZeroMQ context to create the socket in. :type context: zmq.Context :param endpoint: the enpoint to connect to. :type endpoint: str :param service: the service the client should use :type service: str """ _proto_version = C_CLIENT def __init__(self, context, endpoint): """Initialize the MDPClient. """ self.context = context self.endpoint = endpoint def start(self): """ Initialize the zmq sockets on a ioloop stream. The separation of this part from the init is useful if we start the client on a separate thread with a new ioloop (for example to enable use in an ipython notebook) """ socket = self.context.socket(zmq.DEALER) ioloop = IOLoop.instance() self.stream = ZMQStream(socket, ioloop) self.stream.on_recv(self._on_message) self._proto_prefix = [EMPTY_FRAME, self._proto_version] self._delayed_timeout = None self.timed_out = False socket.connect(self.endpoint) def shutdown(self): """Method to deactivate the client connection completely. Will delete the stream and the underlying socket. .. warning:: The instance MUST not be used after :func:`shutdown` has been called. :rtype: None """ if not self.stream: return self.stream.socket.setsockopt(zmq.LINGER, 0) self.stream.socket.close() self.stream.close() self.stream = None def request(self, service, msg, msg_extra=STANDARD, timeout=None): """Send the given message. :param msg: message parts to send. :type msg: list of str :param msg_extra: Extra message flags (e.g. STANDARD or BROADCAST) :type msg_extra: int :param timeout: time to wait in milliseconds. :type timeout: int :rtype None: """ if type(msg) in (bytes, unicode): msg = [msg] # # prepare full message # to_send = [chr(msg_extra)] + self._proto_prefix[:] to_send.extend([service]) to_send.extend(msg) self.stream.send_multipart(to_send) if timeout: self._start_timeout(timeout) def _on_timeout(self): """Helper called after timeout. """ self.timed_out = True self._delayed_timeout = None self.on_timeout() def _start_timeout(self, timeout): """Helper for starting the timeout. :param timeout: the time to wait in milliseconds. :type timeout: int """ self._delayed_timeout = DelayedCallback(self._on_timeout, timeout) self._delayed_timeout.start() def _on_message(self, msg): """Helper method called on message receive. :param msg: list of message parts. :type msg: list of str """ if self._delayed_timeout: # # disable timout # self._delayed_timeout.stop() self._delayed_timeout = None self.on_message(msg) def on_message(self, msg): """Public method called when a message arrived. .. note:: Does nothing. Should be overloaded! """ raise NotImplementedError('on_message must be implemented by the subclass.') def on_timeout(self): """Public method called when a timeout occured. .. note:: Does nothing. Should be overloaded! """ raise NotImplementedError('on_timeout must be implemented by the subclass.')
class Client(object): def __init__(self, context, endpoint, service): """Initialize the Client. """ socket = context.socket(zmq.DEALER) self.ioloop = IOLoop.instance() self.service = service.encode('utf-8') self.endpoint = endpoint self.stream = ZMQStream(socket, self.ioloop) self.stream.on_recv(self._on_message) self.can_send = True self._proto_prefix = [MDP_CLIENT_VERSION, b'\x01', service.encode('utf-8')] self._tmo = None self.timed_out = False self.multicast_services = None self.multicast_already_received = [] socket.connect(endpoint) return def shutdown(self): """Method to deactivate the client connection completely. Will delete the stream and the underlying socket. .. warning:: The instance MUST not be used after :func:`shutdown` has been called. :rtype: None """ if not self.stream: return self.stream.socket.setsockopt(zmq.LINGER, 0) self.stream.socket.close() self.stream.close() self.stream = None return def request(self, msg, timeout=None): """Send the given message. :param msg: message parts to send. :type msg: list of str :param timeout: time to wait in milliseconds. :type timeout: int | float :rtype None: """ if not self.can_send: raise InvalidStateError() try: if type(msg) in (bytes, unicode): # python 2 msg = [msg] except NameError: if type(msg) == bytes: # python 3 msg = [msg] # prepare full message to_send = [b''] to_send.extend(self._proto_prefix[:]) to_send.extend(msg) self.stream.send_multipart(to_send) self.can_send = False self.multicast_services = None if timeout: self._start_timeout(timeout) return def rpc(self, method, args=None, kwargs=None, timeout=None): """Call RPC method :param method: name of method :type method: str :param args: list of args :type args: list :param kwargs: list of kwargs :type kwargs: dict :param timeout: time to wait in milliseconds. :type timeout: int | float :rtype None: """ data = [method.encode('utf-8'), msgpack.packb([] if args is None else args), msgpack.packb({} if kwargs is None else kwargs)] return self.request(data, timeout) def _on_message(self, message): """Helper method called on message receive. :param message: list of message parts. :type message: list of str """ message.pop(0) # remove empty string protocol_version = message.pop(0) if protocol_version != MDP_WORKER_VERSION: # version check, ignore old versions return message_type = message.pop(0) if message_type == b'\x02': # partial message worker = message.pop(0) try: msg = msgpack.unpackb(message[0]) except: msg = message[0] self.on_partial_message(worker, msg) elif message_type == b'\x03': # final message worker = message.pop(0) try: msg = msgpack.unpackb(message[0]) except: msg = message[0] self.on_message(worker, msg) if message[0] not in self.multicast_already_received: self.multicast_already_received.append(worker) if self.multicast_services is None or len(self.multicast_already_received) == len(self.multicast_services): self.stop_waiting() elif message_type == b'\x04': # error message worker = message.pop(0) self.on_error_message(worker, message[0]) self.stop_waiting() elif message_type == b'\x05': # multicast start, load list of multicast services self.multicast_services = message self.multicast_already_received = [] self.on_multicast_start(message) elif message_type == b'\x06': # exception message worker = message.pop(0) try: msg = msgpack.unpackb(message[0]) except: msg = {b'class': 'Exception', b'message': 'parsing failed', b'traceback': message[0]} self.on_exception_message(worker, msg[b'class'], msg[b'message'], msg[b'traceback']) if message[0] not in self.multicast_already_received: self.multicast_already_received.append(worker) if self.multicast_services is None or len(self.multicast_already_received) == len(self.multicast_services): self.stop_waiting() #else: unknown type - do nothing return def wait_for_reply(self): """Start waiting for replies """ self.ioloop.start() def stop_waiting(self): """Stop waiting for replies and cancel timeout """ self.ioloop.stop() self.can_send = True self._stop_timeout() def _on_timeout(self): """Helper called after timeout. """ self.timed_out = True self._tmo = None self.ioloop.stop() self.on_timeout() return def _start_timeout(self, timeout): """Helper for starting the timeout. :param timeout: the time to wait in milliseconds. :type timeout: int """ self._tmo = DelayedCallback(self._on_timeout, timeout) self._tmo.start() return def _stop_timeout(self): """Helper for stopping timeout event """ if self._tmo: self._tmo.stop() self._tmo = None def on_message(self, service, msg): """Public method called when a message arrived. .. note:: Does nothing. Should be overloaded! :param service: name of the worker :type service: str :param msg: message :type msg: mixed """ pass def on_partial_message(self, service, msg): """Public method called when a partial message arrived. .. note:: Does nothing. Should be overloaded! :param service: name of the worker :type service: str :param msg: message :type msg: mixed """ pass def on_error_message(self, worker, msg): """Public method called when an error message arrived. .. note:: Does nothing. Should be overloaded! :param service: name of the worker :type service: str :param msg: message :type msg: str """ pass def on_exception_message(self, service, cls, message, traceback): """Public method called when an exception arrived. .. note:: Does nothing. Should be overloaded! :param service: name of the worker :type service: str :param cls: exception class name :type cls: str :param message: error message :type message: str :param traceback: traceback :type traceback: str """ pass def on_timeout(self): """Public method called when a timeout occured. .. note:: Does nothing. Should be overloaded! """ pass def on_multicast_start(self, services): """Public method called when multicast request started .. note:: Does nothing. Should be overloaded! :param services: list of services in multicast group :type services: list """ pass
class DeviceConnection(object): SERVICE_NAME = 'device' # service to connect to TIMEOUT = 5 # time to wait for answer in seconds # Number of connections to try before restarting when in incorrect state CONNECTION_ATTEMPS = 3 HB_INTERVAL = 1000 * 10 # in milliseconds def __init__(self, context, device_id, address): self.context = context self.device_id = device_id self.address = address self.ticker = None self.updater = None self.socket = None self.can_send = False self._restart() self.heartbeat() return def _restart(self): self.shutdown() self.socket = self.context.socket(zmq.REQ) self.socket.setsockopt(zmq.LINGER, 0) self.socket.connect(self.address) self.can_send = True self.connection_attempts = 0 self.device_state = 'unknown' self.ticker = PeriodicCallback(self.heartbeat, DeviceConnection.HB_INTERVAL) self.ticker.start() # delay = seconds_till_next('hour', duration=1) + 2 # 2 second buffer delay = 60 # send update one minute after startup self.updater = DelayedCallback(self.update, delay * 1000) self.updater.start() return def shutdown(self): if self.ticker: self.ticker.stop() self.ticker = None if self.updater: self.updater.stop() if self.socket: self.socket.close() self.socket = None self.can_send = False return def send(self, message): if not self.can_send: self.connection_attempts += 1 logger.error("DeviceConnection is not in state to send") return None else: self.connection_attempts = 0 self.can_send = False logger.debug("DeviceConnection sending {0}".format(message)) reply = mdp_request(self.socket, DeviceConnection.SERVICE_NAME, message, DeviceConnection.TIMEOUT) if reply: logger.debug("DeviceConnection reply received: {0}".format(reply)) self.can_send = True return reply else: # Timeout! Will be restarted at next heartbeat logger.warn("DeviceConnection timeout. Will be restarted at next heartbeat") self.connection_attempts = DeviceConnection.CONNECTION_ATTEMPS return None def heartbeat(self): if self.connection_attempts >= DeviceConnection.CONNECTION_ATTEMPS: logger.warn("DeviceConnection attempts max reached. Restarting connection.") self._restart() message = HeartbeatMessage(self.device_id) reply = self.send([pickle.dumps(message)]) if reply: message = pickle.loads(reply[1]) if message.response == self.device_state: # device state has not changed pass elif message.response == 'unknown': logger.info("connection status set to 'unknown'") self.device_state = 'unknown' elif message.response == 'joined': logger.info("connection status set to 'joined'") self.device_state = 'joined' elif message.response == 'connected': logger.info("connection status set to 'connected'") self.device_state = 'connected' else: self._restart() return def create(self): logger.info("Sending create details for device") device_details = DeviceInfo(self.device_id, 'farm_monitor') device_details.create() message = [pickle.dumps(device_details)] reply = self.send(message) if reply: message = pickle.loads(reply[1]) logger.debug("Update response from {0} :{1}".format(message.source, message.reply)) return def update(self): self.updater.stop() if self.device_state == 'connected': logger.info("Sending update for device") update = DeviceUpdate(self.device_id, 'farm_monitor') update.create() reply = self.send([pickle.dumps(update)]) if reply: message = pickle.loads(reply[1]) logger.debug("Update response from {0} :{1}".format(message.source, message.reply)) # delay = seconds_till_next('hour', duration=1) + 2 # 2 second buffer delay = 60 * 60 # send next update in 60 minutes self.updater = DelayedCallback(self.update, delay * 1000) self.updater.start() return def backup(self): backup_index = create_backup() session = Session() backup = session.query(Backup).filter_by(index=backup_index).first() standalone_configuration = session.query(SystemSetup.standalone_configuration).scalar() if self.device_state == 'connected': failed_attempts = 0 # don't send the file if we are a combined configuration if standalone_configuration: logger.info("Sending device backup file to FarmMonitor") file_size = os.path.getsize(backup.filepath) bytes_sent = 0 zip_file = open(backup.filepath, 'r') while bytes_sent < file_size and failed_attempts < 3: zip_file.seek(bytes_sent) data = zip_file.read(512) logger.debug("Sending {0} bytes of backup file".format(len(data))) message = FileMessage(self.device_id, 'farm_monitor', 'backup') message.set_file(backup.filepath, data) reply = self.send([pickle.dumps(message)]) if reply: # only mark the data received if a reply is recieved bytes_sent += len(data) failed_attempts = 0 reply_message = pickle.loads(reply[1]) logger.debug("Update reply: {0}".format(reply_message.reply)) else: logger.warn("Failed attempt in sending backup file") failed_attempts += 1 zip_file.close() # only send the database object if the file was sent correctly if failed_attempts == 0: logger.info("Sending device backup database object to FarmMonitor") message = FileMessage(self.device_id, 'farm_monitor', 'backup') message.set_db_object(pickle.dumps(backup)) reply = self.send([pickle.dumps(message)]) if reply: reply_message = pickle.loads(reply[1]) logger.debug("Update response: {0}".format(reply_message.reply)) else: logger.warn("To many failed attempts. Backup failed to send to Farm Monitor") session.close() return def perform_update(self, update_id): session = Session() standalone_configuration = session.query(SystemSetup.standalone_configuration).scalar() if self.device_state == 'connected': # request the update db_object logger.info("Requesting update with id: {0} from FarmMonitor".format(update_id)) message = [self.device_id, 'perform_update', 'db_object', update_id] message = FileMessage(self.device_id, 'farm_monitor', 'perform_update') message.set_db_object(update_id) reply = self.send([pickle.dumps(message)]) if reply: reply_message = pickle.loads(reply[1]) logger.debug("Update response from farm_monitor") if reply_message.data: update = pickle.loads(reply_message.data) else: logger.warn("incorrect response in perform_update: {0}".format(reply[0])) session.close() return failed_attempts = 0 # don't request the file if we are a combined configuration if standalone_configuration: if not os.path.exists(update.filepath): os.makedirs(update.filepath) bytes_received = 0 more_to_send = True new_file = open(update.filepath + update.filename, 'a') while more_to_send and failed_attempts < 3: logger.info("Requesting update file from FarmMonitor") message = FileMessage(self.device_id, 'farm_monitor', 'perform_update') message.request_file(update_id, str(bytes_received)) reply = self.send([pickle.dumps(message)]) if reply: reply_message = pickle.loads(reply[1]) logger.debug("Update response from FarmMonitor") failed_attempts = 0 # in this case, reply_message.filepath is really a boolean. # reusing the FileMessage object more_to_send = reply_message.filepath data = reply_message.data new_file.seek(bytes_received) new_file.write(data) bytes_received += len(data) else: logger.warn("Failed attempt in requesting update file") failed_attempts += 1 new_file.close() # compare the hash of the file sha256 = hashlib.sha256() with open(update.filepath + update.filename, 'rb') as f: for block in iter(lambda: f.read(2048), b''): sha256.update(block) if update.sha256 == sha256.hexdigest(): logger.info("Update file successfully received") print('update received good') session.merge(update) session.commit() else: logger.warn("Update file hash does not match. Update failed") session.close() return # now perform the actual update run_update(update.id, 'farm_device') else: logger.warn("No reply when requesting update db_object from FarMonitor.") session.close() return
class MDPWorker(ZMQStream): def __init__(self, broker, service, io_loop=None): """Create and setup an MDP worker. @param broker A string containing the broker's URL @param service A string containing the service name @param io_loop An existing I/O loop object. If None, the default will be used. """ self.service=service self._broker = broker self.ctx = zmq.Context() sock = self.ctx.socket(zmq.DEALER) ZMQStream.__init__(self, sock, io_loop) # last watchdog timer tick self.watchdog = 0 # connection callback one-shot self._conncb = DelayedCallback(self.send_ready, 3000, self.io_loop) # heartbeat callback..runs continuous when connected self._hbcb = PeriodicCallback(self.send_heartbeat, 2000, self.io_loop) # number of connection attempts self._conn_attempt = 0 # waiting to connect state self._waiting_to_connect = True # have we been disconnected? (flags a reconnect attempt) self.disconnected = False # connect the socket and send a READY when the io_loop starts self.connect(self._broker) self._conncb.start() def reset_watchdog(self): """Private method used to reset the HEARTBEAT watchdog """ self.watchdog = time.time() def disconnect(self): """Disconnect from the broker. """ logging.info("Disconnected from broker") self.on_recv(None) # stop message processing self._conncb.stop() # stop any pending reconnect self._hbcb.stop() # stop heartbeats self.disconnected = True self.io_loop.stop() # stop the I/O loop. If it's used by something else, the caller can restart it def reconnect(self): """Try to reconnect to the broker. """ if self.disconnected: # don't try and reconnect, we got an explicit disconnect return logging.info("Attempting to reconnect to broker") self._hbcb.stop() self._conn_attempt = 0 self._waiting_to_connect = True try: self.connect(self._broker) except ZMQError: logging.exception() self.io_loop.stop() return self._conncb.start() def send_ready(self): """Send a READY message. """ if not self._waiting_to_connect: # connected already return if self.disconnected: # don't try and connect, we got an explicit disconnect return logging.debug("Sending READY") if self._conn_attempt >= 10: logging.error("10 connection attempts have failed. Giving up.") return self._conn_attempt += 1 logging.debug("Connection attempt %i" % self._conn_attempt) rdy = [b'', MDPW_VER, b'\x01', self.service] self.on_recv(self.on_message) self.send_multipart(rdy) # There is no reply to READY so # we must assume we are connected unless we see a DISCONNECT self._waiting_to_connect = False self._disconed = False self.reset_watchdog() self._hbcb.start() def send_reply(self, client, msg, partial=False): """Send a reply to a client. This is typically called from on_request() @param client The client identifier as passed to on_request() @param msg The message to send to the client. If this is a list, it's appended to the multipart; otherwise it is converted to a string and sent as a single frame. @param partial If this is True, the message is sent as a PARTIAL and at least one more call must be made to send_reply(). Otherwise a FINAL is sent and not more calls should be made to send_reply() until another request is processed. """ self._hbcb.stop() # stop while sending other messages if partial: rply = [b'', MDPW_VER, b'\x03', client, b''] else: rply = [b'', MDPW_VER, b'\x04', client, b''] if isinstance(msg, list): rply.extend(msg) else: rply.append(msg) try: logging.debug("Sending reply: %s" % msg_str(rply)) self.send_multipart(rply) except BaseException,e: logging.error("Error sending reply: " % e) self._hbcb.start() # restart heartbeats
class MNClient(MN_object): """Class for the MN client side. Thin asynchronous encapsulation of a zmq.REQ socket. Provides a :func:`request` method with optional timeout. :param context: the ZeroMQ context to create the socket in. :type context: zmq.Context :param endpoint: the endpoint to connect to. :type endpoint: str :param service: the service the client should use :type service: str """ _proto_version = CLIENT_PROTO def __init__(self, context, endpoint, service): """Initialize the MNClient. """ self.context = context self.service = service self.endpoint = endpoint self.can_send = True self._proto_prefix = ['', CLIENT_PROTO, service] self._tmo = None self.timed_out = False self._create_stream() return def _create_stream(self): """Helper for opening a stream. """ socket = self.context.socket(zmq.DEALER) ioloop = IOLoop.instance() self.stream = ZMQStream(socket, ioloop) self.stream.on_recv(self._on_message) self.stream.socket.setsockopt(zmq.LINGER, 0) self.stream.connect(self.endpoint) def shutdown(self): """Method to deactivate the client connection completely. Will delete the stream and the underlying socket. .. warning:: The instance MUST not be used after :func:`shutdown` has been called. :rtype: None """ if not self.stream: return self.stream.socket.close() self.stream.close() self.stream = None return def request(self, msg, timeout=None): """Send the given message. :param msg: message parts to send. :type msg: list of str :param timeout: time to wait in milliseconds. :type timeout: int :rtype None: """ if not self.can_send: raise InvalidStateError() if isinstance(msg, bytes): msg = [msg] # prepare full message # to_send = self._proto_prefix[:] # to_send.extend(msg) to_send = msg if self.stream.closed(): self._create_stream() # TODO check this self.stream.send_multipart(to_send) self.can_send = False if timeout: self._start_timeout(timeout) return def _on_timeout(self): """Helper called after timeout. """ self.timed_out = True self._tmo = None self.on_timeout() return def _start_timeout(self, timeout): """Helper for starting the timeout. :param timeout: the time to wait in milliseconds. :type timeout: int """ self._tmo = DelayedCallback(self._on_timeout, timeout) self._tmo.start() return def _on_message(self, msg): """Helper method called on message receive. :param msg: list of message parts. :type msg: list of str """ if self._tmo: # disable timout self._tmo.stop() self._tmo = None # setting state before invoking on_message, so we can request from there self.can_send = True self.on_message(msg) return def on_message(self, msg): """Public method called when a message arrived. .. note:: Does nothing. Should be overloaded! """ pass def on_timeout(self): """Public method called when a timeout occurred. .. note:: Does nothing. Should be overloaded! """ pass
class GrainBinService(object): SERVICE_NAME = 'grain_bin' def __init__(self, context, device_id, address): self.context = context self.address = address self.device_id = device_id self.socket = None # delay = seconds_till_next('hour', duration=1) + 10 # 10 second buffer delay = 120 # 2 minutes after startup self.updater = DelayedCallback(self.update, delay * 1000) self.updater.start() return def shutdown(self): self.updater.stop() return def update(self): self.updater.stop() if internal_dealer.check_connection(): logger.info("Sending update for all grainbins") socket = self.context.socket(zmq.REQ) socket.setsockopt(zmq.LINGER, 0) socket.connect(self.address) update = GrainbinUpdate(self.device_id, 'farm_monitor') update.create() message = [pickle.dumps(update)] reply = mdp_request(socket, 'grainbin', message, 5) if reply: message = pickle.loads(reply[1]) logger.debug("reply from farm monitor for update: {0}".format(message.reply)) # delay = seconds_till_next('hour', duration=1) + 10 # 10 second buffer delay = 60 * 60 # send next update in one hour self.updater = DelayedCallback(self.update, delay * 1000) self.updater.start() return def update_individual(self, bus_number): if internal_dealer.check_connection(): logger.info("Sending update for grainbin: {0}".format(bus_number)) socket = self.context.socket(zmq.REQ) socket.setsockopt(zmq.LINGER, 0) socket.connect(self.address) update = GrainbinUpdate(self.device_id, 'farm_monitor') update.create(bus_number=bus_number) message = [pickle.dumps(update)] reply = mdp_request(socket, 'grainbin', message, 5) if reply: message = pickle.loads(reply[1]) logger.debug("reply from farm monitor for update: {0}".format(message.reply)) return def rescan(self, bus_number): logger.info("sending rescan for grainbin: {0}".format(bus_number)) info = GrainbinInfo(self.device_id, 'farm_monitor') info.create() message = [pickle.dumps(info)] socket = self.context.socket(zmq.REQ) socket.setsockopt(zmq.LINGER, 0) socket.connect(self.address) reply = mdp_request(socket, 'grainbin', message, 5) if reply: message = pickle.loads(reply[1]) logger.debug("reply from farm monitor for rescan: {0}".format(message.reply)) return