Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
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)]
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
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)]
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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))
Exemplo n.º 10
0
        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)
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
    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']
Exemplo n.º 13
0
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
Exemplo n.º 14
0
    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()
Exemplo n.º 15
0
    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)
Exemplo n.º 16
0
    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()
Exemplo n.º 17
0
 def __create_sock(self):
     # UDP socket broadcasting to CA servers
     self.udp_sock = ca.bcast_socket()
     self.sock_thread = SocketThread(self.udp_sock, self)
Exemplo n.º 18
0
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))
Exemplo n.º 19
0
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
Exemplo n.º 20
0
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)]
Exemplo n.º 21
0
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)]
Exemplo n.º 22
0
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)]
Exemplo n.º 23
0
 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)
Exemplo n.º 24
0
    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....')
Exemplo n.º 25
0
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()
Exemplo n.º 26
0
    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()
Exemplo n.º 27
0
    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....')