示例#1
0
 def init_transport() -> pyuavcan.transport.Transport:
     assert isinstance(registry, register.Registry)
     if transport is None:
         out = make_transport(registry, reconfigurable=reconfigurable_transport)
         if out is not None:
             return out
         raise MissingTransportConfigurationError(
             "Available registers do not encode a valid transport configuration"
         )
     if not isinstance(transport, RedundantTransport) and reconfigurable_transport:
         out = RedundantTransport()
         out.attach_inferior(transport)
         return out
     return transport
示例#2
0
 def make_udp_serial(nid: typing.Optional[int]) -> pyuavcan.transport.Transport:
     tr = RedundantTransport()
     if nid is not None:
         tr.attach_inferior(UDPTransport(f"127.0.0.{nid}"))
     else:
         tr.attach_inferior(UDPTransport(f"127.0.0.1", anonymous=True))
     tr.attach_inferior(SerialTransport("socket://localhost:50905", local_node_id=nid))
     return tr
示例#3
0
def construct_transport(expression: str) -> Transport:
    context = _make_evaluation_context()
    trs = _evaluate_transport_expr(expression, context)
    _logger.debug("Transport expression evaluation result: %r", trs)
    if len(trs) == 1:
        return trs[0]  # Non-redundant transport
    if len(trs) > 1:
        from pyuavcan.transport.redundant import RedundantTransport

        rt = RedundantTransport()
        for t in trs:
            rt.attach_inferior(t)
        assert rt.inferiors == trs
        return rt
    raise ValueError("No transports specified")
示例#4
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     red.attach_inferior(CANTransport(MockMedia(
         bus_0, 8, 1), nid))  # Heterogeneous setup (CAN classic)
     red.attach_inferior(CANTransport(MockMedia(
         bus_1, 32, 2), nid))  # Heterogeneous setup (CAN FD)
     red.attach_inferior(CANTransport(MockMedia(
         bus_2, 64, 3), nid))  # Heterogeneous setup (CAN FD)
     return red
示例#5
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     if nid is not None:
         red.attach_inferior(UDPTransport(f"127.0.0.{nid}"))
     else:
         red.attach_inferior(UDPTransport("127.0.0.1", anonymous=True))
     red.attach_inferior(SerialTransport(VIRTUAL_BUS_URI, nid))
     print("UDP+SERIAL:", red)
     return red
示例#6
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     # Triply redundant CAN bus.
     red = RedundantTransport()
     red.attach_inferior(CANTransport(MockMedia(
         bus_0, 8, 1), nid))  # Heterogeneous setup (CAN classic)
     red.attach_inferior(CANTransport(MockMedia(bus_1, 32, 1),
                                      nid))  # Heterogeneous setup (CAN FD)
     red.attach_inferior(CANTransport(MockMedia(bus_2, 64, 1),
                                      nid))  # Heterogeneous setup (CAN FD)
     print('REDUNDANT TRANSPORT CANx3:', red)
     return red
示例#7
0
        def make_tmr_can(
                nid: typing.Optional[int]) -> pyuavcan.transport.Transport:
            from pyuavcan.transport.redundant import RedundantTransport

            tr = RedundantTransport()
            tr.attach_inferior(
                CANTransport(SocketCANMedia("vcan0", 8), local_node_id=nid))
            tr.attach_inferior(
                CANTransport(SocketCANMedia("vcan1", 32), local_node_id=nid))
            tr.attach_inferior(
                CANTransport(SocketCANMedia("vcan2", 64), local_node_id=nid))
            return tr
示例#8
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     red.attach_inferior(
         CANTransport(SocketCANMedia("vcan0", 64), nid))
     red.attach_inferior(
         CANTransport(SocketCANMedia("vcan1", 32), nid))
     return red
示例#9
0
    def construct_subsystem(self, args: argparse.Namespace) -> pyuavcan.transport.Transport:
        context = _make_evaluation_context()
        _logger.debug('Expression evaluation context: %r', list(context.keys()))

        trs: typing.List[pyuavcan.transport.Transport] = []
        if args.transport is not None:
            for expression in args.transport:
                t = _evaluate_transport_expr(expression, context)
                _logger.info('Expression %r yields %r', expression, t)
                trs.append(t)

        if len(trs) < 1:
            raise ValueError('No transports specified')
        elif len(trs) == 1:
            return trs[0]  # Non-redundant transport
        else:
            from pyuavcan.transport.redundant import RedundantTransport
            rt = RedundantTransport()
            for t in trs:
                rt.attach_inferior(t)
            assert rt.inferiors == trs
            return rt
示例#10
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     red.attach_inferior(
         UDPTransport(f'127.0.0.{nid}/8')
         if nid is not None else UDPTransport('127.255.255.255/8'))
     red.attach_inferior(SerialTransport(VIRTUAL_BUS_URI, nid))
     print('REDUNDANT TRANSPORT UDP+SERIAL:', red)
     return red
示例#11
0
def _unittest_redundant_transport_capture() -> None:
    def mon(_x: object) -> None:
        return None

    tr = RedundantTransport()
    inf_a = LoopbackTransport(1234)
    inf_b = LoopbackTransport(1234)
    tr.begin_capture(mon)
    assert inf_a.capture_handlers == []
    assert inf_b.capture_handlers == []
    tr.attach_inferior(inf_a)
    assert inf_a.capture_handlers == [mon]
    assert inf_b.capture_handlers == []
    tr.attach_inferior(inf_b)
    assert inf_a.capture_handlers == [mon]
    assert inf_b.capture_handlers == [mon]
示例#12
0
文件: node.py 项目: lellisjr/yakut
def _unittest_output_tid_file_path() -> None:
    from pyuavcan.transport.redundant import RedundantTransport
    from pyuavcan.transport.loopback import LoopbackTransport

    def once(tr: Transport) -> typing.Optional[pathlib.Path]:
        return _get_output_transfer_id_map_path(tr)

    assert once(LoopbackTransport(None)) is None
    assert once(LoopbackTransport(123)) == OUTPUT_TRANSFER_ID_MAP_DIR / "123"

    red = RedundantTransport()
    assert once(red) is None
    red.attach_inferior(LoopbackTransport(4000))
    red.attach_inferior(LoopbackTransport(4000))
    assert once(red) == OUTPUT_TRANSFER_ID_MAP_DIR / "4000"

    red = RedundantTransport()
    red.attach_inferior(LoopbackTransport(None))
    red.attach_inferior(LoopbackTransport(None))
    assert once(red) is None
示例#13
0
async def _unittest_redundant_transport(caplog: typing.Any) -> None:
    from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer
    from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier
    from pyuavcan.transport import ProtocolParameters

    loop = asyncio.get_event_loop()
    loop.slow_callback_duration = 1.0

    tr_a = RedundantTransport()
    tr_b = RedundantTransport(loop)
    assert tr_a.sample_statistics() == RedundantTransportStatistics([])
    assert tr_a.inferiors == []
    assert tr_a.local_node_id is None
    assert tr_a.loop is asyncio.get_event_loop()
    assert tr_a.local_node_id is None
    assert tr_a.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=0,
        max_nodes=0,
        mtu=0,
    )
    assert tr_a.descriptor == '<redundant></redundant>'  # Empty, no inferiors.
    assert tr_a.input_sessions == []
    assert tr_a.output_sessions == []

    assert tr_a.loop == tr_b.loop

    #
    # Instantiate session objects.
    #
    meta = PayloadMetadata(10_240)

    pub_a = tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    sub_any_a = tr_a.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert pub_a is tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert set(tr_a.input_sessions) == {sub_any_a}
    assert set(tr_a.output_sessions) == {pub_a}
    assert tr_a.sample_statistics() == RedundantTransportStatistics()

    pub_b = tr_b.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    sub_any_b = tr_b.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    sub_sel_b = tr_b.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta)
    assert sub_sel_b is tr_b.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta)
    assert set(tr_b.input_sessions) == {sub_any_b, sub_sel_b}
    assert set(tr_b.output_sessions) == {pub_b}
    assert tr_b.sample_statistics() == RedundantTransportStatistics()

    #
    # Exchange test with no inferiors, expected to fail.
    #
    assert len(pub_a.inferiors) == 0
    assert len(sub_any_a.inferiors) == 0
    assert not await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=1,
        fragmented_payload=[memoryview(b'abc')]),
                                      monotonic_deadline=loop.time() + 1.0)
    assert not await sub_any_a.receive_until(loop.time() + 0.1)
    assert not await sub_any_b.receive_until(loop.time() + 0.1)
    assert tr_a.sample_statistics() == RedundantTransportStatistics()
    assert tr_b.sample_statistics() == RedundantTransportStatistics()

    #
    # Adding inferiors - loopback, transport A only.
    #
    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*loop.*'):
        tr_a.attach_inferior(
            LoopbackTransport(
                111, loop=asyncio.new_event_loop()))  # Wrong event loop.
    assert len(pub_a.inferiors) == 0
    assert len(sub_any_a.inferiors) == 0

    lo_mono_0 = LoopbackTransport(111)
    lo_mono_1 = LoopbackTransport(111)

    tr_a.attach_inferior(lo_mono_0)
    assert len(pub_a.inferiors) == 1
    assert len(sub_any_a.inferiors) == 1

    with pytest.raises(ValueError):
        tr_a.detach_inferior(lo_mono_1)  # Not a registered inferior (yet).

    tr_a.attach_inferior(lo_mono_1)
    assert len(pub_a.inferiors) == 2
    assert len(sub_any_a.inferiors) == 2

    with pytest.raises(ValueError):
        tr_a.attach_inferior(lo_mono_0)  # Double-add not allowed.

    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*node-id.*'):
        tr_a.attach_inferior(LoopbackTransport(None))  # Wrong node-ID.

    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*node-id.*'):
        tr_a.attach_inferior(LoopbackTransport(1230))  # Wrong node-ID.

    assert tr_a.inferiors == [lo_mono_0, lo_mono_1]
    assert len(pub_a.inferiors) == 2
    assert len(sub_any_a.inferiors) == 2

    assert tr_a.sample_statistics() == RedundantTransportStatistics(inferiors=[
        lo_mono_0.sample_statistics(),
        lo_mono_1.sample_statistics(),
    ])
    assert tr_a.local_node_id == 111
    assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>'

    assert await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=2,
        fragmented_payload=[memoryview(b'def')]),
                                  monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_a.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'def')]
    assert rx.transfer_id == 2
    assert not await sub_any_b.receive_until(loop.time() + 0.1)

    #
    # Incapacitate one inferior, ensure things are still OK.
    #
    with caplog.at_level(logging.CRITICAL,
                         logger=pyuavcan.transport.redundant.__name__):
        for s in lo_mono_0.output_sessions:
            s.exception = RuntimeError('INTENDED EXCEPTION')

        assert await pub_a.send_until(Transfer(
            timestamp=Timestamp.now(),
            priority=Priority.LOW,
            transfer_id=3,
            fragmented_payload=[memoryview(b'qwe')]),
                                      monotonic_deadline=loop.time() + 1.0)
        rx = await sub_any_a.receive_until(loop.time() + 1.0)
        assert rx is not None
        assert rx.fragmented_payload == [memoryview(b'qwe')]
        assert rx.transfer_id == 3

    #
    # Remove old loopback transports. Configure new ones with cyclic TID.
    #
    lo_cyc_0 = LoopbackTransport(111)
    lo_cyc_1 = LoopbackTransport(111)
    cyc_proto_params = ProtocolParameters(
        transfer_id_modulo=32,  # Like CAN
        max_nodes=128,  # Like CAN
        mtu=63,  # Like CAN
    )
    lo_cyc_0.protocol_parameters = cyc_proto_params
    lo_cyc_1.protocol_parameters = cyc_proto_params
    assert lo_cyc_0.protocol_parameters == lo_cyc_1.protocol_parameters == cyc_proto_params

    assert tr_a.protocol_parameters.transfer_id_modulo >= 2**56
    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*transfer-id.*'):
        tr_a.attach_inferior(lo_cyc_0)  # Transfer-ID modulo mismatch

    tr_a.detach_inferior(lo_mono_0)
    tr_a.detach_inferior(lo_mono_1)
    del lo_mono_0  # Prevent accidental reuse.
    del lo_mono_1
    assert tr_a.inferiors == []  # All removed, okay.
    assert pub_a.inferiors == []
    assert sub_any_a.inferiors == []
    assert tr_a.local_node_id is None  # Back to the roots
    assert tr_a.descriptor == '<redundant></redundant>'  # Yes yes

    # Now we can add our cyclic transports safely.
    tr_a.attach_inferior(lo_cyc_0)
    assert tr_a.protocol_parameters.transfer_id_modulo == 32
    tr_a.attach_inferior(lo_cyc_1)
    assert tr_a.protocol_parameters == cyc_proto_params, 'Protocol parameter mismatch'
    assert tr_a.local_node_id == 111
    assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>'

    # Exchange test.
    assert await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=4,
        fragmented_payload=[memoryview(b'rty')]),
                                  monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_a.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'rty')]
    assert rx.transfer_id == 4

    #
    # Real heterogeneous transport test.
    #
    tr_a.detach_inferior(lo_cyc_0)
    tr_a.detach_inferior(lo_cyc_1)
    del lo_cyc_0  # Prevent accidental reuse.
    del lo_cyc_1

    udp_a = UDPTransport('127.0.0.111/8')
    udp_b = UDPTransport('127.0.0.222/8')

    serial_a = SerialTransport(SERIAL_URI, 111)
    serial_b = SerialTransport(SERIAL_URI, 222, mtu=2048)  # Heterogeneous.

    tr_a.attach_inferior(udp_a)
    tr_a.attach_inferior(serial_a)

    tr_b.attach_inferior(udp_b)
    tr_b.attach_inferior(serial_b)

    print('tr_a.descriptor', tr_a.descriptor)
    print('tr_b.descriptor', tr_b.descriptor)

    assert tr_a.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**64,
        max_nodes=4096,
        mtu=1024,
    )
    assert tr_a.local_node_id == 111
    assert tr_a.descriptor == f'<redundant>{udp_a.descriptor}{serial_a.descriptor}</redundant>'

    assert tr_b.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**64,
        max_nodes=4096,
        mtu=1024,
    )
    assert tr_b.local_node_id == 222
    assert tr_b.descriptor == f'<redundant>{udp_b.descriptor}{serial_b.descriptor}</redundant>'

    assert await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=5,
        fragmented_payload=[memoryview(b'uio')]),
                                  monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_b.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'uio')]
    assert rx.transfer_id == 5
    assert not await sub_any_a.receive_until(loop.time() + 0.1)
    assert not await sub_any_b.receive_until(loop.time() + 0.1)
    assert not await sub_sel_b.receive_until(loop.time() + 0.1)

    #
    # Construct new session with the transports configured.
    #
    pub_a_new = tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta)
    assert pub_a_new is tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta)
    assert set(tr_a.output_sessions) == {pub_a, pub_a_new}

    assert await pub_a_new.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=6,
        fragmented_payload=[memoryview(b'asd')]),
                                      monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_b.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'asd')]
    assert rx.transfer_id == 6

    #
    # Termination.
    #
    tr_a.close()
    tr_a.close()  # Idempotency
    tr_b.close()
    tr_b.close()  # Idempotency

    with pytest.raises(pyuavcan.transport.ResourceClosedError
                       ):  # Make sure the inferiors are closed.
        udp_a.get_output_session(
            OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    with pytest.raises(pyuavcan.transport.ResourceClosedError
                       ):  # Make sure the inferiors are closed.
        serial_b.get_output_session(
            OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    with pytest.raises(pyuavcan.transport.ResourceClosedError
                       ):  # Make sure the sessions are closed.
        await pub_a.send_until(Transfer(timestamp=Timestamp.now(),
                                        priority=Priority.LOW,
                                        transfer_id=100,
                                        fragmented_payload=[]),
                               monotonic_deadline=loop.time() + 1.0)

    await asyncio.sleep(
        1
    )  # Let all pending tasks finalize properly to avoid stack traces in the output.
示例#14
0
async def _unittest_slow_node(
    generated_packages: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]
) -> None:
    from pyuavcan.application import Node
    from uavcan.node import Version_1_0, Heartbeat_1_0, GetInfo_1_0, Mode_1_0, Health_1_0

    asyncio.get_running_loop().slow_callback_duration = 3.0

    assert generated_packages
    remote_pres = Presentation(UDPTransport("127.1.1.1"))
    remote_hb_sub = remote_pres.make_subscriber_with_fixed_subject_id(
        Heartbeat_1_0)
    remote_info_cln = remote_pres.make_client_with_fixed_service_id(
        GetInfo_1_0, 258)

    trans = RedundantTransport()
    pres = Presentation(trans)
    try:
        info = GetInfo_1_0.Response(
            protocol_version=Version_1_0(
                *pyuavcan.UAVCAN_SPECIFICATION_VERSION),
            software_version=Version_1_0(*pyuavcan.__version_info__[:2]),
            name="org.uavcan.pyuavcan.test.node",
        )
        node = Node(pres, info, with_diagnostic_subscriber=True)
        print("node:", node)
        assert node.presentation is pres
        node.start()
        node.start()  # Idempotency

        node.heartbeat_publisher.priority = pyuavcan.transport.Priority.FAST
        node.heartbeat_publisher.period = 0.5
        node.heartbeat_publisher.mode = Mode_1_0.MAINTENANCE  # type: ignore
        node.heartbeat_publisher.health = Health_1_0.ADVISORY  # type: ignore
        node.heartbeat_publisher.vendor_specific_status_code = 93
        with pytest.raises(ValueError):
            node.heartbeat_publisher.period = 99.0
        with pytest.raises(ValueError):
            node.heartbeat_publisher.vendor_specific_status_code = -299

        assert node.heartbeat_publisher.priority == pyuavcan.transport.Priority.FAST
        assert node.heartbeat_publisher.period == pytest.approx(0.5)
        assert node.heartbeat_publisher.mode == Mode_1_0.MAINTENANCE
        assert node.heartbeat_publisher.health == Health_1_0.ADVISORY
        assert node.heartbeat_publisher.vendor_specific_status_code == 93

        assert None is await remote_hb_sub.receive_for(2.0)

        assert trans.local_node_id is None
        trans.attach_inferior(UDPTransport("127.1.1.2"))
        assert trans.local_node_id == 258

        for _ in range(2):
            hb_transfer = await remote_hb_sub.receive_for(2.0)
            assert hb_transfer is not None
            hb, transfer = hb_transfer
            assert transfer.source_node_id == 258
            assert transfer.priority == pyuavcan.transport.Priority.FAST
            assert 1 <= hb.uptime <= 9
            assert hb.mode.value == Mode_1_0.MAINTENANCE
            assert hb.health.value == Health_1_0.ADVISORY
            assert hb.vendor_specific_status_code == 93

        info_transfer = await remote_info_cln.call(GetInfo_1_0.Request())
        assert info_transfer is not None
        resp, transfer = info_transfer
        assert transfer.source_node_id == 258
        assert isinstance(resp, GetInfo_1_0.Response)
        assert resp.name.tobytes().decode() == "org.uavcan.pyuavcan.test.node"
        assert resp.protocol_version.major == pyuavcan.UAVCAN_SPECIFICATION_VERSION[
            0]
        assert resp.software_version.major == pyuavcan.__version_info__[0]

        trans.detach_inferior(trans.inferiors[0])
        assert trans.local_node_id is None

        assert None is await remote_hb_sub.receive_for(2.0)

        node.close()
        node.close()  # Idempotency
    finally:
        pres.close()
        remote_pres.close()
        await asyncio.sleep(1.0)  # Let the background tasks terminate.
def make_transport(
    registers: MutableMapping[str, ValueProxy],
    *,
    reconfigurable: bool = False,
) -> Optional[pyuavcan.transport.Transport]:
    """
    Constructs a transport instance based on the configuration encoded in the supplied registers.
    If more than one transport is defined, a redundant instance will be constructed.

    The register schema is documented below per transport class
    (refer to the transport class documentation to find the defaults for optional registers).
    All transports also accept the following standard regsiters:

    +-------------------+-------------------+-----------------------------------------------------------------------+
    | Register name     | Register type     | Semantics                                                             |
    +===================+===================+=======================================================================+
    | ``uavcan.node.id``| ``natural16[1]``  | The node-ID to use. If the value exceeds the valid                    |
    |                   |                   | range, the constructed node will be anonymous.                        |
    +-------------------+-------------------+-----------------------------------------------------------------------+

    ..  list-table:: :mod:`pyuavcan.transport.udp`
        :widths: 1 1 9
        :header-rows: 1

        * - Register name
          - Register type
          - Register semantics

        * - ``uavcan.udp.iface``
          - ``string``
          - Whitespace-separated list of /16 IP subnet addresses.
            16 least significant bits are replaced with the node-ID if configured, otherwise left unchanged.
            E.g.: ``127.42.0.42``: node-ID 257, result ``127.42.1.1``;
            ``127.42.0.42``: anonymous, result ``127.42.0.42``.

        * - ``uavcan.udp.duplicate_service_transfers``
          - ``bit[1]``
          - Apply deterministic data loss mitigation to RPC-service transfers by setting multiplication factor = 2.

        * - ``uavcan.udp.mtu``
          - ``natural16[1]``
          - The MTU for all constructed transport instances.

    ..  list-table:: :mod:`pyuavcan.transport.serial`
        :widths: 1 1 9
        :header-rows: 1

        * - Register name
          - Register type
          - Register semantics

        * - ``uavcan.serial.iface``
          - ``string``
          - Whitespace-separated list of serial port names.
            E.g.: ``/dev/ttyACM0``, ``COM9``, ``socket://127.0.0.1:50905``.

        * - ``uavcan.serial.duplicate_service_transfers``
          - ``bit[1]``
          - Apply deterministic data loss mitigation to RPC-service transfers by setting multiplication factor = 2.

        * - ``uavcan.serial.baudrate``
          - ``natural32[1]``
          - The baudrate to set for all specified serial ports. Leave unchanged if zero.

    ..  list-table:: :mod:`pyuavcan.transport.can`
        :widths: 1 1 9
        :header-rows: 1

        * - Register name
          - Register type
          - Register semantics

        * - ``uavcan.can.iface``
          - ``string``
          - Whitespace-separated list of CAN iface names.
            Each iface name shall follow the format defined in :class:`pyuavcan.transport.can.media.pythoncan`.
            E.g.: ``socketcan:vcan0``.

        * - ``uavcan.can.mtu``
          - ``natural16[1]``
          - The MTU value to use with all constructed CAN transports.
            Values other than 8 and 64 should not be used.

        * - ``uavcan.can.bitrate``
          - ``natural32[2]``
          - The bitrates to use for all constructed CAN transports
            for arbitration (first value) and data (second value) segments.
            To use Classic CAN, set both to the same value and set MTU = 8.

    ..  list-table:: :mod:`pyuavcan.transport.loopback`
        :widths: 1 1 9
        :header-rows: 1

        * - Register name
          - Register type
          - Register semantics

        * - ``uavcan.loopback``
          - ``bit[1]``
          - If True, a loopback transport will be constructed. This is intended for testing only.

    :param registers:
        A mutable mapping of :class:`str` to :class:`pyuavcan.application.register.ValueProxy`.
        Normally, it should be constructed by :func:`pyuavcan.application.make_registry`.

    :param reconfigurable:
        If False (default), the return value is:

        - None if the registers do not encode a valid transport configuration.
        - A single transport instance if a non-redundant configuration is defined.
        - An instance of :class:`pyuavcan.transport.RedundantTransport` if more than one transport
          configuration is defined.

        If True, then the returned instance is always of type :class:`pyuavcan.transport.RedundantTransport`,
        where the set of inferiors is empty if no transport configuration is defined.
        This case is intended for applications that may want to change the transport configuration afterwards.

    :return:
        None if no transport is configured AND ``reconfigurable`` is False.
        Otherwise, a functional transport instance is returned.

    :raises:
        - :class:`pyuavcan.application.register.MissingRegisterError` if a register is expected but cannot be found.
        - :class:`pyuavcan.application.register.ValueConversionError` if a register is found but its value
          cannot be converted to the correct type.

    ..  doctest::
        :hide:

        >>> import tests
        >>> tests.asyncio_allow_event_loop_access_from_top_level()

    >>> from pyuavcan.application.register import ValueProxy, Natural16, Natural32
    >>> reg = {
    ...     "uavcan.udp.iface": ValueProxy("127.99.0.0"),
    ...     "uavcan.node.id": ValueProxy(Natural16([257])),
    ... }
    >>> tr = make_transport(reg)
    >>> tr
    UDPTransport('127.99.1.1', local_node_id=257, ...)
    >>> tr.close()
    >>> tr = make_transport(reg, reconfigurable=True)                   # Same but reconfigurable.
    >>> tr                                                              # Wrapped into RedundantTransport.
    RedundantTransport(UDPTransport('127.99.1.1', local_node_id=257, ...))
    >>> tr.close()

    >>> int(reg["uavcan.udp.mtu"])      # Defaults created automatically to expose all configurables.
    1200
    >>> int(reg["uavcan.can.mtu"])
    64
    >>> reg["uavcan.can.bitrate"].ints
    [1000000, 4000000]

    >>> reg = {                                             # Triply-redundant heterogeneous transport:
    ...     "uavcan.udp.iface":    ValueProxy("127.99.0.15 127.111.0.15"),  # Double UDP transport
    ...     "uavcan.serial.iface": ValueProxy("socket://127.0.0.1:50905"),  # Serial transport
    ... }
    >>> tr = make_transport(reg)                            # The node-ID was not set, so the transport is anonymous.
    >>> tr                                          # doctest: +NORMALIZE_WHITESPACE
    RedundantTransport(UDPTransport('127.99.0.15',  local_node_id=None, ...),
                       UDPTransport('127.111.0.15', local_node_id=None, ...),
                       SerialTransport('socket://127.0.0.1:50905', local_node_id=None, ...))
    >>> tr.close()

    >>> reg = {
    ...     "uavcan.can.iface":   ValueProxy("virtual: virtual:"),    # Doubly-redundant CAN
    ...     "uavcan.can.mtu":     ValueProxy(Natural16([32])),
    ...     "uavcan.can.bitrate": ValueProxy(Natural32([500_000, 2_000_000])),
    ...     "uavcan.node.id":     ValueProxy(Natural16([123])),
    ... }
    >>> tr = make_transport(reg)
    >>> tr                                          # doctest: +NORMALIZE_WHITESPACE
    RedundantTransport(CANTransport(PythonCANMedia('virtual:', mtu=32), local_node_id=123),
                       CANTransport(PythonCANMedia('virtual:', mtu=32), local_node_id=123))
    >>> tr.close()

    >>> reg = {
    ...     "uavcan.udp.iface": ValueProxy("127.99.1.1"),       # Per the standard register specs,
    ...     "uavcan.node.id": ValueProxy(Natural16([0xFFFF])),  # 0xFFFF means unset/anonymous.
    ... }
    >>> tr = make_transport(reg)
    >>> tr
    UDPTransport('127.99.1.1', local_node_id=None, ...)
    >>> tr.close()

    >>> tr = make_transport({})
    >>> tr is None
    True
    >>> tr = make_transport({}, reconfigurable=True)
    >>> tr                                                          # Redundant transport with no inferiors.
    RedundantTransport()
    """
    def init(name: str, default: RelaxedValue) -> ValueProxy:
        return registers.setdefault("uavcan." + name, ValueProxy(default))

    # Per Specification, if uavcan.node.id = 65535, the node-ID is unspecified.
    node_id: Optional[int] = int(init("node.id", Natural16([0xFFFF])))
    # TODO: currently, we raise an error if the node-ID setting exceeds the maximum allowed value for the current
    # transport, but the spec recommends that we should handle this as if the node-ID was not set at all.
    if node_id is not None and not (0 <= node_id < 0xFFFF):
        node_id = None

    transports = list(
        itertools.chain(*(f(registers, node_id) for f in _SPECIALIZATIONS)))
    assert all(isinstance(t, pyuavcan.transport.Transport) for t in transports)

    if not reconfigurable:
        if not transports:
            return None
        if len(transports) == 1:
            return transports[0]

    from pyuavcan.transport.redundant import RedundantTransport

    red = RedundantTransport()
    for tr in transports:
        red.attach_inferior(tr)
    return red
示例#16
0
async def _unittest_redundant_transport_reconfiguration() -> None:
    from pyuavcan.transport import OutputSessionSpecifier, MessageDataSpecifier, PayloadMetadata

    tr = RedundantTransport()
    tr.attach_inferior(LoopbackTransport(1234))
    ses = tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(5555), None),
        PayloadMetadata(0))
    assert ses
    tr.detach_inferior(tr.inferiors[0])
    tr.attach_inferior(LoopbackTransport(1235))  # Different node-ID
    tr.detach_inferior(tr.inferiors[0])
    tr.attach_inferior(LoopbackTransport(
        None, allow_anonymous_transfers=True))  # Anonymous
    with pytest.raises(
            pyuavcan.transport.OperationNotDefinedForAnonymousNodeError):
        tr.attach_inferior(
            LoopbackTransport(None, allow_anonymous_transfers=False))
    assert len(tr.inferiors) == 1

    tr.close()
    await asyncio.sleep(2.0)
示例#17
0
async def _unittest_redundant_transport_capture() -> None:
    from threading import Lock
    from pyuavcan.transport import Capture, Trace, TransferTrace, Priority, ServiceDataSpecifier
    from pyuavcan.transport import AlienTransfer, AlienTransferMetadata, AlienSessionSpecifier
    from pyuavcan.transport.redundant import RedundantDuplicateTransferTrace, RedundantCapture
    from tests.transport.can.media.mock import MockMedia as CANMockMedia

    asyncio.get_event_loop().slow_callback_duration = 5.0

    tracer = RedundantTransport.make_tracer()
    traces: typing.List[typing.Optional[Trace]] = []
    lock = Lock()

    def handle_capture(cap: Capture) -> None:
        with lock:
            # Drop TX frames, they are not interesting for this test.
            assert isinstance(cap, RedundantCapture)
            if isinstance(cap.inferior, pyuavcan.transport.serial.SerialCapture
                          ) and cap.inferior.own:
                return
            if isinstance(
                    cap.inferior,
                    pyuavcan.transport.can.CANCapture) and cap.inferior.own:
                return
            print("CAPTURE:", cap)
            traces.append(tracer.update(cap))

    async def wait(how_many: int) -> None:
        for _ in range(10):
            await asyncio.sleep(0.1)
            with lock:
                if len(traces) >= how_many:
                    return
        assert False, "No traces received"

    # Setup capture -- one is added before capture started, the other is added later.
    # Make sure they are treated identically.
    tr = RedundantTransport()
    inf_a: pyuavcan.transport.Transport = SerialTransport(SERIAL_URI, 1234)
    inf_b: pyuavcan.transport.Transport = SerialTransport(SERIAL_URI, 1234)
    tr.attach_inferior(inf_a)
    assert not tr.capture_active
    assert not inf_a.capture_active
    assert not inf_b.capture_active
    tr.begin_capture(handle_capture)
    assert tr.capture_active
    assert inf_a.capture_active
    assert not inf_b.capture_active
    tr.attach_inferior(inf_b)
    assert tr.capture_active
    assert inf_a.capture_active
    assert inf_b.capture_active

    # Send a transfer and make sure it is handled and deduplicated correctly.
    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.IMMEDIATE,
            transfer_id=1234,
            session_specifier=AlienSessionSpecifier(
                source_node_id=321,
                destination_node_id=222,
                data_specifier=ServiceDataSpecifier(
                    77, ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        [memoryview(b"hello")],
    )
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        # Check the status of the deduplication process. We should get two: one transfer, one duplicate.
        assert len(traces) == 2
        trace = traces.pop(0)
        assert isinstance(trace, TransferTrace)
        assert trace.transfer == transfer
        # This is the duplicate.
        assert isinstance(traces.pop(0), RedundantDuplicateTransferTrace)
        assert not traces

    # Spoof the same thing again, get nothing out: transfers discarded by the inferior's own reassemblers.
    # WARNING: this will fail if too much time has passed since the previous transfer due to TID timeout.
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        assert None is traces.pop(0)
        assert None is traces.pop(0)
        assert not traces

    # But if we change ONLY destination, deduplication will not take place.
    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.IMMEDIATE,
            transfer_id=1234,
            session_specifier=AlienSessionSpecifier(
                source_node_id=321,
                destination_node_id=333,
                data_specifier=ServiceDataSpecifier(
                    77, ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        [memoryview(b"hello")],
    )
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        # Check the status of the deduplication process. We should get two: one transfer, one duplicate.
        assert len(traces) == 2
        trace = traces.pop(0)
        assert isinstance(trace, TransferTrace)
        assert trace.transfer == transfer
        # This is the duplicate.
        assert isinstance(traces.pop(0), RedundantDuplicateTransferTrace)
        assert not traces

    # Change the inferior configuration and make sure it is handled properly.
    tr.detach_inferior(inf_a)
    tr.detach_inferior(inf_b)
    inf_a.close()
    inf_b.close()
    # The new inferiors use cyclic transfer-ID; the tracer should reconfigure itself automatically!
    can_peers: typing.Set[CANMockMedia] = set()
    inf_a = CANTransport(CANMockMedia(can_peers, 64, 2), 111)
    inf_b = CANTransport(CANMockMedia(can_peers, 64, 2), 111)
    tr.attach_inferior(inf_a)
    tr.attach_inferior(inf_b)
    # Capture should have been launched automatically.
    assert inf_a.capture_active
    assert inf_b.capture_active

    # Send transfer over CAN and observe that it is handled well.
    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.IMMEDIATE,
            transfer_id=19,
            session_specifier=AlienSessionSpecifier(
                source_node_id=111,
                destination_node_id=22,
                data_specifier=ServiceDataSpecifier(
                    77, ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        [memoryview(b"hello")],
    )
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        # Check the status of the deduplication process. We should get two: one transfer, one duplicate.
        assert len(traces) == 2
        trace = traces.pop(0)
        assert isinstance(trace, TransferTrace)
        assert trace.transfer == transfer
        # This is the duplicate.
        assert isinstance(traces.pop(0), RedundantDuplicateTransferTrace)
        assert not traces

    # Dispose of everything.
    tr.close()
    await asyncio.sleep(1.0)
示例#18
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     red.attach_inferior(UDPTransport("127.0.0.1", local_node_id=nid))
     red.attach_inferior(SerialTransport(VIRTUAL_BUS_URI, nid))
     print("UDP+SERIAL:", red)
     return red
示例#19
0
async def _unittest_slow_node(
        compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]) -> None:
    from pyuavcan.application import make_node, make_registry
    import uavcan.primitive
    from uavcan.node import Version_1_0, Heartbeat_1_0, GetInfo_1_0, Mode_1_0, Health_1_0

    asyncio.get_running_loop().slow_callback_duration = 3.0

    assert compiled
    remote_pres = Presentation(UDPTransport("127.1.1.1"))
    remote_hb_sub = remote_pres.make_subscriber_with_fixed_subject_id(
        Heartbeat_1_0)
    remote_info_cln = remote_pres.make_client_with_fixed_service_id(
        GetInfo_1_0, 258)

    trans = RedundantTransport()
    try:
        info = GetInfo_1_0.Response(
            protocol_version=Version_1_0(
                *pyuavcan.UAVCAN_SPECIFICATION_VERSION),
            software_version=Version_1_0(*pyuavcan.__version_info__[:2]),
            name="org.uavcan.pyuavcan.test.node",
        )
        node = make_node(info,
                         make_registry(None, typing.cast(Dict[str, bytes],
                                                         {})),
                         transport=trans)
        print("node:", node)
        assert node.presentation.transport is trans
        node.start()
        node.start()  # Idempotency

        # Check port instantiation API for non-fixed-port-ID types.
        assert "uavcan.pub.optional.id" not in node.registry  # Nothing yet.
        with pytest.raises(KeyError, match=r".*uavcan\.pub\.optional\.id.*"):
            node.make_publisher(uavcan.primitive.Empty_1_0, "optional")
        assert 0xFFFF == int(
            node.registry["uavcan.pub.optional.id"])  # Created automatically!
        with pytest.raises(TypeError):
            node.make_publisher(uavcan.primitive.Empty_1_0)

        # Same but for fixed port-ID types.
        assert "uavcan.pub.atypical_heartbeat.id" not in node.registry  # Nothing yet.
        port = node.make_publisher(uavcan.node.Heartbeat_1_0,
                                   "atypical_heartbeat")
        assert port.port_id == pyuavcan.dsdl.get_model(
            uavcan.node.Heartbeat_1_0).fixed_port_id
        port.close()
        assert 0xFFFF == int(node.registry["uavcan.pub.atypical_heartbeat.id"]
                             )  # Created automatically!
        node.registry[
            "uavcan.pub.atypical_heartbeat.id"] = 111  # Override the default.
        port = node.make_publisher(uavcan.node.Heartbeat_1_0,
                                   "atypical_heartbeat")
        assert port.port_id == 111
        port.close()

        node.heartbeat_publisher.priority = pyuavcan.transport.Priority.FAST
        node.heartbeat_publisher.period = 0.5
        node.heartbeat_publisher.mode = Mode_1_0.MAINTENANCE  # type: ignore
        node.heartbeat_publisher.health = Health_1_0.ADVISORY  # type: ignore
        node.heartbeat_publisher.vendor_specific_status_code = 93
        with pytest.raises(ValueError):
            node.heartbeat_publisher.period = 99.0
        with pytest.raises(ValueError):
            node.heartbeat_publisher.vendor_specific_status_code = -299

        assert node.heartbeat_publisher.priority == pyuavcan.transport.Priority.FAST
        assert node.heartbeat_publisher.period == pytest.approx(0.5)
        assert node.heartbeat_publisher.mode == Mode_1_0.MAINTENANCE
        assert node.heartbeat_publisher.health == Health_1_0.ADVISORY
        assert node.heartbeat_publisher.vendor_specific_status_code == 93

        assert None is await remote_hb_sub.receive_for(2.0)

        assert trans.local_node_id is None
        trans.attach_inferior(UDPTransport("127.1.1.2"))
        assert trans.local_node_id == 258

        for _ in range(2):
            hb_transfer = await remote_hb_sub.receive_for(2.0)
            assert hb_transfer is not None
            hb, transfer = hb_transfer
            assert transfer.source_node_id == 258
            assert transfer.priority == pyuavcan.transport.Priority.FAST
            assert 1 <= hb.uptime <= 9
            assert hb.mode.value == Mode_1_0.MAINTENANCE
            assert hb.health.value == Health_1_0.ADVISORY
            assert hb.vendor_specific_status_code == 93

        info_transfer = await remote_info_cln.call(GetInfo_1_0.Request())
        assert info_transfer is not None
        resp, transfer = info_transfer
        assert transfer.source_node_id == 258
        assert isinstance(resp, GetInfo_1_0.Response)
        assert resp.name.tobytes().decode() == "org.uavcan.pyuavcan.test.node"
        assert resp.protocol_version.major == pyuavcan.UAVCAN_SPECIFICATION_VERSION[
            0]
        assert resp.software_version.major == pyuavcan.__version_info__[0]

        trans.detach_inferior(trans.inferiors[0])
        assert trans.local_node_id is None

        assert None is await remote_hb_sub.receive_for(2.0)

        node.close()
        node.close()  # Idempotency
    finally:
        trans.close()
        remote_pres.close()
        await asyncio.sleep(1.0)  # Let the background tasks terminate.