Beispiel #1
0
async def main() -> None:
    with make_node(NodeInfo(name="org.uavcan.pyuavcan.demo.plant"),
                   "plant.db") as node:
        # Expose internal states for diagnostics.
        node.registry[
            "status.saturation"] = lambda: saturation  # The register type will be deduced as "bit[1]".

        # Initialize values from the registry. The temperature is in kelvin because in UAVCAN everything follows SI.
        # Here, we specify the type explicitly as "real32[1]". If we pass a native float, it would be "real64[1]".
        temp_environment = float(
            node.registry.setdefault("model.environment.temperature",
                                     register.Real32([292.15])))
        temp_plant = temp_environment

        # Set up the ports.
        pub_meas = node.make_publisher(uavcan.si.sample.temperature.Scalar_1,
                                       "temperature")
        pub_meas.priority = pyuavcan.transport.Priority.HIGH
        sub_volt = node.make_subscriber(uavcan.si.unit.voltage.Scalar_1,
                                        "voltage")
        sub_volt.receive_in_background(handle_command)

        # Run the main loop forever.
        next_update_at = asyncio.get_running_loop().time()
        while True:
            # Publish new measurement and update node health.
            await pub_meas.publish(
                uavcan.si.sample.temperature.Scalar_1(
                    timestamp=uavcan.time.SynchronizedTimestamp_1(
                        microsecond=int(time.time() * 1e6)),
                    kelvin=temp_plant,
                ))
            node.heartbeat_publisher.health = Health.ADVISORY if saturation else Health.NOMINAL

            # Sleep until the next iteration.
            next_update_at += UPDATE_PERIOD
            await asyncio.sleep(next_update_at -
                                asyncio.get_running_loop().time())

            # Update the simulation.
            temp_plant += heater_voltage * 0.1 * UPDATE_PERIOD  # Energy input from the heater.
            temp_plant -= (temp_plant - temp_environment
                           ) * 0.05 * UPDATE_PERIOD  # Dissipation.
Beispiel #2
0
async def _unittest_slow_diagnostic_subscriber(compiled: typing.List[
    pyuavcan.dsdl.GeneratedPackageInfo], caplog: typing.Any) -> None:
    from pyuavcan.application import make_node, NodeInfo, diagnostic, make_registry
    from uavcan.time import SynchronizedTimestamp_1_0

    assert compiled
    asyncio.get_running_loop().slow_callback_duration = 1.0

    node = make_node(
        NodeInfo(),
        make_registry(None, typing.cast(Dict[str, bytes], {})),
        transport=LoopbackTransport(2222),
    )
    node.start()
    pub = node.make_publisher(diagnostic.Record)
    diagnostic.DiagnosticSubscriber(node)

    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"

    pub.close()
    node.close()
    await asyncio.sleep(1.0)  # Let the background tasks terminate.
async def _unittest_slow_plug_and_play_centralized(
    compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo], mtu: int
) -> None:
    from pyuavcan.application import make_node, NodeInfo
    from pyuavcan.application.plug_and_play import CentralizedAllocator, Allocatee

    assert compiled
    asyncio.get_running_loop().slow_callback_duration = 5.0

    peers: typing.Set[MockMedia] = set()
    trans_client = CANTransport(MockMedia(peers, mtu, 1), None)
    node_server = make_node(
        NodeInfo(unique_id=_uid("deadbeefdeadbeefdeadbeefdeadbeef")),
        transport=CANTransport(MockMedia(peers, mtu, 1), 123),
    )
    node_server.start()

    cln_a = Allocatee(trans_client, _uid("00112233445566778899aabbccddeeff"), 42)
    assert cln_a.get_result() is None
    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(make_node(NodeInfo(), transport=trans_client), _TABLE)
    allocator = CentralizedAllocator(node_server, _TABLE)

    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(trans_client, _uid("aabbccddeeff00112233445566778899"))
    assert cln_b.get_result() is None
    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(trans_client, _uid("00112233445566778899aabbccddeeff"), 42)
    assert cln_a.get_result() is None
    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(trans_client, _uid("00000000000000000000000000000003"))
    assert cln_c.get_result() is None
    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(node_server.presentation, _uid("00000000000000000000000000000009"), 100)
    assert cln_d.get_result() == 123
    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()
    trans_client.close()
    node_server.close()
    await asyncio.sleep(1.0)  # Let the tasks finalize properly.
Beispiel #4
0
async def _unittest_file(
        compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]) -> None:
    from pyuavcan.application import make_node, NodeInfo
    from pyuavcan.transport.udp import UDPTransport
    from pyuavcan.application.file import FileClient, FileServer, Error

    assert compiled
    asyncio.get_running_loop().slow_callback_duration = 3.0

    root_a = mkdtemp(".file", "a.")
    root_b = mkdtemp(".file", "b.")
    srv_node = make_node(
        NodeInfo(name="org.uavcan.pyuavcan.test.file.server"),
        transport=UDPTransport("127.63.0.0",
                               222,
                               service_transfer_multiplier=2),
    )
    cln_node = make_node(
        NodeInfo(name="org.uavcan.pyuavcan.test.file.client"),
        transport=UDPTransport("127.63.0.0",
                               223,
                               service_transfer_multiplier=2),
    )
    try:
        srv_node.start()
        file_server = FileServer(srv_node, [root_a, root_b])
        assert (Path(root_a), Path("abc")) == file_server.locate(Path("abc"))
        assert [] == list(file_server.glob("*"))

        cln_node.start()
        cln = FileClient(cln_node, 222)

        async def ls(path: str) -> typing.List[str]:
            out: typing.List[str] = []
            async for e in cln.list(path):
                out.append(e)
            return out

        assert [] == await ls("")
        assert [] == await ls("nonexistent/directory")
        assert (await cln.get_info("none")).error.value == Error.NOT_FOUND

        assert 0 == await cln.touch("a/foo/x")
        assert 0 == await cln.touch("a/foo/y")
        assert 0 == await cln.touch("b")
        assert ["foo"] == await ls("a")

        # Make sure files are created.
        assert [
            (file_server.roots[0], Path("a/foo/x")),
            (file_server.roots[0], Path("a/foo/y")),
        ] == list(sorted(file_server.glob("a/foo/*")))

        assert await cln.read("a/foo/x") == b""
        assert await cln.read(
            "/a/foo/x") == b""  # Slash or no slash makes no difference.
        assert await cln.read("a/foo/z") == Error.NOT_FOUND
        assert (await cln.get_info("a/foo/z")).error.value == Error.NOT_FOUND

        # Write non-existent file
        assert await cln.write("a/foo/z",
                               bytes(range(200)) * 3) == Error.NOT_FOUND

        # Write into empty file
        assert await cln.write("a/foo/x", bytes(range(200)) * 3) == 0
        assert await cln.read("a/foo/x") == bytes(range(200)) * 3
        assert (await cln.get_info("a/foo/x")).size == 600

        # Truncation -- this write is shorter
        hundred = bytes(x ^ 0xFF for x in range(100))
        assert await cln.write("a/foo/x", hundred * 4) == 0
        assert (await cln.get_info("a/foo/x")).size == 400
        assert await cln.read("a/foo/x") == (hundred * 4)
        assert (await cln.get_info("a/foo/x")).size == 400

        # Fill in the middle without truncation
        ref = bytearray(hundred * 4)
        for i in range(100):
            ref[i + 100] = 0x55
        assert len(ref) == 400
        assert (await cln.get_info("a/foo/x")).size == 400
        assert await cln.write("a/foo/x",
                               b"\x55" * 100,
                               offset=100,
                               truncate=False) == 0
        assert (await cln.get_info("a/foo/x")).size == 400
        assert await cln.read("a/foo/x") == ref

        # Fill in the middle with truncation
        assert await cln.write("a/foo/x", b"\xAA" * 50, offset=50) == 0
        assert (await cln.get_info("a/foo/x")).size == 100
        assert await cln.read("a/foo/x") == hundred[:50] + b"\xAA" * 50

        # Directories
        info = await cln.get_info("a/foo")
        print("a/foo:", info)
        assert info.error.value == 0
        assert info.is_writeable
        assert info.is_readable
        assert not info.is_file_not_directory
        assert not info.is_link

        assert (await
                cln.get_info("a/foo/nothing")).error.value == Error.NOT_FOUND
        assert await cln.write("a/foo", b"123") in (Error.IS_DIRECTORY,
                                                    Error.ACCESS_DENIED
                                                    )  # Windows compatibility

        # Removal
        assert (await cln.remove("a/foo/z")) == Error.NOT_FOUND
        assert (await cln.remove("a/foo/x")) == 0
        assert (await cln.touch("a/foo/x")) == 0  # Put it back
        assert (await cln.remove("a/foo/")) == 0  # Removed
        assert (await cln.remove("a/foo/")) == Error.NOT_FOUND  # Not found

        # Copy
        assert (await cln.touch("r/a")) == 0
        assert (await cln.touch("r/b/0")) == 0
        assert (await cln.touch("r/b/1")) == 0
        assert not (await cln.get_info("r/b")).is_file_not_directory
        assert ["a", "b"] == await ls("r")
        assert (await cln.copy("r/b", "r/c")) == 0
        assert ["a", "b", "c"] == await ls("r")
        assert (await cln.copy("r/a", "r/c")) != 0  # Overwrite not enabled
        assert ["a", "b", "c"] == await ls("r")
        assert not (await cln.get_info("r/c")).is_file_not_directory
        assert (await cln.copy("/r/a", "r/c", overwrite=True)) == 0
        assert (await cln.get_info("r/c")).is_file_not_directory

        # Move
        assert ["a", "b", "c"] == await ls("r")
        assert (await cln.move("/r/a", "r/c")) != 0  # Overwrite not enabled
        assert (await cln.move("/r/a", "r/c", overwrite=True)) == 0
        assert ["b", "c"] == await ls("r")
        assert (await cln.move("/r/a", "r/c",
                               overwrite=True)) == Error.NOT_FOUND
        assert ["b", "c"] == await ls("r")

        # Access protected files
        if sys.platform.startswith("linux"):  # pragma: no branch
            file_server.roots.append(Path("/"))
            info = await cln.get_info("dev/null")
            print("/dev/null:", info)
            assert info.error.value == 0
            assert not info.is_link
            assert info.is_writeable
            assert info.is_file_not_directory

            info = await cln.get_info("/bin/sh")
            print("/bin/sh:", info)
            assert info.error.value == 0
            assert not info.is_writeable
            assert info.is_file_not_directory

            assert await cln.read("/dev/null",
                                  size=100) == b""  # Read less than requested
            assert await cln.read(
                "/dev/zero",
                size=100) == b"\x00" * 256  # Read more than requested
            assert await cln.write("bin/sh", b"123") == Error.ACCESS_DENIED

            file_server.roots.pop(-1)
    finally:
        srv_node.close()
        cln_node.close()
        await asyncio.sleep(1.0)
        shutil.rmtree(root_a, ignore_errors=True)
        shutil.rmtree(root_b, ignore_errors=True)
Beispiel #5
0
async def _unittest_slow_node_tracker(
        compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]) -> None:
    from . import get_transport
    from uavcan.node import GetInfo_1_0
    from pyuavcan.application import make_node, NodeInfo
    from pyuavcan.application.node_tracker import NodeTracker, Entry

    assert compiled
    asyncio.get_running_loop().slow_callback_duration = 3.0

    n_a = make_node(NodeInfo(name="org.uavcan.pyuavcan.test.node_tracker.a"),
                    transport=get_transport(0xA))
    n_b = make_node(NodeInfo(name="org.uavcan.pyuavcan.test.node_tracker.b"),
                    transport=get_transport(0xB))
    n_c = make_node(NodeInfo(name="org.uavcan.pyuavcan.test.node_tracker.c"),
                    transport=get_transport(0xC))
    n_trk = make_node(
        NodeInfo(name="org.uavcan.pyuavcan.test.node_tracker.trk"),
        transport=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))

        trk = NodeTracker(n_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

        trk.add_update_handler(simple_handler)

        n_trk.start()
        n_trk.start()  # Idempotency.

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

        # Bring the first node online and make sure it is detected and reported.
        n_a.heartbeat_publisher.vendor_specific_status_code = 0xDE
        n_a.start()
        await asyncio.sleep(9)
        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 30 >= trk.registry[0xA].heartbeat.uptime >= 2
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xDE
        assert trk.registry[0xA].info is None

        # Bring the second node online and make sure it is detected and reported.
        n_b.heartbeat_publisher.vendor_specific_status_code = 0xBE
        n_b.start()
        await asyncio.sleep(9)
        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 60 >= trk.registry[0xA].heartbeat.uptime >= 4
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xDE
        assert trk.registry[0xA].info is None
        assert 30 >= trk.registry[0xB].heartbeat.uptime >= 2
        assert trk.registry[0xB].heartbeat.vendor_specific_status_code == 0xBE
        assert trk.registry[0xB].info is None

        await asyncio.sleep(9)
        assert not last_update_args
        assert list(trk.registry.keys()) == [0xA, 0xB]
        assert 90 >= trk.registry[0xA].heartbeat.uptime >= 6
        assert trk.registry[0xA].heartbeat.vendor_specific_status_code == 0xDE
        assert trk.registry[0xA].info is None
        assert 60 >= 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(
                    ) == "org.uavcan.pyuavcan.test.node_tracker.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(
                    ) == "org.uavcan.pyuavcan.test.node_tracker.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(
                    ) == "org.uavcan.pyuavcan.test.node_tracker.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

        n_trk.close()
        n_trk.close()  # Idempotency
        n_trk = make_node(n_trk.info, transport=get_transport(0xDD))
        n_trk.start()
        trk = NodeTracker(n_trk)
        trk.add_update_handler(validating_handler)
        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(9)
        assert num_events_a == 2
        assert num_events_b == 2
        assert num_events_c == 0
        assert list(trk.registry.keys()) == [0xA, 0xB]
        assert 60 >= 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(
        ) == "org.uavcan.pyuavcan.test.node_tracker.a"
        assert 60 >= 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(
        ) == "org.uavcan.pyuavcan.test.node_tracker.b"

        # Node B goes offline.
        n_b.close()
        await asyncio.sleep(9)
        assert num_events_a == 2
        assert num_events_b == 3
        assert num_events_c == 0
        assert list(trk.registry.keys()) == [0xA]
        assert 90 >= 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(
        ) == "org.uavcan.pyuavcan.test.node_tracker.a"

        # Node C appears online. It does not respond to GetInfo.
        n_c.heartbeat_publisher.vendor_specific_status_code = 0xF0
        n_c.start()
        # To make it not respond to GetInfo, get under the hood and break the transport session for this RPC-service.
        get_info_service_id = pyuavcan.dsdl.get_fixed_port_id(GetInfo_1_0)
        assert get_info_service_id
        for ses in n_c.presentation.transport.input_sessions:
            ds = ses.specifier.data_specifier
            if isinstance(ds, pyuavcan.transport.ServiceDataSpecifier
                          ) and ds.service_id == get_info_service_id:
                ses.close()
        await asyncio.sleep(9)
        assert num_events_a == 2
        assert num_events_b == 3
        assert num_events_c == 1
        assert list(trk.registry.keys()) == [0xA, 0xC]
        assert 180 >= 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(
        ) == "org.uavcan.pyuavcan.test.node_tracker.a"
        assert 30 >= 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.
        n_a.close()
        n_c.close()
        n_a = make_node(
            NodeInfo(name="org.uavcan.pyuavcan.test.node_tracker.a"),
            transport=get_transport(0xA))
        n_a.heartbeat_publisher.vendor_specific_status_code = 0xFE
        n_a.start()
        await asyncio.sleep(9)
        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 30 >= 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(
        ) == "org.uavcan.pyuavcan.test.node_tracker.a"

        # Node A goes offline. No online nodes are left standing.
        n_a.close()
        await asyncio.sleep(9)
        assert num_events_a == 5
        assert num_events_b == 3
        assert num_events_c == 2
        assert not trk.registry
    finally:
        for p in [n_a, n_b, n_c, n_trk]:
            p.close()
        await asyncio.sleep(
            1
        )  # Let all pending tasks finalize properly to avoid stack traces in the output.
Beispiel #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.