Example #1
0
async def _unittest_slow_diagnostic(generated_packages: typing.List[
    pyuavcan.dsdl.GeneratedPackageInfo], caplog: typing.Any) -> None:
    from pyuavcan.application import diagnostic
    from uavcan.time import SynchronizedTimestamp_1_0

    assert generated_packages

    pres = Presentation(LoopbackTransport(2222))
    pub = pres.make_publisher_with_fixed_subject_id(diagnostic.Record)
    diag = diagnostic.DiagnosticSubscriber(pres)

    diag.start()

    caplog.clear()
    await pub.publish(
        diagnostic.Record(
            timestamp=SynchronizedTimestamp_1_0(123456789),
            severity=diagnostic.Severity(diagnostic.Severity.INFO),
            text="Hello world!",
        ))
    await asyncio.sleep(1.0)
    print("Captured log records:")
    for lr in caplog.records:
        print("   ", lr)
        assert isinstance(lr, logging.LogRecord)
        pat = r"uavcan\.diagnostic\.Record: node=2222 severity=2 ts_sync=123\.456789 ts_local=\S+:\nHello world!"
        if lr.levelno == logging.INFO and re.match(pat, lr.message):
            break
    else:
        assert False, "Expected log message not captured"

    diag.close()
    pub.close()
    pres.close()
    await asyncio.sleep(1.0)  # Let the background tasks terminate.
async def _unittest_slow_plug_and_play_allocatee(
    compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo], caplog: typing.Any
) -> None:
    from pyuavcan.presentation import Presentation
    from pyuavcan.application.plug_and_play import Allocatee, NodeIDAllocationData_2, ID

    assert compiled

    asyncio.get_running_loop().slow_callback_duration = 5.0

    peers: typing.Set[MockMedia] = set()
    pres_client = Presentation(CANTransport(MockMedia(peers, 64, 1), None))
    pres_server = Presentation(CANTransport(MockMedia(peers, 64, 1), 123))
    allocatee = Allocatee(pres_client, _uid("00112233445566778899aabbccddeeff"), 42)
    pub = pres_server.make_publisher_with_fixed_subject_id(NodeIDAllocationData_2)

    await pub.publish(NodeIDAllocationData_2(ID(10), unique_id=_uid("aabbccddeeff00112233445566778899")))  # Mismatch.
    await asyncio.sleep(1.0)
    assert allocatee.get_result() is None

    with caplog.at_level(logging.CRITICAL, logger=pyuavcan.application.plug_and_play.__name__):  # Bad NID.
        await pub.publish(NodeIDAllocationData_2(ID(999), unique_id=_uid("00112233445566778899aabbccddeeff")))
        await asyncio.sleep(1.0)
        assert allocatee.get_result() is None

    await pub.publish(NodeIDAllocationData_2(ID(0), unique_id=_uid("00112233445566778899aabbccddeeff")))  # Correct.
    await asyncio.sleep(1.0)
    assert allocatee.get_result() == 0

    allocatee.close()
    pub.close()
    pres_client.close()
    pres_server.close()
    await asyncio.sleep(1.0)  # Let the tasks finalize properly.
Example #3
0
def _make_subscriber(subjects: typing.Sequence[str],
                     presentation: Presentation) -> Subscriber[_M]:
    group = [construct_port_id_and_type(ds) for ds in subjects]
    assert len(group) > 0
    if len(group) == 1:
        ((subject_id, dtype), ) = group
        return presentation.make_subscriber(dtype, subject_id)
    raise NotImplementedError(
        "Multi-subject subscription is not yet implemented. See https://github.com/UAVCAN/pyuavcan/issues/65"
    )
Example #4
0
async def _unittest_slow_node_tracker(generated_packages: typing.List[pyuavcan.dsdl.GeneratedPackageInfo],
                                      caplog: typing.Any) -> None:
    from . import get_transport
    from pyuavcan.presentation import Presentation
    from pyuavcan.application.node_tracker import NodeTracker, Entry, GetInfo, Heartbeat

    assert generated_packages

    p_a = Presentation(get_transport(0xA))
    p_b = Presentation(get_transport(0xB))
    p_c = Presentation(get_transport(0xC))
    p_trk = Presentation(get_transport(None))

    try:
        last_update_args: typing.List[typing.Tuple[int, typing.Optional[Entry], typing.Optional[Entry]]] = []

        def simple_handler(node_id: int, old: typing.Optional[Entry], new: typing.Optional[Entry]) -> None:
            last_update_args.append((node_id, old, new))

        def faulty_handler(_node_id: int, _old: typing.Optional[Entry], _new: typing.Optional[Entry]) -> None:
            raise Exception('INTENDED EXCEPTION')

        trk = NodeTracker(p_trk)

        assert not trk.registry
        assert pytest.approx(trk.get_info_timeout) == trk.DEFAULT_GET_INFO_TIMEOUT
        assert trk.get_info_attempts == trk.DEFAULT_GET_INFO_ATTEMPTS

        # Override the defaults to simplify and speed-up testing.
        trk.get_info_timeout = 1.0
        trk.get_info_attempts = 2
        assert pytest.approx(trk.get_info_timeout) == 1.0
        assert trk.get_info_attempts == 2

        with caplog.at_level(logging.CRITICAL, logger=pyuavcan.application.node_tracker.__name__):
            trk.add_update_handler(faulty_handler)
            trk.add_update_handler(simple_handler)

            trk.start()
            trk.start()  # Idempotency

            await asyncio.sleep(1)
            assert not last_update_args
            assert not trk.registry

            # Bring the first node online and make sure it is detected and reported.
            hb_a = asyncio.create_task(_publish_heartbeat(p_a, 0xde))
            await asyncio.sleep(2.5)
            assert len(last_update_args) == 1
            assert last_update_args[0][0] == 0xA
            assert last_update_args[0][1] is None
            assert last_update_args[0][2] is not None
            assert last_update_args[0][2].heartbeat.uptime == 0
            assert last_update_args[0][2].heartbeat.vendor_specific_status_code == 0xde
            last_update_args.clear()
            assert list(trk.registry.keys()) == [0xA]
            assert 3 >= trk.registry[0xA].heartbeat.uptime >= 2
            assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xde
            assert trk.registry[0xA].info is None

            # Remove the faulty handler -- no point keeping the noise in the log.
            trk.remove_update_handler(faulty_handler)

        # Bring the second node online and make sure it is detected and reported.
        hb_b = asyncio.create_task(_publish_heartbeat(p_b, 0xbe))
        await asyncio.sleep(2.5)
        assert len(last_update_args) == 1
        assert last_update_args[0][0] == 0xB
        assert last_update_args[0][1] is None
        assert last_update_args[0][2] is not None
        assert last_update_args[0][2].heartbeat.uptime == 0
        assert last_update_args[0][2].heartbeat.vendor_specific_status_code == 0xbe
        last_update_args.clear()
        assert list(trk.registry.keys()) == [0xA, 0xB]
        assert 6 >= trk.registry[0xA].heartbeat.uptime >= 4
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xde
        assert trk.registry[0xA].info is None
        assert 3 >= trk.registry[0xB].heartbeat.uptime >= 2
        assert trk.registry[0xB].heartbeat.vendor_specific_status_code == 0xbe
        assert trk.registry[0xB].info is None

        # Enable get info servers. They will not be queried yet because the tracker node is anonymous.
        _serve_get_info(p_a, 'node-A')
        _serve_get_info(p_b, 'node-B')
        await asyncio.sleep(2.5)
        assert not last_update_args
        assert list(trk.registry.keys()) == [0xA, 0xB]
        assert 9 >= trk.registry[0xA].heartbeat.uptime >= 6
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xde
        assert trk.registry[0xA].info is None
        assert 6 >= trk.registry[0xB].heartbeat.uptime >= 4
        assert trk.registry[0xB].heartbeat.vendor_specific_status_code == 0xbe
        assert trk.registry[0xB].info is None

        # Create a new tracker, this time with a valid node-ID, and make sure node info is requested.
        # We are going to need a new handler for this.
        num_events_a = 0
        num_events_b = 0
        num_events_c = 0

        def validating_handler(node_id: int, old: typing.Optional[Entry], new: typing.Optional[Entry]) -> None:
            nonlocal num_events_a, num_events_b, num_events_c
            _logger.info('VALIDATING HANDLER %s %s %s', node_id, old, new)
            if node_id == 0xA:
                if num_events_a == 0:  # First detection
                    assert old is None
                    assert new is not None
                    assert new.heartbeat.vendor_specific_status_code == 0xde
                    assert new.info is None
                elif num_events_a == 1:  # Get info received
                    assert old is not None
                    assert new is not None
                    assert old.heartbeat.vendor_specific_status_code == 0xde
                    assert new.heartbeat.vendor_specific_status_code == 0xde
                    assert old.info is None
                    assert new.info is not None
                    assert new.info.name.tobytes().decode() == 'node-A'
                elif num_events_a == 2:  # Restart detected
                    assert old is not None
                    assert new is not None
                    assert old.heartbeat.vendor_specific_status_code == 0xde
                    assert new.heartbeat.vendor_specific_status_code == 0xfe
                    assert old.info is not None
                    assert new.info is None
                elif num_events_a == 3:  # Get info after restart received
                    assert old is not None
                    assert new is not None
                    assert old.heartbeat.vendor_specific_status_code == 0xfe
                    assert new.heartbeat.vendor_specific_status_code == 0xfe
                    assert old.info is None
                    assert new.info is not None
                    assert new.info.name.tobytes().decode() == 'node-A'
                elif num_events_a == 4:  # Offline
                    assert old is not None
                    assert new is None
                    assert old.heartbeat.vendor_specific_status_code == 0xfe
                    assert old.info is not None
                else:
                    assert False
                num_events_a += 1
            elif node_id == 0xB:
                if num_events_b == 0:
                    assert old is None
                    assert new is not None
                    assert new.heartbeat.vendor_specific_status_code == 0xbe
                    assert new.info is None
                elif num_events_b == 1:
                    assert old is not None
                    assert new is not None
                    assert old.heartbeat.vendor_specific_status_code == 0xbe
                    assert new.heartbeat.vendor_specific_status_code == 0xbe
                    assert old.info is None
                    assert new.info is not None
                    assert new.info.name.tobytes().decode() == 'node-B'
                elif num_events_b == 2:
                    assert old is not None
                    assert new is None
                    assert old.heartbeat.vendor_specific_status_code == 0xbe
                    assert old.info is not None
                else:
                    assert False
                num_events_b += 1
            elif node_id == 0xC:
                if num_events_c == 0:
                    assert old is None
                    assert new is not None
                    assert new.heartbeat.vendor_specific_status_code == 0xf0
                    assert new.info is None
                elif num_events_c == 1:
                    assert old is not None
                    assert new is None
                    assert old.heartbeat.vendor_specific_status_code == 0xf0
                    assert old.info is None
                else:
                    assert False
                num_events_c += 1
            else:
                assert False

        trk.close()
        trk.close()  # Idempotency
        p_trk = Presentation(get_transport(0xDD))
        trk = NodeTracker(p_trk)
        trk.add_update_handler(validating_handler)
        trk.start()
        trk.get_info_timeout = 1.0
        trk.get_info_attempts = 2
        assert pytest.approx(trk.get_info_timeout) == 1.0
        assert trk.get_info_attempts == 2

        await asyncio.sleep(2.5)
        assert num_events_a == 2
        assert num_events_b == 2
        assert num_events_c == 0
        assert list(trk.registry.keys()) == [0xA, 0xB]
        assert 12 >= trk.registry[0xA].heartbeat.uptime >= 8
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xde
        assert trk.registry[0xA].info is not None
        assert trk.registry[0xA].info.name.tobytes().decode() == 'node-A'
        assert 9 >= trk.registry[0xB].heartbeat.uptime >= 6
        assert trk.registry[0xB].heartbeat.vendor_specific_status_code == 0xbe
        assert trk.registry[0xB].info is not None
        assert trk.registry[0xB].info.name.tobytes().decode() == 'node-B'

        # Node B goes offline.
        hb_b.cancel()
        await asyncio.sleep(6)
        assert num_events_a == 2
        assert num_events_b == 3
        assert num_events_c == 0
        assert list(trk.registry.keys()) == [0xA]
        assert 20 >= trk.registry[0xA].heartbeat.uptime >= 12
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xde
        assert trk.registry[0xA].info is not None
        assert trk.registry[0xA].info.name.tobytes().decode() == 'node-A'

        # Node C appears online. It does not respond to GetInfo.
        hb_c = asyncio.create_task(_publish_heartbeat(p_c, 0xf0))
        await asyncio.sleep(6)
        assert num_events_a == 2
        assert num_events_b == 3
        assert num_events_c == 1
        assert list(trk.registry.keys()) == [0xA, 0xC]
        assert 28 >= trk.registry[0xA].heartbeat.uptime >= 17
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xde
        assert trk.registry[0xA].info is not None
        assert trk.registry[0xA].info.name.tobytes().decode() == 'node-A'
        assert 7 >= trk.registry[0xC].heartbeat.uptime >= 5
        assert trk.registry[0xC].heartbeat.vendor_specific_status_code == 0xf0
        assert trk.registry[0xC].info is None

        # Node A is restarted. Node C goes offline.
        hb_c.cancel()
        hb_a.cancel()
        hb_a = asyncio.create_task(_publish_heartbeat(p_a, 0xfe))
        await asyncio.sleep(6)
        assert num_events_a == 4  # Two extra events: node restart detection, then get info reception.
        assert num_events_b == 3
        assert num_events_c == 2
        assert list(trk.registry.keys()) == [0xA]
        assert 7 >= trk.registry[0xA].heartbeat.uptime >= 5
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xfe
        assert trk.registry[0xA].info is not None
        assert trk.registry[0xA].info.name.tobytes().decode() == 'node-A'

        # Node A goes offline. No online nodes are left standing.
        hb_a.cancel()
        await asyncio.sleep(6)
        assert num_events_a == 5
        assert num_events_b == 3
        assert num_events_c == 2
        assert not trk.registry

        # Finalization.
        trk.close()
        trk.close()  # Idempotency
        for c in [hb_a, hb_b, hb_c]:
            c.cancel()
    finally:
        for p in [p_a, p_b, p_c, p_trk]:
            p.close()
        await asyncio.sleep(1)  # Let all pending tasks finalize properly to avoid stack traces in the output.
Example #5
0
async def _unittest_spoofer(caplog: pytest.LogCaptureFixture) -> None:
    dcs_pres = Presentation(LoopbackTransport(1234))
    dcs_pub_spoof = dcs_pres.make_publisher(Spoof, 1)
    spoofer = Spoofer(dcs_pres.make_subscriber(Spoof, 1))

    # No target transports configured -- spoofing will do nothing except incrementing the transfer-ID counter.
    assert await dcs_pub_spoof.publish(
        Spoof(
            timeout=uavcan.si.unit.duration.Scalar_1_0(1.0),
            priority=org_uavcan_yukon.io.transfer.Priority_1_0(3),
            session=org_uavcan_yukon.io.transfer.Session_0_1(
                subject=org_uavcan_yukon.io.transfer.SubjectSession_0_1(
                    subject_id=uavcan.node.port.SubjectID_1_0(6666),
                    source=[uavcan.node.ID_1_0(1234)])),
            transfer_id=[],
            iface_id=[],
            payload=org_uavcan_yukon.io.transfer.Payload_1_0(b"Hello world!"),
        ))

    await asyncio.sleep(0.5)

    # Validate the transfer-ID map.
    assert len(spoofer._transfer_id_map) == 1
    assert list(spoofer._transfer_id_map.keys())[0].source_node_id == 1234
    assert list(spoofer._transfer_id_map.values())[0]._value == 1

    # Configure transports.
    cap_a: typing.List[pyuavcan.transport.Capture] = []
    cap_b: typing.List[pyuavcan.transport.Capture] = []
    target_tr_a = LoopbackTransport(None)
    target_tr_b = LoopbackTransport(None)
    target_tr_a.begin_capture(cap_a.append)
    target_tr_b.begin_capture(cap_b.append)
    spoofer.add_iface(111, target_tr_a)
    spoofer.add_iface(222, target_tr_b)

    # Spoof on both, successfully.
    spoof = Spoof(
        timeout=uavcan.si.unit.duration.Scalar_1_0(1.0),
        priority=org_uavcan_yukon.io.transfer.Priority_1_0(3),
        session=org_uavcan_yukon.io.transfer.Session_0_1(
            subject=org_uavcan_yukon.io.transfer.SubjectSession_0_1(
                subject_id=uavcan.node.port.SubjectID_1_0(6666),
                source=[uavcan.node.ID_1_0(1234)])),
        transfer_id=[9876543210],  # This transfer will not touch the TID map.
        iface_id=[],  # All ifaces.
        payload=org_uavcan_yukon.io.transfer.Payload_1_0(b"abcd"),
    )
    assert await dcs_pub_spoof.publish(spoof)
    await asyncio.sleep(0.5)

    assert len(cap_a) == len(cap_b)
    (cap, ) = cap_a
    cap_a.clear()
    cap_b.clear()
    assert isinstance(cap, LoopbackCapture)
    assert cap.transfer.metadata.transfer_id == 9876543210
    assert len(spoofer._transfer_id_map) == 1  # New entry was not created.
    assert spoofer.status == {
        111:
        IfaceStatus(num_bytes=4,
                    num_errors=0,
                    num_timeouts=0,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
        222:
        IfaceStatus(num_bytes=4,
                    num_errors=0,
                    num_timeouts=0,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
    }

    # Make one time out, the other raise an error, third one is closed.
    target_tr_a.spoof_result = False
    target_tr_b.spoof_result = RuntimeError("Intended exception")
    target_tr_c = LoopbackTransport(None)
    target_tr_c.close()
    spoofer.add_iface(0, target_tr_c)

    with caplog.at_level(logging.CRITICAL):
        assert await dcs_pub_spoof.publish(spoof)
        await asyncio.sleep(2.0)
    assert not cap_a
    assert not cap_b
    old_status = spoofer.status
    assert old_status == {
        0:
        IfaceStatus(num_bytes=0,
                    num_errors=1,
                    num_timeouts=0,
                    num_transfers=0,
                    backlog=0,
                    backlog_peak=0),
        111:
        IfaceStatus(num_bytes=4,
                    num_errors=0,
                    num_timeouts=1,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
        222:
        IfaceStatus(num_bytes=4,
                    num_errors=1,
                    num_timeouts=0,
                    num_transfers=1,
                    backlog=0,
                    backlog_peak=0),
    }

    # Force only one iface out of three. Check that the backlog counter goes up.
    spoof.iface_id = [0]
    assert await dcs_pub_spoof.publish(spoof)
    assert await dcs_pub_spoof.publish(spoof)
    assert await dcs_pub_spoof.publish(spoof)
    await asyncio.sleep(2.0)
    assert spoofer.status[0].backlog > 0
    assert spoofer.status[0].backlog_peak > 0
    assert spoofer.status[111] == old_status[111]
    assert spoofer.status[222] == old_status[222]

    # Finalize.
    spoofer.close()
    target_tr_a.close()
    target_tr_b.close()
    target_tr_c.close()
    dcs_pres.close()
    await asyncio.sleep(1.0)
Example #6
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.
def _uid(as_hex: str) -> bytes:
    out = bytes.fromhex(as_hex)
    assert len(out) == 16
    return out

_TABLE = pathlib.Path('allocation_table.sqlite.tmp')


if __name__ == '__main__':
    media = pyuavcan.transport.can.media.socketcan.SocketCANMedia('can0', mtu=64)
    #transport_client = pyuavcan.transport.can.CANTransport(media, local_node_id=None)
    transport_server = pyuavcan.transport.can.CANTransport(media, local_node_id=123)
    
    
    #pres_client = Presentation(transport_client)
    pres_server = Presentation(transport_server)

    allocator = CentralizedAllocator(pres_server, _uid('deadbeefdeadbeefdeadbeefdeadbeef'), _TABLE)
    allocator.start()   

    app_tasks = asyncio.Task.all_tasks()
    
    async def list_tasks_periodically() -> None:
        """Print active tasks periodically for demo purposes."""
        import re

        def repr_task(t: asyncio.Task) -> str:
            try:
                out, = re.findall(r'^<([^<]+<[^>]+>)', str(t))
            except ValueError:
                out = str(t)
Example #8
0
async def _unittest_slow_plug_and_play_centralized(
        generated_packages: typing.List[pyuavcan.dsdl.GeneratedPackageInfo],
        mtu: int) -> None:
    from pyuavcan.application.plug_and_play import CentralizedAllocator, Allocatee

    assert generated_packages

    asyncio.get_running_loop().slow_callback_duration = 5.0

    peers: typing.Set[MockMedia] = set()
    pres_client = Presentation(CANTransport(MockMedia(peers, mtu, 1), None))
    pres_server = Presentation(CANTransport(MockMedia(peers, mtu, 1), 123))

    cln_a = Allocatee(pres_client, _uid("00112233445566778899aabbccddeeff"),
                      42)
    assert cln_a.get_result() is None
    cln_a.start()
    await asyncio.sleep(2.0)
    assert cln_a.get_result() is None  # Nope, no response.

    try:
        _TABLE.unlink()
    except FileNotFoundError:
        pass
    with pytest.raises(ValueError, match=".*anonymous.*"):
        CentralizedAllocator(pres_client,
                             _uid("deadbeefdeadbeefdeadbeefdeadbeef"), _TABLE)
    with pytest.raises(ValueError):
        CentralizedAllocator(pres_client, b"123", _TABLE)
    allocator = CentralizedAllocator(pres_server,
                                     _uid("deadbeefdeadbeefdeadbeefdeadbeef"),
                                     _TABLE)
    allocator.start()

    allocator.register_node(41, None)
    allocator.register_node(
        41, _uid("00000000000000000000000000000001"))  # Overwrites
    allocator.register_node(42, _uid("00000000000000000000000000000002"))
    allocator.register_node(42, None)  # Does not overwrite
    allocator.register_node(43, _uid("0000000000000000000000000000000F"))
    allocator.register_node(
        43, _uid("00000000000000000000000000000003"))  # Overwrites
    allocator.register_node(43, None)  # Does not overwrite

    use_v2 = mtu > cln_a._MTU_THRESHOLD  # pylint: disable=protected-access
    await asyncio.sleep(2.0)
    assert cln_a.get_result() == (44 if use_v2 else 125)

    # Another request.
    cln_b = Allocatee(pres_client, _uid("aabbccddeeff00112233445566778899"))
    assert cln_b.get_result() is None
    cln_b.start()
    await asyncio.sleep(2.0)
    assert cln_b.get_result() == (125 if use_v2 else 124)

    # Re-request A and make sure we get the same response.
    cln_a = Allocatee(pres_client, _uid("00112233445566778899aabbccddeeff"),
                      42)
    assert cln_a.get_result() is None
    cln_a.start()
    await asyncio.sleep(2.0)
    assert cln_a.get_result() == (44 if use_v2 else 125)

    # C should be served from the manually added entries above.
    cln_c = Allocatee(pres_client, _uid("00000000000000000000000000000003"))
    assert cln_c.get_result() is None
    cln_c.start()
    await asyncio.sleep(2.0)
    assert cln_c.get_result() == 43

    # This one requires no allocation because the transport is not anonymous.
    cln_d = Allocatee(pres_server, _uid("00000000000000000000000000000009"),
                      100)
    assert cln_d.get_result() == 123
    cln_d.start()
    await asyncio.sleep(2.0)
    assert cln_d.get_result() == 123  # No change.

    # More test coverage needed.

    # Finalization.
    cln_a.close()
    cln_b.close()
    cln_c.close()
    cln_d.close()
    allocator.close()
    pres_client.close()
    pres_server.close()
    await asyncio.sleep(1.0)  # Let the tasks finalize properly.
Example #9
0
def subscribe(
    purser: yakut.Purser,
    subject: typing.Tuple[str, ...],
    with_metadata: bool,
    count: typing.Optional[int],
) -> None:
    """
    Subscribe to specified subjects and print messages into stdout.
    This command does not instantiate a local node and does not disturb the network in any way,
    so many instances can be cheaply executed concurrently.
    It is recommended to use anonymous transport (i.e., without a node-ID).

    The arguments are a list of message data type names prepended with the subject-ID;
    the subject-ID may be omitted if the data type defines a fixed one:

    \b
        [SUBJECT_ID.]TYPE_NAME.MAJOR.MINOR

    If multiple subjects are specified, a synchronous subscription will be used.
    It is useful for subscribing to a group of coupled subjects like lockstep sensor feeds,
    but it will not work for subjects that are temporally unrelated or published at different rates.

    Each object emitted into stdout is a key-value mapping where the number of elements equals the number
    of subjects the command is asked to subscribe to;
    the keys are subject-IDs and values are the received message objects.

    In data type names forward or backward slashes can be used instead of ".";
    version numbers can be also separated using underscores.
    This is done to allow the user to rely on filesystem autocompletion when typing the command.

    Examples:

    \b
        yakut sub 33.uavcan.si.unit.angle.Scalar.1.0 --no-metadata
    """
    _logger.debug("subject=%r, with_metadata=%r, count=%r", subject,
                  with_metadata, count)
    if not subject:
        _logger.info("Nothing to do because no subjects are specified")
        return
    if count is not None and count <= 0:
        _logger.info("Nothing to do because count=%s", count)
        return

    count = count if count is not None else sys.maxsize
    formatter = purser.make_formatter()

    transport = purser.get_transport()
    if transport.local_node_id is not None:
        _logger.info(
            "It is recommended to use an anonymous transport with this command."
        )

    with contextlib.closing(Presentation(transport)) as presentation:
        subscriber = _make_subscriber(subject, presentation)
        try:
            _run(subscriber,
                 formatter,
                 with_metadata=with_metadata,
                 count=count)
        finally:
            if _logger.isEnabledFor(logging.INFO):
                _logger.info("%s", presentation.transport.sample_statistics())
                _logger.info("%s", subscriber.sample_statistics())
Example #10
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.