async def _broadcaster_recv_loop(self, task_status): self.udp_sock = ca.bcast_socket(socket_module=socket) # Must bind or getsocketname() will raise on Windows. # See https://github.com/caproto/caproto/issues/514. self.udp_sock.bind(('', 0)) self.broadcaster.our_address = safe_getsockname(self.udp_sock) command = self.broadcaster.register('127.0.0.1') await self.send(ca.EPICS_CA2_PORT, command) task_status.started() while True: async with self._cleanup_condition: if self._cleanup_event.is_set(): self.udp_sock.close() self.log.debug('Exiting broadcaster recv loop') break try: with trio.fail_after(0.5): bytes_received, address = await self.udp_sock.recvfrom(4096 ) except trio.TooSlowError: continue if bytes_received: if bytes_received is ca.DISCONNECTED: break commands = self.broadcaster.recv(bytes_received, address) await self.command_chan.send.send(commands)
def write(pv_name, data, *, notify=False, data_type=None, metadata=None, timeout=1, priority=0, repeater=True): """ Write to a Channel. Parameters ---------- pv_name : str data : str, int, or float or any Iterable of these Value(s) to write. notify : boolean, optional Request notification of completion and wait for it. False by default. data_type : {'native', 'status', 'time', 'graphic', 'control'} or ChannelType or int ID, optional Write as specific data type. Default is inferred from input. metadata : ``ctypes.BigEndianStructure`` or tuple Status and control metadata for the values timeout : float, optional Default is 1 second. priority : 0, optional Virtual Circuit priority. Default is 0, lowest. Highest is 99. repeater : boolean, optional Spawn a Channel Access Repeater process if the port is available. True default, as the Channel Access spec stipulates that well-behaved clients should do this. Returns ------- initial, final : tuple of ReadNotifyResponse objects Examples -------- Write the value 5 to a Channel named 'cat'. >>> write('cat', 5) # returns None Request notification of completion ("put completion") and wait for it. >>> write('cat', 5, notify=True) # returns a WriteNotifyResponse """ if repeater: # As per the EPICS spec, a well-behaved client should start a # caproto-repeater that will continue running after it exits. spawn_repeater() udp_sock = ca.bcast_socket() try: udp_sock.settimeout(timeout) chan = make_channel(pv_name, udp_sock, priority, timeout) finally: udp_sock.close() try: return _write(chan, data, metadata, timeout, data_type, notify) finally: try: if chan.states[ca.CLIENT] is ca.CONNECTED: send(chan.circuit, chan.clear()) finally: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)]
async def _broadcaster_recv_loop(self, task_status): self.udp_sock = ca.bcast_socket(socket_module=socket) self.broadcaster.our_address = self.udp_sock.getsockname()[:2] command = self.broadcaster.register('127.0.0.1') await self.send(ca.EPICS_CA2_PORT, command) task_status.started() while True: async with self._cleanup_condition: if self._cleanup_event.is_set(): self.udp_sock.close() self.log.debug('Exiting broadcaster recv loop') break try: with trio.fail_after(0.5): bytes_received, address = await self.udp_sock.recvfrom(4096 ) except trio.TooSlowError: continue if bytes_received: if bytes_received is ca.DISCONNECTED: break commands = self.broadcaster.recv(bytes_received, address) await self.command_chan.send.send(commands)
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Curio server starting up...') try: for address in ca.get_beacon_address_list(): sock = ca.bcast_socket(socket) await sock.connect(address) interface, _ = sock.getsockname() self.beacon_socks[address] = (interface, sock) async def make_socket(interface, port): return curio.network.tcp_server_socket(interface, port) self.port, self.tcp_sockets = await self._bind_tcp_sockets_with_consistent_port_number( make_socket) async with curio.TaskGroup() as self._task_group: g = self._task_group for interface, sock in self.tcp_sockets.items(): # Use run_server instead of tcp_server so we can hand in a # socket that is already bound, avoiding a race between the # moment we check for port availability and the moment the # TCP server binds. self.log.info("Listening on %s:%d", interface, self.port) await g.spawn(curio.network.run_server, sock, self.tcp_handler) await g.spawn(self._await_stop) await g.spawn(self.broadcaster_udp_server_loop) await g.spawn(self.broadcaster_queue_loop) await g.spawn(self.subscription_queue_loop) await g.spawn(self.broadcast_beacon_loop) async_lib = CurioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) await g.spawn(method, async_lib) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) self._log_task_group_exceptions(self._task_group) except curio.TaskCancelled as ex: self.log.info('Server task cancelled. Must shut down.') raise ServerExit() from ex finally: self.log.info('Server exiting....') async_lib = CurioAsyncLayer() async with curio.TaskGroup() as task_group: for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) await task_group.spawn(method, async_lib) self._log_task_group_exceptions(task_group) for sock in self.tcp_sockets.values(): await sock.close() for sock in self.udp_socks.values(): await sock.close() for _interface, sock in self.beacon_socks.values(): await sock.close() self._task_group = None
def __init__(self, pvdb, interfaces=None): super().__init__(pvdb, interfaces) self.nursery = None self.command_chan = open_memory_channel(ca.MAX_COMMAND_BACKLOG) self.command_bundle_queue = self.command_chan.send self.subscription_chan = open_memory_channel(ca.MAX_TOTAL_SUBSCRIPTION_BACKLOG) self.subscription_queue = self.subscription_chan.send self.beacon_sock = ca.bcast_socket(socket)
def read(pv_name, *, data_type=None, timeout=1, priority=0, notify=True, force_int_enums=False, repeater=True): """ Read a Channel. Parameters ---------- pv_name : str data_type : {'native', 'status', 'time', 'graphic', 'control'} or ChannelType or int ID, optional Request specific data type or a class of data types, matched to the channel's native data type. Default is Channel's native data type. timeout : float, optional Default is 1 second. priority : 0, optional Virtual Circuit priority. Default is 0, lowest. Highest is 99. notify : boolean, optional Send a ReadNotifyRequest instead of a ReadRequest. True by default. force_int_enums : boolean, optional Retrieve enums as integers. (Default is strings.) repeater : boolean, optional Spawn a Channel Access Repeater process if the port is available. True default, as the Channel Access spec stipulates that well-behaved clients should do this. Returns ------- response : ReadResponse or ReadNotifyResponse Examples -------- Get the value of a Channel named 'cat'. >>> read('cat').data """ if repeater: # As per the EPICS spec, a well-behaved client should start a # caproto-repeater that will continue running after it exits. spawn_repeater() udp_sock = ca.bcast_socket() try: udp_sock.settimeout(timeout) chan = make_channel(pv_name, udp_sock, priority, timeout) finally: udp_sock.close() try: return _read(chan, timeout, data_type=data_type, notify=notify, force_int_enums=force_int_enums) finally: try: if chan.states[ca.CLIENT] is ca.CONNECTED: send(chan.circuit, chan.clear()) finally: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)]
async def check_repeater(): for pv in (ioc.pvs['float'], ioc.pvs['str']): data = await run_caget('curio', pv) print(data) udp_sock = ca.bcast_socket() for i in range(3): print('Sending repeater register request ({})'.format(i + 1)) udp_sock.sendto(bytes(ca.RepeaterRegisterRequest('0.0.0.0')), ('127.0.0.1', REPEATER_PORT)) await curio.sleep(1)
def __init__(self): self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT) self.log = self.broadcaster.log self.command_bundle_queue = curio.Queue() self.broadcaster_command_condition = curio.Condition() # UDP socket broadcasting to CA servers self.udp_sock = ca.bcast_socket(socket) self.registered = False # refers to RepeaterRegisterRequest self.loop_ready_event = curio.Event() self.unanswered_searches = {} # map search id (cid) to name self.search_results = {} # map name to address
async def broadcaster_udp_server_loop(self): self.udp_sock = ca.bcast_socket(socket) try: self.udp_sock.bind((self.host, ca.EPICS_CA1_PORT)) except Exception: logger.error('[server] udp bind failure!') raise while True: bytes_received, address = await self.udp_sock.recvfrom(4096) if bytes_received: commands = self.broadcaster.recv(bytes_received, address) await self.command_bundle_queue.put((address, commands))
async def check_repeater(): for pv in ( "XF:31IDA-OP{Tbl-Ax:X1}Mtr.VAL", "XF:31IDA-OP{Tbl-Ax:X2}Mtr.VAL", ): data = await run_caget(pv) print(data) udp_sock = ca.bcast_socket() for i in range(3): print('Sending repeater register request ({})'.format(i + 1)) udp_sock.sendto(bytes(ca.RepeaterRegisterRequest('0.0.0.0')), ('127.0.0.1', REPEATER_PORT)) await curio.sleep(1)
async def broadcaster_udp_server_loop(self): for interface in self.interfaces: udp_sock = ca.bcast_socket(socket) try: udp_sock.bind((interface, ca.EPICS_CA1_PORT)) except Exception: self.log.exception('UDP bind failure on interface %r', interface) raise self.udp_socks[interface] = udp_sock async with curio.TaskGroup() as g: for interface, udp_sock in self.udp_socks.items(): self.log.debug('Broadcasting on %s:%d', interface, ca.EPICS_CA1_PORT) await g.spawn(self._core_broadcaster_loop, udp_sock)
def __init__(self): self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT) self.log = self.broadcaster.log self.command_bundle_queue = curio.Queue() self.broadcaster_command_condition = curio.Condition() # UDP socket broadcasting to CA servers self.udp_sock = ca.bcast_socket(socket) self.broadcaster.our_address = safe_getsockname(self.udp_sock) self.registered = False # refers to RepeaterRegisterRequest self.loop_ready_event = curio.Event() self.unanswered_searches = {} # map search id (cid) to name self.search_results = {} # map name to address self.environ = ca.get_environment_variables() self.ca_server_port = self.environ['EPICS_CA_SERVER_PORT']
def make_broadcaster_socket() -> Tuple[socket.socket, int]: """ Make and bind a broadcaster socket. Returns ------- udp_sock : socket.socket The UDP socket. port : int The bound port. """ udp_sock = bcast_socket() udp_sock.bind(('', 0)) port = udp_sock.getsockname()[1] logger.debug('Bound to UDP port %d for search', port) return udp_sock, port
async def broadcaster_udp_server_loop(self, task_status): for interface in self.interfaces: udp_sock = ca.bcast_socket(socket) try: await udp_sock.bind((interface, ca.EPICS_CA1_PORT)) except Exception: self.log.exception('UDP bind failure on interface %r', interface) raise self.udp_socks[interface] = udp_sock for interface, udp_sock in self.udp_socks.items(): self.log.debug('Broadcasting on %s:%d', interface, ca.EPICS_CA1_PORT) self.nursery.start_soon(self._core_broadcaster_loop, udp_sock) task_status.started()
async def broadcaster_udp_server_loop(self): for interface in self.interfaces: udp_sock = ca.bcast_socket(socket) self.broadcaster.server_addresses.append(udp_sock.getsockname()) try: udp_sock.bind((interface, self.ca_server_port)) except Exception: self.log.exception('UDP bind failure on interface %r:%d', interface, self.ca_server_port) raise self.log.debug('UDP socket bound on %s:%d', interface, self.ca_server_port) self.udp_socks[interface] = udp_sock async with curio.TaskGroup() as g: for interface, udp_sock in self.udp_socks.items(): self.log.debug('Broadcasting on %s:%d', interface, self.ca_server_port) await g.spawn(self._core_broadcaster_loop, udp_sock)
async def broadcaster_udp_server_loop(self, task_status): for interface in self.interfaces: udp_sock = ca.bcast_socket(socket) self.broadcaster._our_addresses.append(udp_sock.getsockname()[:2]) try: await udp_sock.bind((interface, self.ca_server_port)) except Exception: self.log.exception('UDP bind failure on interface %r', interface) raise self.log.debug('UDP socket bound on %s:%d', interface, self.ca_server_port) self.udp_socks[interface] = udp_sock for interface, udp_sock in self.udp_socks.items(): self.log.debug('Broadcasting on %s:%d', interface, self.ca_server_port) self.nursery.start_soon(self._core_broadcaster_loop, udp_sock) task_status.started()
def __create_sock(self): # UDP socket broadcasting to CA servers self.udp_sock = ca.bcast_socket() self.sock_thread = SocketThread(self.udp_sock, self)
def search(*pvs): '''Search for a PV over the network by broadcasting over UDP Returns: (host, port) ''' udp_sock = bcast_socket() udp_sock.bind(('', 0)) port = udp_sock.getsockname()[1] seq_id = random.randint(1, 2 ** 31) search_ids = {pv: random.randint(1, 2 ** 31) for pv in pvs} search_req = pva.SearchRequestLE( sequence_id=seq_id, flags=(pva.SearchFlags.reply_required | pva.SearchFlags.broadcast), response_address='127.0.0.1', # TODO host ip response_port=port, protocols=['tcp'], channels=[{'id': search_ids[pv], 'channel_name': pv} for pv in pvs] ) # NOTE: cache needed here to give interface for channels cache = pva.CacheContext() payload = search_req.serialize(cache=cache) header = pva.MessageHeaderLE( flags=(pva.MessageFlags.APP_MESSAGE | pva.MessageFlags.FROM_CLIENT | pva.MessageFlags.LITTLE_ENDIAN), command=search_req.ID, payload_size=len(payload) ) for addr, bcast_addr in get_netifaces_addresses(): search_req.response_address = addr bytes_to_send = bytes(header) + search_req.serialize(cache=cache) dest = (addr, pva.PVA_BROADCAST_PORT) print('Sending SearchRequest to', bcast_addr, 'requesting response at {}:{}'.format(addr, port)) udp_sock.sendto(bytes_to_send, dest) response_data, addr = udp_sock.recvfrom(1024) response_data = bytearray(response_data) print('Received from', addr, ':', response_data) response_header, buf, offset = pva.MessageHeaderLE.deserialize( response_data, cache=pva.NullCache) assert response_header.valid msg_class = response_header.get_message( pva.MessageFlags.FROM_SERVER, use_fixed_byte_order=pva.LITTLE_ENDIAN) print('Response header:', response_header) print('Response msg class:', msg_class) msg, buf, off = msg_class.deserialize(buf, cache=pva.NullCache) offset += off print('Response message:', msg) assert offset == len(response_data) if msg.found: id_to_pv = {id_: pv for pv, id_ in search_ids.items()} found_pv = id_to_pv[msg.search_instance_ids[0]] print('Found {} on {}:{}!' ''.format(found_pv, msg.server_address, msg.server_port)) return (msg.server_address, msg.server_port) else: # TODO as a simple client, this only grabs the first response from # the quickest server, which is clearly not the right way to do it raise ValueError('PVs {} not found in brief search' ''.format(pvs))
def read_write_read(pv_name, data, *, notify=False, read_data_type=None, write_data_type=None, metadata=None, timeout=1, priority=0, force_int_enums=False, repeater=True): """ Write to a Channel, but sandwich the write between to reads. This is what the command-line utilities ``caproto-put`` and ``caput`` do. Notice that if you want the second reading to reflect the written value, you should pass the parameter ``notify=True``. (This is also true of ``caproto-put``/``caput``, which needs the ``-c`` argument to behave the way you might expect it to behave.) This is provided as a separate function in order to support ``caproto-put`` efficiently. Making separate calls to :func:`read` and :func:`write` would re-create a connection redundantly. Parameters ---------- pv_name : str The PV name to write/read/write data : str, bytes, int, or float or any Iterable of these Value to write. notify : boolean, optional Request notification of completion and wait for it. False by default. read_data_type : {'native', 'status', 'time', 'graphic', 'control'} or ChannelType or int ID, optional Request specific data type. write_data_type : {'native', 'status', 'time', 'graphic', 'control'} or ChannelType or int ID, optional Write as specific data type. Default is inferred from input. metadata : ``ctypes.BigEndianStructure`` or tuple Status and control metadata for the values timeout : float, optional Default is 1 second. priority : 0, optional Virtual Circuit priority. Default is 0, lowest. Highest is 99. force_int_enums : boolean, optional Retrieve enums as integers. (Default is strings.) repeater : boolean, optional Spawn a Channel Access Repeater process if the port is available. True default, as the Channel Access spec stipulates that well-behaved clients should do this. Returns ------- initial, write_response, final : tuple of response The middle response comes from the write, and it will be ``None`` unless ``notify=True``. Examples -------- Write the value 5 to a Channel named 'simple:A'. >>> read_write_read('cat', 5) # returns initial, None, final Request notification of completion ("put completion") and wait for it. >>> read_write_read('cat', 5, notify=True) # initial, WriteNotifyResponse, final """ if repeater: # As per the EPICS spec, a well-behaved client should start a # caproto-repeater that will continue running after it exits. spawn_repeater() udp_sock = ca.bcast_socket() # Must bind or getsocketname() will raise on Windows. # See https://github.com/caproto/caproto/issues/514. udp_sock.bind(('', 0)) try: udp_sock.settimeout(timeout) chan = make_channel(pv_name, udp_sock, priority, timeout) finally: udp_sock.close() try: initial = _read(chan, timeout, read_data_type, None, notify=True, force_int_enums=force_int_enums) res = _write(chan, data, metadata, timeout, write_data_type, notify) final = _read(chan, timeout, read_data_type, None, notify=True, force_int_enums=force_int_enums) finally: try: if chan.states[ca.CLIENT] is ca.CONNECTED: send(chan.circuit, chan.clear(), chan.name) finally: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)] return initial, res, final
def write(pv_name, data, *, notify=False, data_type=None, metadata=None, timeout=1, priority=0, repeater=True): """ Write to a Channel. Parameters ---------- pv_name : str The PV name to write to data : str, bytes, int, or float or any Iterable of these Value(s) to write. notify : boolean, optional Request notification of completion and wait for it. False by default. data_type : {'native', 'status', 'time', 'graphic', 'control'} or ChannelType or int ID, optional Write as specific data type. Default is inferred from input. metadata : ``ctypes.BigEndianStructure`` or tuple Status and control metadata for the values timeout : float, optional Default is 1 second. priority : 0, optional Virtual Circuit priority. Default is 0, lowest. Highest is 99. repeater : boolean, optional Spawn a Channel Access Repeater process if the port is available. True default, as the Channel Access spec stipulates that well-behaved clients should do this. Returns ------- initial, final : tuple of ReadNotifyResponse objects Examples -------- Write the value 5 to a Channel named 'simple:A'. >>> write('simple:A', 5) # returns None Request notification of completion ("put completion") and wait for it. >>> write('cat', 5, notify=True) # blocks until complete, then returns: WriteNotifyResponse( data_type=<ChannelType.LONG: 5>, data_count=1, status=CAStatusCode( name='ECA_NORMAL', code=0, code_with_severity=1, severity=<CASeverity.SUCCESS: 1>, success=1, defunct=False, description='Normal successful completion'), ioid=0) """ if repeater: # As per the EPICS spec, a well-behaved client should start a # caproto-repeater that will continue running after it exits. spawn_repeater() udp_sock = ca.bcast_socket() # Must bind or getsocketname() will raise on Windows. # See https://github.com/caproto/caproto/issues/514. udp_sock.bind(('', 0)) try: udp_sock.settimeout(timeout) chan = make_channel(pv_name, udp_sock, priority, timeout) finally: udp_sock.close() try: return _write(chan, data, metadata, timeout, data_type, notify) finally: try: if chan.states[ca.CLIENT] is ca.CONNECTED: send(chan.circuit, chan.clear(), chan.name) finally: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)]
def block(*subscriptions, duration=None, timeout=1, force_int_enums=False, repeater=True): """ Activate one or more subscriptions and process incoming responses. Use Ctrl+C (SIGINT) to escape, or from another thread, call :func:`interrupt()`. Parameters ---------- *subscriptions : Subscriptions The list of subscriptions. duration : float, optional How many seconds to run for. Run forever (None) by default. timeout : float, optional Default is 1 second. This is not the same as `for`; this is the timeout for failure in the event of no connection. force_int_enums : boolean, optional Retrieve enums as integers. (Default is strings.) repeater : boolean, optional Spawn a Channel Access Repeater process if the port is available. True default, as the Channel Access spec stipulates that well-behaved clients should do this. Examples -------- Activate subscription(s) and block while they process updates. >>> sub1 = subscribe('cat') >>> sub1 = subscribe('dog') >>> block(sub1, sub2) """ _permission_to_block.append(object()) if duration is not None: deadline = time.time() + duration else: deadline = None if repeater: # As per the EPICS spec, a well-behaved client should start a # caproto-repeater that will continue running after it exits. spawn_repeater() loggers = {} for sub in subscriptions: loggers[sub.pv_name] = logging.LoggerAdapter( logging.getLogger('caproto.ch'), {'pv': sub.pv_name}) udp_sock = ca.bcast_socket() # Must bind or getsocketname() will raise on Windows. # See https://github.com/caproto/caproto/issues/514. udp_sock.bind(('', 0)) try: udp_sock.settimeout(timeout) channels = {} for sub in subscriptions: pv_name = sub.pv_name chan = make_channel(pv_name, udp_sock, sub.priority, timeout) channels[sub] = chan finally: udp_sock.close() try: # Subscribe to all the channels. sub_ids = {} for sub, chan in channels.items(): loggers[chan.name].debug("Detected native data_type %r.", chan.native_data_type) # abundance of caution ntype = field_types['native'][chan.native_data_type] if ((ntype is ChannelType.ENUM) and (not force_int_enums)): ntype = ChannelType.STRING time_type = field_types['time'][ntype] # Adjust the timeout during monitoring. sockets[chan.circuit].settimeout(None) loggers[chan.name].debug("Subscribing with data_type %r.", time_type) req = chan.subscribe(data_type=time_type, data_count=sub.data_count, mask=sub.mask) send(chan.circuit, req, chan.name) sub_ids[(chan.circuit, req.subscriptionid)] = sub logger.debug('Subscribed. Building socket selector.') try: circuits = set(chan.circuit for chan in channels.values()) selector = selectors.DefaultSelector() sock_to_circuit = {} for circuit in circuits: sock = sockets[circuit] sock_to_circuit[sock] = circuit selector.register(sock, selectors.EVENT_READ) if duration is None: logger.debug('Continuing until SIGINT is received....') while True: events = selector.select(timeout=0.1) if deadline is not None and time.time() > deadline: logger.debug('Deadline reached.') return if not _permission_to_block: logger.debug("Interrupted via " "caproto.sync.client.interrupt().") break for selector_key, _ in events: circuit = sock_to_circuit[selector_key.fileobj] commands = recv(circuit) for response in commands: if isinstance(response, ca.ErrorResponse): raise ErrorResponseReceived(response) if response is ca.DISCONNECTED: # TODO Re-connect. raise CaprotoError("Disconnected") sub = sub_ids.get((circuit, response.subscriptionid)) if sub: sub.process(response) except KeyboardInterrupt: logger.debug('Received SIGINT. Closing.') pass finally: _permission_to_block.clear() try: for chan in channels.values(): if chan.states[ca.CLIENT] is ca.CONNECTED: send(chan.circuit, chan.clear(), chan.name) finally: # Reinstate the timeout for channel cleanup. for chan in channels.values(): sockets[chan.circuit].settimeout(timeout) sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)]
def read(pv_name, *, data_type=None, data_count=None, timeout=1, priority=0, notify=True, force_int_enums=False, repeater=True): """ Read a Channel. Parameters ---------- pv_name : str The PV name to read from data_type : {'native', 'status', 'time', 'graphic', 'control'} or ChannelType or int ID, optional Request specific data type or a class of data types, matched to the channel's native data type. Default is Channel's native data type. data_count : integer, optional Requested number of values. Default is the channel's native data count. timeout : float, optional Default is 1 second. priority : 0, optional Virtual Circuit priority. Default is 0, lowest. Highest is 99. notify : boolean, optional Send a ReadNotifyRequest instead of a ReadRequest. True by default. force_int_enums : boolean, optional Retrieve enums as integers. (Default is strings.) repeater : boolean, optional Spawn a Channel Access Repeater process if the port is available. True default, as the Channel Access spec stipulates that well-behaved clients should do this. Returns ------- response : ReadResponse or ReadNotifyResponse Examples -------- Get the value of a Channel named 'simple:A'. >>> read('simple:A').data array([1], dtype=int32) Request a richer Channel Access data type that includes the timestamp, and access the timestamp. >>> read('cat', data_type='time').metadata.timestmap 1570622339.042392 A convenience method is provided for access the timestamp as a Python datetime object. >>> read('cat' data_type='time').metadata.stamp.as_datetime() datetime.datetime(2019, 10, 9, 11, 58, 59, 42392) The requested data type may also been given as a specific Channel Access type >>> from caproto import ChannelType >>> read('cat', data_type=ChannelType.CTRL_FLOAT).metadata DBR_CTRL_FLOAT( status=<AlarmStatus.NO_ALARM: 0>, severity=<AlarmSeverity.NO_ALARM: 0>, upper_disp_limit=0.0, lower_disp_limit=0.0, upper_alarm_limit=0.0, upper_warning_limit=0.0, lower_warning_limit=0.0, lower_alarm_limit=0.0, upper_ctrl_limit=0.0, lower_ctrl_limit=0.0, precision=0, units=b'') or the corresponding integer identifer >>> read('cat', data_type=30).metadata DBR_CTRL_FLOAT( status=<AlarmStatus.NO_ALARM: 0>, severity=<AlarmSeverity.NO_ALARM: 0>, upper_disp_limit=0.0, lower_disp_limit=0.0, upper_alarm_limit=0.0, upper_warning_limit=0.0, lower_warning_limit=0.0, lower_alarm_limit=0.0, upper_ctrl_limit=0.0, lower_ctrl_limit=0.0, precision=0, units=b'') """ if repeater: # As per the EPICS spec, a well-behaved client should start a # caproto-repeater that will continue running after it exits. spawn_repeater() udp_sock = ca.bcast_socket() # Must bind or getsocketname() will raise on Windows. # See https://github.com/caproto/caproto/issues/514. udp_sock.bind(('', 0)) try: udp_sock.settimeout(timeout) chan = make_channel(pv_name, udp_sock, priority, timeout) finally: udp_sock.close() try: return _read(chan, timeout, data_type=data_type, data_count=data_count, notify=notify, force_int_enums=force_int_enums) finally: try: if chan.states[ca.CLIENT] is ca.CONNECTED: send(chan.circuit, chan.clear(), chan.name) finally: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)]
def __init__(self, pvdb, interfaces=None): super().__init__(pvdb, interfaces) self.nursery = None self.command_bundle_queue = trio.Queue(1000) self.subscription_queue = trio.Queue(1000) self.beacon_sock = ca.bcast_socket(socket)
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Server starting up...') try: async with trio.open_nursery() as self.nursery: for address in ca.get_beacon_address_list(): sock = ca.bcast_socket(socket) await sock.connect(address) interface, _ = sock.getsockname() self.beacon_socks[address] = (interface, sock) # This reproduces the common with # self._bind_tcp_sockets_with_consistent_port_number because # trio makes socket binding async where asyncio and curio make # it synchronous. # Find a random port number that is free on all interfaces, # and get a bound TCP socket with that port number on each # interface. tcp_sockets = {} # maps interface to bound socket stashed_ex = None for port in ca.random_ports(100): try: for interface in self.interfaces: s = trio.socket.socket() await s.bind((interface, port)) tcp_sockets[interface] = s except IOError as ex: stashed_ex = ex for s in tcp_sockets.values(): s.close() else: self.port = port break else: raise RuntimeError('No available ports and/or bind failed' ) from stashed_ex # (End of reproduced code) for interface, listen_sock in tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) await self.nursery.start(self.server_accept_loop, listen_sock) await self.nursery.start(self.broadcaster_udp_server_loop) await self.nursery.start(self.broadcaster_queue_loop) await self.nursery.start(self.subscription_queue_loop) await self.nursery.start(self.broadcast_beacon_loop) async_lib = TrioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) async def startup(task_status): task_status.started() await method(async_lib) await self.nursery.start(startup) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) except trio.Cancelled: self.log.info('Server task cancelled. Will shut down.') finally: self.log.info('Server exiting....')
def main(*, skip_monitor_section=False): # A broadcast socket udp_sock = ca.bcast_socket() # Register with the repeater. bytes_to_send = b.send(ca.RepeaterRegisterRequest('0.0.0.0')) # TODO: for test environment with specific hosts listed in # EPICS_CA_ADDR_LIST if False: fake_reg = (('127.0.0.1', ca.EPICS_CA1_PORT), [ca.RepeaterConfirmResponse(repeater_address='127.0.0.1')]) b.command_queue.put(fake_reg) else: udp_sock.sendto(bytes_to_send, ('', CA_REPEATER_PORT)) # Receive response data, address = udp_sock.recvfrom(1024) commands = b.recv(data, address) b.process_commands(commands) # Search for pv1. # CA requires us to send a VersionRequest and a SearchRequest bundled into # one datagram. bytes_to_send = b.send(ca.VersionRequest(0, 13), ca.SearchRequest(pv1, 0, 13)) for host in ca.get_address_list(): if ':' in host: host, _, specified_port = host.partition(':') udp_sock.sendto(bytes_to_send, (host, int(specified_port))) else: udp_sock.sendto(bytes_to_send, (host, CA_SERVER_PORT)) print('searching for %s' % pv1) # Receive a VersionResponse and SearchResponse. bytes_received, address = udp_sock.recvfrom(1024) commands = b.recv(bytes_received, address) b.process_commands(commands) c1, c2 = commands assert type(c1) is ca.VersionResponse assert type(c2) is ca.SearchResponse address = ca.extract_address(c2) circuit = ca.VirtualCircuit(our_role=ca.CLIENT, address=address, priority=0) circuit.log.setLevel('DEBUG') chan1 = ca.ClientChannel(pv1, circuit) sockets[chan1.circuit] = socket.create_connection(chan1.circuit.address) # Initialize our new TCP-based CA connection with a VersionRequest. send(chan1.circuit, ca.VersionRequest(priority=0, version=13)) recv(chan1.circuit) # Send info about us. send(chan1.circuit, ca.HostNameRequest('localhost')) send(chan1.circuit, ca.ClientNameRequest('username')) send(chan1.circuit, ca.CreateChanRequest(name=pv1, cid=chan1.cid, version=13)) commands = recv(chan1.circuit) # Test subscriptions. assert chan1.native_data_type and chan1.native_data_count add_req = ca.EventAddRequest(data_type=chan1.native_data_type, data_count=chan1.native_data_count, sid=chan1.sid, subscriptionid=0, low=0, high=0, to=0, mask=1) send(chan1.circuit, add_req) commands = recv(chan1.circuit) if not skip_monitor_section: try: print('Monitoring until Ctrl-C is hit. Meanwhile, use caput to ' 'change the value and watch for commands to arrive here.') while True: commands = recv(chan1.circuit) if commands: print(commands) except KeyboardInterrupt: pass cancel_req = ca.EventCancelRequest(data_type=add_req.data_type, sid=add_req.sid, subscriptionid=add_req.subscriptionid) send(chan1.circuit, cancel_req) commands, = recv(chan1.circuit) # Test reading. send( chan1.circuit, ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=12)) commands, = recv(chan1.circuit) # Test writing. request = ca.WriteNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=13, data=(4, )) send(chan1.circuit, request) recv(chan1.circuit) time.sleep(2) send( chan1.circuit, ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=14)) recv(chan1.circuit) # Test "clearing" (closing) the channel. send(chan1.circuit, ca.ClearChannelRequest(chan1.sid, chan1.cid)) recv(chan1.circuit) sockets.pop(chan1.circuit).close() udp_sock.close()
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Trio server starting up...') try: async with trio.open_nursery() as self.nursery: for address in ca.get_beacon_address_list(): sock = ca.bcast_socket(socket) await sock.connect(address) interface, _ = sock.getsockname() self.beacon_socks[address] = (interface, sock) async def make_socket(interface, port): s = trio.socket.socket() await s.bind((interface, port)) return s res = await self._bind_tcp_sockets_with_consistent_port_number( make_socket) self.port, self.tcp_sockets = res await self.nursery.start(self.broadcaster_udp_server_loop) await self.nursery.start(self.broadcaster_queue_loop) await self.nursery.start(self.subscription_queue_loop) await self.nursery.start(self.broadcast_beacon_loop) # Only after all loops have been started, begin listening: for interface, listen_sock in self.tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) await self.nursery.start(self.server_accept_loop, listen_sock) async_lib = TrioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) async def startup(task_status): task_status.started() await method(async_lib) await self.nursery.start(startup) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) except trio.Cancelled: self.log.info('Server task cancelled. Will shut down.') finally: self.log.info('Server exiting....') async_lib = TrioAsyncLayer() async with trio.open_nursery() as nursery: for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) async def shutdown(task_status): task_status.started() await method(async_lib) await nursery.start(shutdown) for sock in self.tcp_sockets.values(): sock.close() for sock in self.udp_socks.values(): sock.close() for interface, sock in self.beacon_socks.values(): sock.close()
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Server starting up...') def make_socket(interface, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setblocking(False) s.bind((interface, port)) return s port, tcp_sockets = self._bind_tcp_sockets_with_consistent_port_number( make_socket) self.port = port tasks = [] for interface, sock in tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) tasks.append(self.loop.create_task(self.server_accept_loop(sock))) class BcastLoop(asyncio.Protocol): parent = self loop = self.loop def __init__(self, *args, **kwargs): self.transport = None self._tasks = () def connection_made(self, transport): self.transport = transport def datagram_received(self, data, addr): tsk = self.loop.create_task(self.parent._broadcaster_recv_datagram( data, addr)) self._tasks = tuple(t for t in self._tasks + (tsk,) if not t.done()) class TransportWrapper: """Make an asyncio transport something you can call sendto on.""" def __init__(self, transport): self.transport = transport async def sendto(self, bytes_to_send, addr_port): self.transport.sendto(bytes_to_send, addr_port) class ConnectedTransportWrapper: """Make an asyncio transport something you can call send on.""" def __init__(self, transport, address): self.transport = transport self.address = address async def send(self, bytes_to_send): self.transport.sendto(bytes_to_send, self.address) for address in ca.get_beacon_address_list(): # Connected sockets do not play well with asyncio, so connect to # one and then discard it. temp_sock = ca.bcast_socket(socket) temp_sock.connect(address) interface, _ = temp_sock.getsockname() temp_sock.close() sock = ca.bcast_socket(socket) transport, _ = await self.loop.create_datagram_endpoint( BcastLoop, sock=sock) wrapped_transport = ConnectedTransportWrapper(transport, address) self.beacon_socks[address] = (interface, wrapped_transport) for interface in self.interfaces: udp_sock = bcast_socket() try: udp_sock.bind((interface, ca.EPICS_CA1_PORT)) except Exception: self.log.exception('UDP bind failure on interface %r', interface) raise transport, self.p = await self.loop.create_datagram_endpoint( BcastLoop, sock=udp_sock) self.udp_socks[interface] = TransportWrapper(transport) self.log.debug('Broadcasting on %s:%d', interface, ca.EPICS_CA1_PORT) tasks.append(self.loop.create_task(self.broadcaster_queue_loop())) tasks.append(self.loop.create_task(self.subscription_queue_loop())) tasks.append(self.loop.create_task(self.broadcast_beacon_loop())) async_lib = AsyncioAsyncLayer(self.loop) for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) tasks.append(self.loop.create_task(method(async_lib))) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) try: await asyncio.gather(*tasks) except asyncio.CancelledError: self.log.info('Server task cancelled. Will shut down.') udp_sock.close() all_tasks = (tasks + self._server_tasks + [c._cq_task for c in self.circuits if c._cq_task is not None] + [t for c in self.circuits for t in c._write_tasks] + list(self.p._tasks)) for t in all_tasks: t.cancel() await asyncio.wait(all_tasks) return except Exception as ex: self.log.exception('Server error. Will shut down') udp_sock.close() raise finally: self.log.info('Server exiting....')