def start_processes(self, counter_pv, data_pv, frame_type_pv, logger, *args): """ This function starts processes and callbacks. This is a main thread that starts thread reacting to the callback, starts the consuming process, and sets a callback on the frame counter PV change. The function then awaits for the data in the exit queue that indicates that all frames have been processed. The functin cancells the callback on exit. Parameters ---------- counter_pv : str a PV string for the area detector frame counter data_pv : str a PV string for the area detector frame data frame_type_pv : str a PV string for the area detector data type logger : Logger a Logger instance, typically synchronized with the consuming process logger *args : list a list of arguments specific to the client process Returns ------- None """ data_thread = CAThread(target = self.deliver_data, args=(data_pv, frame_type_pv, logger,)) data_thread.start() adapter.start_process(self.process_dataq, logger, *args) self.cntr_pv = PV(counter_pv) self.cntr_pv.add_callback(self.on_change, index = 1)
def __init__(self, prefix, ad_prefix, stream, enable_callbacks=0, min_cbtime=0, queuesize=5): """ Parameters ---------- prefix: str The plugin prefix that comes after the areaDetector prefix in the PV names. ad_prefix: str The base areaDetector control prefix. This should match a real areaDetector IOC's prefix. stream: str The image stream to use for the plugins. We'll be using: $(ad_prefix)$(stream):ArrayData for values $(ad_prefix)$(stream):UniqueId_RBV for update monitoring enable_callbcaks: bool, optional If True, start the IOC with callbacks enabled. Start disabled otherwise. min_cbtime: float, optional The initial value for the minimum time for each callback loop. queuesize: int, optional The initial value for the array queue. The default is 5. """ self.server = PypvServer(ad_prefix + prefix) self.ad_prefix = ad_prefix self.ad_directory = {} self.settings_lock = RLock() self.plugins = {} self.has_update = Event() self.enable_callbacks = int(enable_callbacks) self.min_cbtime = float(min_cbtime) self.queue = None queuesize = int(queuesize) self._ndarray_port_cb(value=str(stream)) self._add_builtin('NDArrayPort', str(stream), cb=self._ndarray_port_cb) self._enable_callbacks_cb(value=self.enable_callbacks) self._add_builtin('EnableCallbacks', self.enable_callbacks, cb=self._enable_callbacks_cb) self._min_cbtime_cb(value=self.min_cbtime) self._add_builtin('MinCallbackTime', self.min_cbtime, cb=self._min_cbtime_cb) self._queuesize_cb(value=queuesize) self._add_builtin('QueueSize', queuesize, cb=self._queuesize_cb) self._add_builtin('QueueUse', 0) self._add_builtin('DroppedArrays', 0) arrays = CAThread(target=self._array_cb_loop, args=(), daemon=True) plugins = Thread(target=self._get_queue_loop, args=(), daemon=True) arrays.start() plugins.start()
def start_processes(self, acquire_pv, counter_pv_name, data_pv, frame_type_pv, logger, reportq, *args, **kwargs): """ This function starts processes and callbacks. This is a main thread that starts thread reacting to the callback, starts the consuming process, and sets a callback on the frame counter PV change. The function then awaits for the data in the exit queue that indicates that all frames have been processed. The functin cancells the callback on exit. Parameters ---------- counter_pv : str a PV string for the area detector frame counter data_pv : str a PV string for the area detector frame data frame_type_pv : str a PV string for the area detector data type logger : Logger a Logger instance, typically synchronized with the consuming process logger *args : list a list of arguments specific to the client process Returns ------- None """ data_thread = CAThread(target=self.deliver_data, args=(data_pv, frame_type_pv, logger,)) data_thread.start() p = Process(target=handler.handle_data, args=(self.process_dataq, reportq, args, kwargs,)) p.start() self.counter_pv = PV(counter_pv_name) self.counter_pv.add_callback(self.on_ctr_change, index=1) self.acq_pv = PV(acquire_pv) self.acq_pv.add_callback(self.acq_done, index=2) try: callback_pv_name = kwargs['callback_pv'] self.callback_pv = PV(callback_pv_name) self.callback_pv.add_callback(self.on_change, as_string=True, index=3) except KeyError: pass
def setup(self): """Set up the server. Starts threads and registers for EPICS callbacks. """ self._publisher_thread = CAThread(target=self._publisher, args=(self.update_addr,), daemon=True) self._publisher_thread.start() self._request_thread = CAThread(target=self._request_handler, args=(self.request_addr,), daemon=True) self._request_thread.start() for attr in self.robot.attrs: pv = getattr(self.robot, attr) pv.add_callback(self._pv_callback) self.robot.client_update.add_callback(self._on_robot_update) self.logger.debug('setup complete')
def setup(self): """Set up the server. Starts threads and registers for EPICS callbacks. """ self._publisher_thread = CAThread(target=self._publisher, args=(self.update_addr, ), daemon=True) self._publisher_thread.start() self._request_thread = CAThread(target=self._request_handler, args=(self.request_addr, ), daemon=True) self._request_thread.start() for attr in self.robot.attrs: pv = getattr(self.robot, attr) pv.add_callback(self._pv_callback) self.robot.client_update.add_callback(self._on_robot_update) self.logger.debug('setup complete')
def __init__(self, window, Win, first_cavity_id, last_cavity_id, first_phase, last_phase, phase_step, delay_before_scan, delay_read, num_read, mode): CAThread.__init__(self) self.window = window self.Win = Win self.first_cavity_id = first_cavity_id self.last_cavity_id = last_cavity_id self.first_phase = first_phase self.last_phase = last_phase self.phase_step = phase_step self.delay_before_scan = delay_before_scan self.delay_read = delay_read self.num_read = num_read self.timeToQuit = threading.Event() self.timeToPause = threading.Event() self.timeToQuit.clear() self.timeToPause.clear() self.pause = False self.mode = mode
def start_processes(self, counter_pv, data_pv, frame_type_pv, logger, *args): """ This function starts processes and callbacks. This is a main thread that starts thread reacting to the callback, starts the consuming process, and sets a callback on the frame counter PV change. The function then awaits for the data in the exit queue that indicates that all frames have been processed. The functin cancells the callback on exit. Parameters ---------- counter_pv : str a PV string for the area detector frame counter data_pv : str a PV string for the area detector frame data frame_type_pv : str a PV string for the area detector data type logger : Logger a Logger instance, typically synchronized with the consuming process logger *args : list a list of arguments specific to the client process Returns ------- None """ data_thread = CAThread(target=self.deliver_data, args=( data_pv, frame_type_pv, logger, )) data_thread.start() adapter.start_process(self.process_dataq, logger, *args) self.cntr_pv = PV(counter_pv) self.cntr_pv.add_callback(self.on_change, index=1)
def _process_request(self, message): """Parse requests from the clients and take the appropriate action.""" self.logger.debug('client request: %r', message) operation = message.get('operation') parameters = message.get('parameters', {}) try: target = getattr(self, operation) except (AttributeError, TypeError): self.logger.error('operation does not exist: %r', operation) return {'error': 'invalid request: operation does not exist'} try: operation_type = target._operation_type except AttributeError: self.logger.error('%r must be declared an operation', operation) return { 'error': 'invalid request: %r not an operation' % operation } try: sig = inspect.signature(target) if operation_type == 'query': sig.bind(**parameters) else: sig.bind(None, **parameters) # Must accept a handle argument except (ValueError, TypeError): self.logger.error('invalid arguments for operation %r: %r', operation, parameters) return {'error': 'invalid request: incorrect arguments'} self.logger.debug('calling: %r with %r', operation, parameters) if operation_type == 'query': return target(**parameters) elif operation_type in {'foreground', 'background'}: handle = self._next_handle() thread = CAThread(target=target, args=(handle, ), kwargs=parameters, daemon=True) thread.start() return {'error': None, 'handle': handle} else: return {'error': 'invalid request: unknown operation type'}
def start_processes(self): """ This function starts processes and callbacks. This is a main thread that starts thread reacting to the callback, starts the consuming process, and sets a callback on the frame counter PV change. The function then awaits for the data in the exit queue that indicates that all frames have been processed. The functin cancells the callback on exit. Parameters ---------- none Returns ------- nothing """ data_thread = CAThread(target=self.handle_event, args=()) data_thread.start() self.counter_pv = PV(self.get_counter_pv_name()) self.counter_pv.add_callback(self.on_change, index=1) self.acq_pv = PV(self.get_acquire_pv_name()) self.acq_pv.add_callback(self.acq_done, index=2)
def _process_request(self, message): """Parse requests from the clients and take the appropriate action.""" self.logger.debug('client request: %r', message) operation = message.get('operation') parameters = message.get('parameters', {}) try: target = getattr(self, operation) except (AttributeError, TypeError): self.logger.error('operation does not exist: %r', operation) return {'error': 'invalid request: operation does not exist'} try: operation_type = target._operation_type except AttributeError: self.logger.error('%r must be declared an operation', operation) return {'error': 'invalid request: %r not an operation' % operation} try: sig = inspect.signature(target) if operation_type == 'query': sig.bind(**parameters) else: sig.bind(None, **parameters) # Must accept a handle argument except (ValueError, TypeError): self.logger.error('invalid arguments for operation %r: %r', operation, parameters) return {'error': 'invalid request: incorrect arguments'} self.logger.debug('calling: %r with %r', operation, parameters) if operation_type == 'query': return target(**parameters) elif operation_type in {'foreground', 'background'}: handle = self._next_handle() thread = CAThread(target=target, args=(handle,), kwargs=parameters, daemon=True) thread.start() return {'error': None, 'handle': handle} else: return {'error': 'invalid request: unknown operation type'}
def atl(softioc, caclient, tmpdir_factory): AbortCh.fields['ACNT'] = ':ACNT' AbortCh.fields['TCNT'] = ':TCNT' dburi = ('sqlite:///' + str(tmpdir_factory.mktemp('data').join('testdata.db')) ) insert_current_pv_mock(dburi) set_initial_abort_state() atl = Aborttl(dburi, 'ET_dummyHost:RESETw') thread = CAThread(target=atl.run) thread.daemon = True thread.start() time.sleep(5) yield atl atl.stop() thread.join() time.sleep(1)
def test_cathread(): write( 'Test use CAThread\n') th1 = CAThread(target=run_CAThread, args=(names_a, 3, 'A')) th2 = CAThread(target=run_CAThread, args=(names_b, 5, 'B')) run_threads((th1, th2))
class RobotServer(object): """ The ``RobotServer`` monitors the state of the robot and processes operation requests from ``RobotClient``\ s. The robot state is broadcast to clients via a Zero-MQ publish/subscribe channel. Operation requests are received via a seperate request/reply channel. Args: robot (Robot): An instance of the aspyrobot.Robot class. logger: A logging.Logger object. update_addr: An address to create a Zero-MQ socket to broadcast robot state updates to clients. request_addr: An address to create a Zero-MQ socket to receive operation requests from clients. """ def __init__(self, robot, logger=None, update_addr='tcp://*:2000', request_addr='tcp://*:2001'): self.robot = robot self.logger = logger or logging.getLogger(__name__) self.request_addr = request_addr self.update_addr = update_addr self._zmq_context = zmq.Context() self.publish_queue = Queue() self._foreground_lock = Lock() self._operation_handle = 0 self._handle_lock = Lock() self._shutdown_requested = False @withCA def setup(self): """Set up the server. Starts threads and registers for EPICS callbacks. """ self._publisher_thread = CAThread(target=self._publisher, args=(self.update_addr, ), daemon=True) self._publisher_thread.start() self._request_thread = CAThread(target=self._request_handler, args=(self.request_addr, ), daemon=True) self._request_thread.start() for attr in self.robot.attrs: pv = getattr(self.robot, attr) pv.add_callback(self._pv_callback) self.robot.client_update.add_callback(self._on_robot_update) self.logger.debug('setup complete') def shutdown(self): """Request the server shuts down. Causes the publisher and request threads to exit gracefully. """ self._shutdown_requested = True def _pv_callback(self, pvname, value, char_value, type, **kwargs): """When robot PVs change send a value update to clients.""" suffix = pvname.replace(self.robot._prefix, '') attr = self.robot.attrs_r[suffix] if 'char' in type or 'string' in type: value = char_value self.values_update({attr: value}) def _publisher(self, update_addr): """Publish robot state updates to clients over Zero-MQ.""" socket = self._zmq_context.socket(zmq.PUB) socket.bind(update_addr) while not self._shutdown_requested: try: message = self.publish_queue.get(timeout=.1) except Empty: continue data = message.get('data', {}) if not (len(data) == 1 and 'time' in data): # Don't log time messages self.logger.debug('sending to client: %r', message) socket.send_json(message) socket.close() def _request_handler(self, request_addr): """Listen for operation requests from clients.""" socket = self._zmq_context.socket(zmq.REP) socket.bind(request_addr) while not self._shutdown_requested: try: message = socket.recv_json(flags=zmq.NOBLOCK) except zmq.ZMQError: time.sleep(.05) continue response = self._process_request(message) socket.send_json(response) socket.close() def _process_request(self, message): """Parse requests from the clients and take the appropriate action.""" self.logger.debug('client request: %r', message) operation = message.get('operation') parameters = message.get('parameters', {}) try: target = getattr(self, operation) except (AttributeError, TypeError): self.logger.error('operation does not exist: %r', operation) return {'error': 'invalid request: operation does not exist'} try: operation_type = target._operation_type except AttributeError: self.logger.error('%r must be declared an operation', operation) return { 'error': 'invalid request: %r not an operation' % operation } try: sig = inspect.signature(target) if operation_type == 'query': sig.bind(**parameters) else: sig.bind(None, **parameters) # Must accept a handle argument except (ValueError, TypeError): self.logger.error('invalid arguments for operation %r: %r', operation, parameters) return {'error': 'invalid request: incorrect arguments'} self.logger.debug('calling: %r with %r', operation, parameters) if operation_type == 'query': return target(**parameters) elif operation_type in {'foreground', 'background'}: handle = self._next_handle() thread = CAThread(target=target, args=(handle, ), kwargs=parameters, daemon=True) thread.start() return {'error': None, 'handle': handle} else: return {'error': 'invalid request: unknown operation type'} def _next_handle(self): """Generate a new operation handle in a thread safe way.""" with self._handle_lock: self._operation_handle += 1 return self._operation_handle def _on_robot_update(self, char_value, **_): """Handle special update messages from SPEL. These messages use Python dictionary literal syntax and contain a key for the variable to be set. We call `update_x` method with the other key/values from the dictionary supplied as keyword arguments. """ try: message = literal_eval(char_value) except SyntaxError: return self.logger.error('Invalid update message: %r', char_value) attr = message.pop('set') try: method = getattr(self, 'update_' + attr) except AttributeError: return self.logger.warning('Unhandled robot update: %r', char_value) try: method(**message) except TypeError: self.logger.error('Invalid method signature for update: %r', message) def operation_update(self, handle, message='', stage='update', error=None): """Add an operation update to the queue to be sent clients. Args: handle (int): Operation handle. message (str): Message to be sent to clients. stage (str): `'start'`, `'update'` or `'end'` error (str): Error message. """ self.publish_queue.put({ 'type': 'operation', 'stage': stage, 'handle': handle, 'message': message, 'error': error, }) def values_update(self, update): """Add an robot attribute value update to the queue to be sent clients. Args: update (dict): robot attributes and their values. For example: `{'safety_gate': 1, 'motors_on': 0}` """ self.publish_queue.put({'type': 'values', 'data': update}) @query_operation def refresh(self): """Query operation to fetch the latest values of the robot state. This method should be overridden if the server maintains additional state information that needs to be sent to the clients. """ return self.robot.snapshot() @background_operation def clear(self, handle, level): """ Clear robot state. Args: level (str): `'status`' or `'all'` """ self.logger.warning('clear: %r', level) self.robot.run_background_task('ResetRobotStatus', level)
def run_test(runtime=1, pvnames=None, run_name='thread c'): msg = '-> thread "%s" will run for %.3f sec, monitoring %s\n' stdout.write(msg % (run_name, runtime, pvnames)) def onChanges(pvname=None, value=None, char_value=None, **kw): stdout.write(' %s = %s (%s)\n' % (pvname, char_value, run_name)) stdout.flush() # epics.ca.use_initial_context() # epics.ca.create_context() start_time = time.time() pvs = [epics.PV(pvn, callback=onChanges) for pvn in pvnames] while time.time()-start_time < runtime: time.sleep(0.1) [p.clear_callbacks() for p in pvs] stdout.write( 'Completed Thread %s\n' % ( run_name)) stdout.write( "First, create a PV in the main thread:\n") p = epics.PV(updating_pvlist[0]) stdout.write("Run 2 Background Threads simultaneously:\n") th1 = CAThread(target=run_test,args=(3, pvlist_a, 'A')) th1.start() th2 = CAThread(target=run_test,args=(6, pvlist_b, 'B')) th2.start() th2.join() th1.join() stdout.write('Done\n')
# pvs_b.append(pvname) names_b.append(pvname) names_a = names_b[1:] pvs_a = pvs_b[1:] epics.ca.create_context() styles = ('decorator', 'init', 'cathread') style = styles[2] if style == 'init': write( 'Test use plain threading.Thread, force use of initial CA Context \n') th1 = Thread(target=test_initcontext, args=(names_a, 2, 'A')) th2 = Thread(target=test_initcontext, args=(names_b, 3, 'B')) run_threads((th1, th2)) elif style == 'decorator': write('Test use plain threading.Thread, withInitialContext decorator\n') th1 = Thread(target=test_decorator, args=(names_a, 3, 'A')) th2 = Thread(target=test_decorator, args=(names_b, 5, 'B')) run_threads((th1, th2)) elif style == 'cathread': write('Test use CAThread\n') th1 = CAThread(target=test_CAThread, args=(names_a, 3, 'A')) th2 = CAThread(target=test_CAThread, args=(names_b, 5, 'B')) run_threads((th1, th2)) write('Test Done\n---------------------\n')
class RobotServer(object): """ The ``RobotServer`` monitors the state of the robot and processes operation requests from ``RobotClient``\ s. The robot state is broadcast to clients via a Zero-MQ publish/subscribe channel. Operation requests are received via a seperate request/reply channel. Args: robot (Robot): An instance of the aspyrobot.Robot class. logger: A logging.Logger object. update_addr: An address to create a Zero-MQ socket to broadcast robot state updates to clients. request_addr: An address to create a Zero-MQ socket to receive operation requests from clients. """ def __init__(self, robot, logger=None, update_addr='tcp://*:2000', request_addr='tcp://*:2001'): self.robot = robot self.logger = logger or logging.getLogger(__name__) self.request_addr = request_addr self.update_addr = update_addr self._zmq_context = zmq.Context() self.publish_queue = Queue() self._foreground_lock = Lock() self._operation_handle = 0 self._handle_lock = Lock() self._shutdown_requested = False @withCA def setup(self): """Set up the server. Starts threads and registers for EPICS callbacks. """ self._publisher_thread = CAThread(target=self._publisher, args=(self.update_addr,), daemon=True) self._publisher_thread.start() self._request_thread = CAThread(target=self._request_handler, args=(self.request_addr,), daemon=True) self._request_thread.start() for attr in self.robot.attrs: pv = getattr(self.robot, attr) pv.add_callback(self._pv_callback) self.robot.client_update.add_callback(self._on_robot_update) self.logger.debug('setup complete') def shutdown(self): """Request the server shuts down. Causes the publisher and request threads to exit gracefully. """ self._shutdown_requested = True def _pv_callback(self, pvname, value, char_value, type, **kwargs): """When robot PVs change send a value update to clients.""" suffix = pvname.replace(self.robot._prefix, '') attr = self.robot.attrs_r[suffix] if 'char' in type or 'string' in type: value = char_value self.values_update({attr: value}) def _publisher(self, update_addr): """Publish robot state updates to clients over Zero-MQ.""" socket = self._zmq_context.socket(zmq.PUB) socket.bind(update_addr) while not self._shutdown_requested: try: message = self.publish_queue.get(timeout=.1) except Empty: continue data = message.get('data', {}) if not (len(data) == 1 and 'time' in data): # Don't log time messages self.logger.debug('sending to client: %r', message) socket.send_json(message) socket.close() def _request_handler(self, request_addr): """Listen for operation requests from clients.""" socket = self._zmq_context.socket(zmq.REP) socket.bind(request_addr) while not self._shutdown_requested: try: message = socket.recv_json(flags=zmq.NOBLOCK) except zmq.ZMQError: time.sleep(.05) continue response = self._process_request(message) socket.send_json(response) socket.close() def _process_request(self, message): """Parse requests from the clients and take the appropriate action.""" self.logger.debug('client request: %r', message) operation = message.get('operation') parameters = message.get('parameters', {}) try: target = getattr(self, operation) except (AttributeError, TypeError): self.logger.error('operation does not exist: %r', operation) return {'error': 'invalid request: operation does not exist'} try: operation_type = target._operation_type except AttributeError: self.logger.error('%r must be declared an operation', operation) return {'error': 'invalid request: %r not an operation' % operation} try: sig = inspect.signature(target) if operation_type == 'query': sig.bind(**parameters) else: sig.bind(None, **parameters) # Must accept a handle argument except (ValueError, TypeError): self.logger.error('invalid arguments for operation %r: %r', operation, parameters) return {'error': 'invalid request: incorrect arguments'} self.logger.debug('calling: %r with %r', operation, parameters) if operation_type == 'query': return target(**parameters) elif operation_type in {'foreground', 'background'}: handle = self._next_handle() thread = CAThread(target=target, args=(handle,), kwargs=parameters, daemon=True) thread.start() return {'error': None, 'handle': handle} else: return {'error': 'invalid request: unknown operation type'} def _next_handle(self): """Generate a new operation handle in a thread safe way.""" with self._handle_lock: self._operation_handle += 1 return self._operation_handle def _on_robot_update(self, char_value, **_): """Handle special update messages from SPEL. These messages use Python dictionary literal syntax and contain a key for the variable to be set. We call `update_x` method with the other key/values from the dictionary supplied as keyword arguments. """ try: message = literal_eval(char_value) except SyntaxError: return self.logger.error('Invalid update message: %r', char_value) attr = message.pop('set') try: method = getattr(self, 'update_' + attr) except AttributeError: return self.logger.warning('Unhandled robot update: %r', char_value) try: method(**message) except TypeError: self.logger.error('Invalid method signature for update: %r', message) def operation_update(self, handle, message='', stage='update', error=None): """Add an operation update to the queue to be sent clients. Args: handle (int): Operation handle. message (str): Message to be sent to clients. stage (str): `'start'`, `'update'` or `'end'` error (str): Error message. """ self.publish_queue.put({ 'type': 'operation', 'stage': stage, 'handle': handle, 'message': message, 'error': error, }) def values_update(self, update): """Add an robot attribute value update to the queue to be sent clients. Args: update (dict): robot attributes and their values. For example: `{'safety_gate': 1, 'motors_on': 0}` """ self.publish_queue.put({'type': 'values', 'data': update}) @query_operation def refresh(self): """Query operation to fetch the latest values of the robot state. This method should be overridden if the server maintains additional state information that needs to be sent to the clients. """ return self.robot.snapshot() @background_operation def clear(self, handle, level): """ Clear robot state. Args: level (str): `'status`' or `'all'` """ self.logger.warning('clear: %r', level) self.robot.run_background_task('ResetRobotStatus', level)
stdout.flush() # epics.ca.use_initial_context() # epics.ca.create_context() start_time = time.time() pvs = [epics.PV(pvn, callback=onChanges) for pvn in pvnames] while time.time() - start_time < runtime: time.sleep(0.001) [p.clear_callbacks() for p in pvs] stdout.write('Completed Thread %s\n' % (run_name)) stdout.write("First, create a PV in the main thread:\n") for pvname in pvlist_a + pvlist_b: p = epics.PV(pvname) p.connect() p.get() print(p.info) stdout.write("Run 2 Background Threads simultaneously:\n") th1 = CAThread(target=run_test, args=(30, pvlist_a, 'A')) th1.start() th2 = CAThread(target=run_test, args=(60, pvlist_b, 'B')) th2.start() th2.join() th1.join() stdout.write('Done\n')