class SubSocket(object): """ Subscriber Socket class. This class implements an IPC channel subcriber socker and sets up a callback function for receiving data from that socket that counts how many images it receives during its lifetime. """ def __init__(self, parent, endpoint): """ Initialise IPC channel as a subscriber, and register the callback. :param parent: the class that created this object, a LiveViewer, given so that this object can reference the method in the parent :param endpoint: the URI address of the socket to subscribe to """ self.parent = parent self.endpoint = endpoint self.frame_count = 0 self.channel = IpcTornadoChannel(IpcTornadoChannel.CHANNEL_TYPE_SUB, endpoint=endpoint) self.channel.subscribe() self.channel.connect() # register the get_image method to be called when the ZMQ socket receives a message self.channel.register_callback(self.callback) def callback(self, msg): """ Handle incoming data on the socket. This callback method is called whenever data arrives on the IPC channel socket. Increments the counter, then passes the message on to the image renderer of the parent. :param msg: the multipart message from the IPC channel """ self.frame_count += 1 self.parent.create_image_from_socket(msg) def cleanup(self): """Cleanup channel when the server is closed. Closes the IPC channel socket correctly.""" self.channel.close()
class IpcTornadoClient(object): ENDPOINT_TEMPLATE = "tcp://{IP}:{PORT}" MESSAGE_ID_MAX = 2**32 def __init__(self, ip_address, port): self.logger = logging.getLogger(self.__class__.__name__) self._ip_address = ip_address self._port = port self._parameters = {'status': {'connected': False}} self.ctrl_endpoint = self.ENDPOINT_TEMPLATE.format(IP=ip_address, PORT=port) self.logger.debug("Connecting to client at %s", self.ctrl_endpoint) self.ctrl_channel = IpcTornadoChannel( IpcTornadoChannel.CHANNEL_TYPE_DEALER) self.ctrl_channel.connect(self.ctrl_endpoint) self.ctrl_channel.register_callback(self._callback) self.ctrl_channel.register_monitor(self._monitor_callback) self.message_id = 0 self._lock = RLock() @property def parameters(self): return self._parameters def _monitor_callback(self, msg): # Handle the multi-part message self.logger.debug("Msg received from %s: %s", self.ctrl_endpoint, msg) if msg['event'] == IpcTornadoChannel.CONNECTED: self.logger.debug(" Connected...") self._parameters['status']['connected'] = True if msg['event'] == IpcTornadoChannel.DISCONNECTED: self.logger.debug(" Disconnected...") self._parameters['status']['connected'] = False def _callback(self, msg): # Handle the multi-part message reply = IpcMessage(from_str=msg[0]) if 'request_configuration' in reply.get_msg_val(): self._update_configuration(reply.attrs) if 'status' in reply.get_msg_val(): self._update_status(reply.attrs) def _update_configuration(self, config_msg): params = config_msg['params'] self._parameters['config'] = params def _update_status(self, status_msg): params = status_msg['params'] connected = self._parameters['status']['connected'] params['timestamp'] = status_msg['timestamp'] self._parameters['status'] = params self._parameters['status']['connected'] = connected def connected(self): return self._parameters['status']['connected'] def _send_message(self, msg): msg.set_msg_id(self.message_id) self.message_id = (self.message_id + 1) % self.MESSAGE_ID_MAX self.logger.debug("Sending control message [%s]:\n%s", self.ctrl_endpoint, msg.encode()) with self._lock: self.ctrl_channel.send(msg.encode()) @staticmethod def _raise_reply_error(msg, reply): if reply is not None: raise IpcMessageException( "Request\n%s\nunsuccessful. Got invalid response: %s" % (msg, reply)) else: raise IpcMessageException( "Request\n%s\nunsuccessful. Got no response." % msg) def send_request(self, value): msg = IpcMessage("cmd", value) self._send_message(msg) def send_configuration(self, content, target=None, valid_error=None): msg = IpcMessage("cmd", "configure") if target is not None: msg.set_param(target, content) else: for parameter, value in content.items(): msg.set_param(parameter, value) self._send_message(msg)
class LiveViewProxyNode(object): """ Live View Proxy. Connect to a ZMQ socket from an Odin Data Live View Plugin, saving any frames that arrive in a queue and passing them on to the central proxy controller when needed. """ def __init__(self, name, endpoint, drop_warn_cutoff, callback): """ Initialise a Node for the Adapter. The node should subscribe to a ZMQ socket and pass any frames received at that socket to the main adapter. """ logging.debug("Live View Proxy Node Initialising: %s", endpoint) self.name = name self.endpoint = endpoint self.callback = callback self.dropped_frame_count = 0 self.received_frame_count = 0 self.last_frame = 0 self.current_acq = 0 self.drop_warn_cutoff = drop_warn_cutoff self.drop_unwarn_cutoff = drop_warn_cutoff * 0.75 self.has_warned = False # subscribe to the given socket address. self.channel = IpcTornadoChannel(IpcTornadoChannel.CHANNEL_TYPE_SUB, endpoint=endpoint) self.channel.subscribe() self.channel.connect() # callback is called whenever data 'arrives' at the socket. This is driven by the IOLoop self.channel.register_callback(self.local_callback) self.param_tree = ParameterTree({ 'endpoint': (lambda: self.endpoint, None), 'dropped_frames': (lambda: self.dropped_frame_count, None), 'received_frames': (lambda: self.received_frame_count, None), 'last_frame': (lambda: self.last_frame, None), 'current_acquisition': (lambda: self.current_acq, None), }) logging.debug("Proxy Connected to Socket: %s", endpoint) def cleanup(self): """ Close the Subscriber socket for this node. """ self.channel.close() def local_callback(self, msg): """ Create an instance of the Frame class using the data from the socket. Then, pass the frame on to the adapter to add to the Priority Queue. """ frame = Frame(msg) self.received_frame_count += 1 if frame.num < self.last_frame: # Frames are assumed to be in order for each socket. If a frame suddenly has # a lower frame number, it can be assumed that a new acquisition has begun. logging.debug( "Frame number has reset, new acquisition started on Node %s", self.name) self.current_acq += 1 self.has_warned = False frame.set_acq(self.current_acq) self.last_frame = frame.num self.callback(frame, self) def dropped_frame(self): """ Increase the count of dropped frames due to frame age """ self.dropped_frame_count += 1 current_dropped_percent = float(self.dropped_frame_count) / float( self.received_frame_count) if current_dropped_percent > self.drop_warn_cutoff and not self.has_warned: # If the number of dropped frames reaches a certain percentage threshold of the total # received, warn the user, as it likely means one node is running slow and is unlikely # to display any frames. logging.warning("Node %s has dropped %d%% of frames", self.name, current_dropped_percent * 100) self.has_warned = True elif current_dropped_percent < self.drop_unwarn_cutoff: self.has_warned = False def set_reset(self): """ Reset frame counts for dropped and received frames for starting a new aqquisition """ self.dropped_frame_count = 0 self.received_frame_count = 0 self.last_frame = 0 self.current_acq = 0 self.has_warned = False
class IpcTornadoClient(object): ENDPOINT_TEMPLATE = "tcp://{IP}:{PORT}" MESSAGE_ID_MAX = 2**32 def __init__(self, ip_address, port): self.logger = logging.getLogger(self.__class__.__name__) self._ip_address = ip_address self._port = port self.ctrl_endpoint = self.ENDPOINT_TEMPLATE.format(IP=ip_address, PORT=port) self.logger.debug("Connecting to client at %s", self.ctrl_endpoint) self.ctrl_channel = IpcTornadoChannel( IpcTornadoChannel.CHANNEL_TYPE_DEALER) self.ctrl_channel.connect(self.ctrl_endpoint) self.ctrl_channel.register_callback(self._callback) self._parameters = {} self.message_id = 0 self._lock = RLock() @property def parameters(self): return self._parameters def _callback(self, msg): # Handle the multi-part message self.logger.debug("Msg received: %s", msg) reply = IpcMessage(from_str=msg[0]) if 'request_configuration' in reply.get_msg_val(): self._update_configuration(reply.attrs) if 'status' in reply.get_msg_val(): self._update_status(reply.attrs) def _update_configuration(self, config_msg): params = config_msg['params'] self._parameters['config'] = params logging.debug("Current configuration updated to: %s", self._parameters['config']) def _update_status(self, status_msg): params = status_msg['params'] params['connected'] = True params['timestamp'] = status_msg['timestamp'] self._parameters['status'] = params logging.debug("Status updated to: %s", self._parameters['status']) def check_for_stale_status(self, max_stale_time): if 'status' in self._parameters: # Check for the timestamp if 'timestamp' in self._parameters['status']: ts = datetime.strptime(self._parameters['status']['timestamp'], "%Y-%m-%dT%H:%M:%S.%f") ttlm = datetime.now() - ts if ttlm.seconds > max_stale_time: # This connection has gone stale, set connected to false self._parameters['status'] = {'connected': False} self._parameters['config'] = {} logging.debug("Status updated to: %s", self._parameters['status']) else: # No status for this client so set connected to false self._parameters['status'] = {'connected': False} logging.debug("Status updated to: %s", self._parameters['status']) def _send_message(self, msg): msg.set_msg_id(self.message_id) self.message_id = (self.message_id + 1) % self.MESSAGE_ID_MAX self.logger.debug("Sending control message:\n%s", msg.encode()) with self._lock: self.ctrl_channel.send(msg.encode()) @staticmethod def _raise_reply_error(msg, reply): if reply is not None: raise IpcMessageException( "Request\n%s\nunsuccessful. Got invalid response: %s" % (msg, reply)) else: raise IpcMessageException( "Request\n%s\nunsuccessful. Got no response." % msg) def send_request(self, value): msg = IpcMessage("cmd", value) self._send_message(msg) def send_configuration(self, content, target=None, valid_error=None): msg = IpcMessage("cmd", "configure") if target is not None: msg.set_param(target, content) else: for parameter, value in content.items(): msg.set_param(parameter, value) self._send_message(msg)