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.
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
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
def fact(nid_a: typing.Optional[int], nid_b: typing.Optional[int]) -> TransportPack: bus: typing.Set[MockMedia] = set() media_a = MockMedia(bus, 8, 1) media_b = MockMedia(bus, 64, 2) # Heterogeneous setup assert bus == {media_a, media_b} return CANTransport(media_a, nid_a), CANTransport(media_b, nid_b), True
def can_socketcan_vcan0() -> typing.Iterator[TransportFactory]: from pyuavcan.transport.can import CANTransport from pyuavcan.transport.can.media.socketcan import SocketCANMedia yield lambda nid_a, nid_b: ( CANTransport(SocketCANMedia("vcan0", 16), nid_a), CANTransport(SocketCANMedia("vcan0", 64), nid_b), True, )
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
def _make_transport_can(node_id_a: typing.Optional[int], node_id_b: typing.Optional[int]) -> TransportPack: from pyuavcan.transport.can import CANTransport from tests.transport.can.media.mock import MockMedia bus: typing.Set[MockMedia] = set() media_a = MockMedia(bus, 8, 1) media_b = MockMedia(bus, 64, 2) # Heterogeneous setup assert bus == {media_a, media_b} return CANTransport(media_a, node_id_a), CANTransport(media_b, node_id_b), True
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
def _make_can( registers: MutableMapping[str, ValueProxy], node_id: Optional[int]) -> Iterator[pyuavcan.transport.Transport]: def init(name: str, default: RelaxedValue) -> ValueProxy: return registers.setdefault("uavcan.can." + name, ValueProxy(default)) iface_list = str(init("iface", "")).split() mtu = int(init("mtu", Natural16([64]))) br_arb, br_data = init("bitrate", Natural32([1_000_000, 4_000_000])).ints if iface_list: from pyuavcan.transport.can import CANTransport for iface in iface_list: media: pyuavcan.transport.can.media.Media if iface.lower().startswith("socketcan:"): from pyuavcan.transport.can.media.socketcan import SocketCANMedia media = SocketCANMedia(iface.split(":")[-1], mtu=mtu) else: from pyuavcan.transport.can.media.pythoncan import PythonCANMedia media = PythonCANMedia( iface, br_arb if br_arb == br_data else (br_arb, br_data), mtu) yield CANTransport(media, node_id)
def _get_run_configs() -> typing.Iterable[RunConfig]: """ Provides interface options to test the demo against. When adding new transports, add them to the demo and update this factory accordingly. Don't forget about redundant configurations, too. """ from pyuavcan.transport.redundant import RedundantTransport from pyuavcan.transport.serial import SerialTransport from pyuavcan.transport.udp import UDPTransport # UDP yield RunConfig( demo_env_vars={"DEMO_INTERFACE_KIND": "udp"}, local_transport_factory=lambda nid: UDPTransport(f"127.0.0.{1 if nid is None else nid}", anonymous=nid is None), ) # Serial yield RunConfig( demo_env_vars={"DEMO_INTERFACE_KIND": "serial"}, local_transport_factory=lambda nid: SerialTransport("socket://localhost:50905", local_node_id=nid), ) # DMR UDP+Serial 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 yield RunConfig( demo_env_vars={"DEMO_INTERFACE_KIND": "udp_serial"}, local_transport_factory=make_udp_serial, ) if sys.platform.startswith("linux"): from pyuavcan.transport.can.media.socketcan import SocketCANMedia from pyuavcan.transport.can import CANTransport # CAN yield RunConfig( demo_env_vars={"DEMO_INTERFACE_KIND": "can"}, # The demo uses Classic CAN! SocketCAN does not support nonuniform MTU well. local_transport_factory=lambda nid: CANTransport(SocketCANMedia("vcan0", 8), local_node_id=nid), ) # TMR CAN 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 yield RunConfig( demo_env_vars={"DEMO_INTERFACE_KIND": "can_can_can"}, local_transport_factory=make_tmr_can, )
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)
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.
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 = 1.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 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.