rosotacom-dev 2.5.dev38__tar.gz → 2.5.dev39__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +32 -2
  3. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +8 -2
  4. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +49 -36
  5. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  6. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/SOURCES.txt +2 -0
  7. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/scm_file_list.json +2 -0
  8. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  9. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_catmux_pane_logging.py +74 -0
  10. rosotacom_dev-2.5.dev39/tests/unit/test_qos_fallback_logging.py +139 -0
  11. rosotacom_dev-2.5.dev39/tests/unit/test_relay_optional_parameters.py +176 -0
  12. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.devcontainer/devcontainer.json.template +0 -0
  13. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  14. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/CODEOWNERS +0 -0
  15. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  16. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  17. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  18. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  19. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  20. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  21. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  22. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  23. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/actions/setup-just/action.yml +0 -0
  25. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/dependabot.yml +0 -0
  26. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/benchmark-calibrate.yml +0 -0
  27. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/benchmark-gate.yml +0 -0
  28. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/dev-release.yml +0 -0
  29. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/image-scan.yml +0 -0
  30. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/nightly-e2e.yml +0 -0
  31. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/pr-lightweight.yml +0 -0
  32. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/pr-merge-gate.yml +0 -0
  33. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.github/workflows/release.yml +0 -0
  34. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/.pre-commit-config.yaml +0 -0
  35. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/AGENTS.md +0 -0
  36. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/CONCURRENCY.md +0 -0
  37. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/CONTRIBUTING.md +0 -0
  38. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/DEVELOPMENT_PRINCIPLES.md +0 -0
  39. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/LICENSE.txt +0 -0
  40. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/MANIFEST.in +0 -0
  41. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/README.md +0 -0
  42. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/budgets.jsonl +0 -0
  43. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/README.md +0 -0
  44. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/capacity-size-tight-cyclone.json +0 -0
  45. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  46. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/probe-loss-nominal-cyclone.json +0 -0
  47. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/probe-loss-tight-cyclone.json +0 -0
  48. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/probe-loss-tight-fastdds.json +0 -0
  49. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/calibration/probe-loss-tight-zenoh.json +0 -0
  50. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/codecov.yml +0 -0
  51. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/deployment-configuration.md +0 -0
  52. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/benchmark-ab.md +0 -0
  53. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/benchmark-bag-as-load.md +0 -0
  54. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/ci.md +0 -0
  55. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/ffmpeg-keyframes.md +0 -0
  56. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/README.md +0 -0
  57. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  58. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/delay-alone-no-loss.md +0 -0
  59. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/head-of-line-irregularity.md +0 -0
  60. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  61. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/lighter-message-loss.md +0 -0
  62. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  63. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/forensics-report.md +0 -0
  64. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/geo-link-map.md +0 -0
  65. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/github-repository-settings.md +0 -0
  66. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/link-bandwidth.md +0 -0
  67. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/link-trace.md +0 -0
  68. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/owner-runbook.md +0 -0
  69. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/performance-bands.md +0 -0
  70. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/playbooks/README.md +0 -0
  71. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/quality-model.md +0 -0
  72. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/release-notes/TEMPLATE.md +0 -0
  73. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/release-notes/v2.1.md +0 -0
  74. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/release-notes/v2.2.md +0 -0
  75. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/release-notes/v2.3.md +0 -0
  76. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/release-notes/v2.4.md +0 -0
  77. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/release.md +0 -0
  78. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  79. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0002-expectation-concepts.md +0 -0
  80. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0003-metric-backbone.md +0 -0
  81. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0004-network-profiles.md +0 -0
  82. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  83. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0006-dynamic-qos.md +0 -0
  84. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/0007-regression-gate.md +0 -0
  85. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/rfcs/README.md +0 -0
  86. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/testing.md +0 -0
  87. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/docs/work-items.md +0 -0
  88. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/install.sh +0 -0
  89. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/justfile +0 -0
  90. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/media/control_center.png +0 -0
  91. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/media/echo_test.png +0 -0
  92. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/media/forensics-report-example.png +0 -0
  93. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/media/host.png +0 -0
  94. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/media/vehicle.png +0 -0
  95. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/pyproject.toml +0 -0
  96. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/requirements.txt +0 -0
  97. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/rosotacom.yaml +0 -0
  98. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/session-configuration.md +0 -0
  99. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/setup.cfg +0 -0
  100. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/__init__.py +0 -0
  101. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/__main__.py +0 -0
  102. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/anonymize.py +0 -0
  103. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/bag_ground_truth.py +0 -0
  104. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/benched_set.py +0 -0
  105. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/benchmark.py +0 -0
  106. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/benchmark_delta.py +0 -0
  107. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/bundle_check.py +0 -0
  108. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/cli.py +0 -0
  109. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/cli_benchmark.py +0 -0
  110. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/deployment.py +0 -0
  111. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/ffmpeg_packet.py +0 -0
  112. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/forensics.py +0 -0
  113. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/geomap.py +0 -0
  114. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/image_cache.py +0 -0
  115. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/network_profiles.py +0 -0
  116. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/network_shaper.py +0 -0
  117. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/plots.py +0 -0
  118. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/py.typed +0 -0
  119. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/benched-set.yaml +0 -0
  120. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/.gitignore +0 -0
  121. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/README.md +0 -0
  122. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/__init__.py +0 -0
  123. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  124. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  125. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  126. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  127. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  128. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  129. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  130. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  131. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  132. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  133. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  134. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  135. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  136. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  137. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  138. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  139. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  140. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  141. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  142. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  143. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  144. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  145. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  146. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  147. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  148. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  149. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  150. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  151. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  152. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  153. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  154. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  155. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  156. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  157. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  158. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  159. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  160. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  161. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  162. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  163. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  164. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  165. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  166. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  167. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  168. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  169. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  170. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  171. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  172. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  173. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  174. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  175. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  176. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  177. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  178. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  179. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  180. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  181. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  182. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  183. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  184. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  185. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  186. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  187. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ros2docker.json.example +0 -0
  198. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  199. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  200. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  201. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  202. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  203. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  204. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  205. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  206. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  207. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  208. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  209. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  210. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  211. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  212. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  213. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  214. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  215. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  216. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  217. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  218. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  219. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  220. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  221. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  222. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  223. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  224. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  225. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  226. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  227. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  228. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  229. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  230. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  231. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  232. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  233. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  234. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  235. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  236. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  237. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  238. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  239. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  240. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  241. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  242. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  243. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  244. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  245. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  246. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  247. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  248. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  249. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  250. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  251. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  252. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  253. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  254. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  255. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  256. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  257. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  258. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  259. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  260. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  261. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  262. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  263. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  264. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  265. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  266. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  267. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  268. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  269. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  270. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  271. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  272. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  273. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  274. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  275. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  276. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  277. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  278. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  279. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  280. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  281. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  282. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  283. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  284. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/status_eval.py +0 -0
  285. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/stream_stats.py +0 -0
  286. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/trace_profiles.py +0 -0
  287. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/transit.py +0 -0
  288. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom/video_quality.py +0 -0
  289. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  290. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  291. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  292. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  293. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/terminology.md +0 -0
  294. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_benched_set_registry.py +0 -0
  295. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_findings.py +0 -0
  296. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_markdown_links.py +0 -0
  297. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_ota_source_bundle.py +0 -0
  298. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_package_metadata.py +0 -0
  299. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_packaged_resources.py +0 -0
  300. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  301. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_pytest_policy.py +0 -0
  302. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_readme_examples.py +0 -0
  303. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_security_maintenance_config.py +0 -0
  304. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_session_configuration_doc.py +0 -0
  305. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_session_template_substitution.py +0 -0
  306. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/contract/test_workflow_contracts.py +0 -0
  307. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/conftest.py +0 -0
  308. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_anonymize_e2e.py +0 -0
  309. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_benchmark_ab.py +0 -0
  310. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_benchmark_capacity.py +0 -0
  311. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_benchmark_replay.py +0 -0
  312. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_parallel_smoke.py +0 -0
  313. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_smoke.py +0 -0
  314. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_timeline_stepping.py +0 -0
  315. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/e2e/test_video_quality_e2e.py +0 -0
  316. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/sessions/generate_rmw_matrix.py +0 -0
  317. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  318. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  319. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  320. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  321. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  322. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_anonymize.py +0 -0
  323. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_bag_ground_truth.py +0 -0
  324. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_benched_set.py +0 -0
  325. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_benchmark.py +0 -0
  326. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_benchmark_delta.py +0 -0
  327. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_bundle_check.py +0 -0
  328. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_cli.py +0 -0
  329. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_cli_benchmark.py +0 -0
  330. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_concurrency.py +0 -0
  331. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_deployment.py +0 -0
  332. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_ffmpeg_packet.py +0 -0
  333. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_forensics.py +0 -0
  334. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_geomap.py +0 -0
  335. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_image_cache.py +0 -0
  336. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_link_bytes.py +0 -0
  337. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_link_trace.py +0 -0
  338. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_network_profiles.py +0 -0
  339. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_network_shaper.py +0 -0
  340. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_ota_unwrapper.py +0 -0
  341. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_ota_wrapper.py +0 -0
  342. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_ota_xml.py +0 -0
  343. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_plots.py +0 -0
  344. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_session_pipeline.py +0 -0
  345. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_stage_latency.py +0 -0
  346. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_status_eval.py +0 -0
  347. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_status_overview.py +0 -0
  348. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_status_overview_observer.py +0 -0
  349. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_stream_stats.py +0 -0
  350. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_trace_profiles.py +0 -0
  351. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_transit.py +0 -0
  352. {rosotacom_dev-2.5.dev38 → rosotacom_dev-2.5.dev39}/tests/unit/test_video_quality.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev38
3
+ Version: 2.5.dev39
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -61,8 +61,8 @@ class BaseBridgeRelay(Node, PairRefreshMixin):
61
61
  self.qos_config_file = self.declare_parameter('qos_config_file', '').value
62
62
 
63
63
  # Optional parameters with defaults
64
- self.source_names = self.declare_parameter('source_names', rclpy.Parameter.Type.STRING_ARRAY).value or []
65
- self.target_names = self.declare_parameter('target_names', rclpy.Parameter.Type.STRING_ARRAY).value or []
64
+ self.source_names = self.declare_optional_string_array('source_names')
65
+ self.target_names = self.declare_optional_string_array('target_names')
66
66
 
67
67
  # Derived classes can declare additional parameters here (must happen before initialize_node()).
68
68
  self.declare_additional_parameters()
@@ -73,6 +73,36 @@ class BaseBridgeRelay(Node, PairRefreshMixin):
73
73
  # Create timer for periodic refresh of invalid pairs
74
74
  self.init_pair_refresh(period_s=5.0, log_prefix=self.node_role)
75
75
 
76
+ def declare_optional_string_array(self, name: str) -> list:
77
+ """Declare an optional STRING_ARRAY that reads as [] rather than as unset.
78
+
79
+ Declaring a type without a value leaves the parameter
80
+ PARAMETER_NOT_SET. This node never notices — it reads the value once,
81
+ here, and an unset parameter simply yields None. Everything *else*
82
+ notices: reading a not-set parameter raises, and rclpy's parameter
83
+ service logs one warning per read against this node,
84
+
85
+ [WARN] [ota_relay_out]: Failed to get parameters: The parameter
86
+ 'source_names' is not initialized: source_names
87
+
88
+ which anything that enumerates parameters then produces on a timer. A
89
+ Foxglove bridge with a parameter panel open put 107 of those lines into
90
+ a single pane in one session — the kind of noise that hides the message
91
+ somebody actually needs to see.
92
+
93
+ A plain default cannot express this: `declare_parameter(name, [])`
94
+ infers the type from the value, and an empty list satisfies the
95
+ BYTE_ARRAY test first (`all(...)` is true for an empty sequence). The
96
+ parameter would be typed BYTE_ARRAY and the real string array refused
97
+ later with "Wrong parameter type, expected '5' got '9'". Declaring the
98
+ type and then setting the empty value keeps it STRING_ARRAY, so a wrong
99
+ type is still rejected and an override still wins.
100
+ """
101
+ self.declare_parameter(name, rclpy.Parameter.Type.STRING_ARRAY)
102
+ if self.get_parameter_or(name).type_ == rclpy.Parameter.Type.NOT_SET:
103
+ self.set_parameters([rclpy.Parameter(name, rclpy.Parameter.Type.STRING_ARRAY, [])])
104
+ return self.get_parameter(name).value
105
+
76
106
  def initialize_node(self):
77
107
  """Initialize node with resolved topics and QoS configuration."""
78
108
  try:
@@ -139,7 +139,13 @@ def get_topic_qos(logger, qos_config: dict, topic_name: str, role_name: str):
139
139
  Additionally supports a fallback lookup: if no exact match for topic_name exists
140
140
  in qos_config['topics'], we look for the most specific configured base topic key
141
141
  that matches as a path segment within topic_name (e.g. '/tf' for
142
- '/shuttle_ella/tf/restamped'). If such a fallback is used, it is always logged.
142
+ '/shuttle_ella/tf/restamped'). Such a fallback is logged at debug level: it is
143
+ the designed path, not a degradation. Every processing stage appends a suffix,
144
+ so a session that uses framebridge, latching or a transport reaches it for most
145
+ of its topics, and a definition that declares QoS on the base topic and lets the
146
+ processed names inherit it is doing exactly what the schema intends. Logged as a
147
+ warning it was two lines per topic per peer at every start — enough noise around
148
+ a bring-up to bury an actual fault.
143
149
  """
144
150
  default = qos_config.get('default', {})
145
151
 
@@ -151,7 +157,7 @@ def get_topic_qos(logger, qos_config: dict, topic_name: str, role_name: str):
151
157
  if topic_name not in topics_cfg:
152
158
  base_match = _find_best_base_topic_match(topics_cfg, topic_name)
153
159
  if base_match is not None:
154
- logger.warning(
160
+ logger.debug(
155
161
  f"[dds_qos] No exact QoS match for topic='{topic_name}'. "
156
162
  f"Falling back to base topic='{base_match}'."
157
163
  )
@@ -534,6 +534,19 @@ windows:
534
534
  - commands:
535
535
  - if [ '${ipx_4_topics}' = "None" ]; then exit; fi
536
536
  - ros2 run com_py image_pixel_capper --ros-args -p topics:=[${ipx_4_topics}] -p max_pixels_preset:=${ipx_4_preset} -p qos_config_file:='${qos_config_file}'
537
+ # `if ...; then ...; fi`, never `[[ ... ]] && ...`, for the optional argument
538
+ # lines below. catmux sends every one of these to an interactive shell, and
539
+ # `catmux_log_setup.sh` hooks the pane-exit recorder into PROMPT_COMMAND — so
540
+ # it sees the status of each line, not just of the node the pane finally runs.
541
+ # `&&` short-circuits to status 1 whenever the option is unset, which is the
542
+ # normal case for every option a session does not override, so each of these
543
+ # lines wrote itself into `pane_failures.log` as a dead pane:
544
+ #
545
+ # pane=13-IT/0 exit=1 command=[[ "None" != "None" ]] && args+=(-p "ut.foxglove.bit_rate:=None")
546
+ #
547
+ # A peer then reported `STARTUP CHECK FAILED ... 4 pane(s) exited` on every
548
+ # start while nothing was wrong, which is the one thing that check must not
549
+ # do: it exists to make a genuinely dead pane visible in seconds.
537
550
  - name: IT
538
551
  if: it
539
552
  layout: even-vertical
@@ -542,15 +555,15 @@ windows:
542
555
  - if [ '${it_1_topic}' = "None" ]; then exit; fi
543
556
  - if [[ "${it_1_topic}" == *"/compressed"* ]]; then it_in_transport="compressed"; else it_in_transport="raw"; fi
544
557
  - args=(ros2 run image_transport republish --ros-args -r __node:=republish1 -p in_transport:=${it_in_transport})
545
- - '[[ "${it_1_transport}" != "None" ]] && args+=(-p "out_transport:=${it_1_transport}")'
546
- - '[[ "${it_1_compressed_jpeg_quality}" != "None" ]] && args+=(-p "ut.compressed.jpeg_quality:=${it_1_compressed_jpeg_quality}")'
547
- - '[[ "${it_1_ffmpeg_gop_size}" != "None" ]] && args+=(-p "ut.ffmpeg.gop_size:=${it_1_ffmpeg_gop_size}")'
548
- - '[[ "${it_1_ffmpeg_encoder_av_options}" != "None" ]] && args+=(-p "ut.ffmpeg.encoder_av_options:=${it_1_ffmpeg_encoder_av_options}")'
549
- - '[[ "${it_1_ffmpeg_bit_rate}" != "None" ]] && args+=(-p "ut.ffmpeg.bit_rate:=${it_1_ffmpeg_bit_rate}")'
550
- - '[[ "${it_1_foxglove_gop_size}" != "None" ]] && args+=(-p "ut.foxglove.gop_size:=${it_1_foxglove_gop_size}")'
551
- - '[[ "${it_1_foxglove_encoder_av_options}" != "None" ]] && args+=(-p "ut.foxglove.encoder_av_options:=${it_1_foxglove_encoder_av_options}")'
552
- - '[[ "${it_1_foxglove_bit_rate}" != "None" ]] && args+=(-p "ut.foxglove.bit_rate:=${it_1_foxglove_bit_rate}")'
553
- - '[[ "${it_1_foxglove_qmax}" != "None" ]] && args+=(-p "ut.foxglove.qmax:=${it_1_foxglove_qmax}")'
558
+ - 'if [[ "${it_1_transport}" != "None" ]]; then args+=(-p "out_transport:=${it_1_transport}"); fi'
559
+ - 'if [[ "${it_1_compressed_jpeg_quality}" != "None" ]]; then args+=(-p "ut.compressed.jpeg_quality:=${it_1_compressed_jpeg_quality}"); fi'
560
+ - 'if [[ "${it_1_ffmpeg_gop_size}" != "None" ]]; then args+=(-p "ut.ffmpeg.gop_size:=${it_1_ffmpeg_gop_size}"); fi'
561
+ - 'if [[ "${it_1_ffmpeg_encoder_av_options}" != "None" ]]; then args+=(-p "ut.ffmpeg.encoder_av_options:=${it_1_ffmpeg_encoder_av_options}"); fi'
562
+ - 'if [[ "${it_1_ffmpeg_bit_rate}" != "None" ]]; then args+=(-p "ut.ffmpeg.bit_rate:=${it_1_ffmpeg_bit_rate}"); fi'
563
+ - 'if [[ "${it_1_foxglove_gop_size}" != "None" ]]; then args+=(-p "ut.foxglove.gop_size:=${it_1_foxglove_gop_size}"); fi'
564
+ - 'if [[ "${it_1_foxglove_encoder_av_options}" != "None" ]]; then args+=(-p "ut.foxglove.encoder_av_options:=${it_1_foxglove_encoder_av_options}"); fi'
565
+ - 'if [[ "${it_1_foxglove_bit_rate}" != "None" ]]; then args+=(-p "ut.foxglove.bit_rate:=${it_1_foxglove_bit_rate}"); fi'
566
+ - 'if [[ "${it_1_foxglove_qmax}" != "None" ]]; then args+=(-p "ut.foxglove.qmax:=${it_1_foxglove_qmax}"); fi'
554
567
  - if [[ "${it_1_outgoing_suffix}" != "None" ]]; then outgoing_suffix="${it_1_outgoing_suffix}"; else outgoing_suffix="${it_1_transport}"; fi
555
568
  - if [[ "${it_in_transport}" == "raw" ]]; then in_remap="in"; else in_remap="in/${it_in_transport}"; fi
556
569
  - args+=(--remap "${in_remap}:=${it_1_topic}" --remap "out/${it_1_transport}:=${it_1_topic}/${outgoing_suffix}")
@@ -560,15 +573,15 @@ windows:
560
573
  - if [ '${it_2_topic}' = "None" ]; then exit; fi
561
574
  - if [[ "${it_2_topic}" == *"/compressed"* ]]; then it_in_transport="compressed"; else it_in_transport="raw"; fi
562
575
  - args=(ros2 run image_transport republish --ros-args -r __node:=republish2 -p in_transport:=${it_in_transport})
563
- - '[[ "${it_2_transport}" != "None" ]] && args+=(-p "out_transport:=${it_2_transport}")'
564
- - '[[ "${it_2_compressed_jpeg_quality}" != "None" ]] && args+=(-p "ut.compressed.jpeg_quality:=${it_2_compressed_jpeg_quality}")'
565
- - '[[ "${it_2_ffmpeg_gop_size}" != "None" ]] && args+=(-p "ut.ffmpeg.gop_size:=${it_2_ffmpeg_gop_size}")'
566
- - '[[ "${it_2_ffmpeg_encoder_av_options}" != "None" ]] && args+=(-p "ut.ffmpeg.encoder_av_options:=${it_2_ffmpeg_encoder_av_options}")'
567
- - '[[ "${it_2_ffmpeg_bit_rate}" != "None" ]] && args+=(-p "ut.ffmpeg.bit_rate:=${it_2_ffmpeg_bit_rate}")'
568
- - '[[ "${it_2_foxglove_gop_size}" != "None" ]] && args+=(-p "ut.foxglove.gop_size:=${it_2_foxglove_gop_size}")'
569
- - '[[ "${it_2_foxglove_encoder_av_options}" != "None" ]] && args+=(-p "ut.foxglove.encoder_av_options:=${it_2_foxglove_encoder_av_options}")'
570
- - '[[ "${it_2_foxglove_bit_rate}" != "None" ]] && args+=(-p "ut.foxglove.bit_rate:=${it_2_foxglove_bit_rate}")'
571
- - '[[ "${it_2_foxglove_qmax}" != "None" ]] && args+=(-p "ut.foxglove.qmax:=${it_2_foxglove_qmax}")'
576
+ - 'if [[ "${it_2_transport}" != "None" ]]; then args+=(-p "out_transport:=${it_2_transport}"); fi'
577
+ - 'if [[ "${it_2_compressed_jpeg_quality}" != "None" ]]; then args+=(-p "ut.compressed.jpeg_quality:=${it_2_compressed_jpeg_quality}"); fi'
578
+ - 'if [[ "${it_2_ffmpeg_gop_size}" != "None" ]]; then args+=(-p "ut.ffmpeg.gop_size:=${it_2_ffmpeg_gop_size}"); fi'
579
+ - 'if [[ "${it_2_ffmpeg_encoder_av_options}" != "None" ]]; then args+=(-p "ut.ffmpeg.encoder_av_options:=${it_2_ffmpeg_encoder_av_options}"); fi'
580
+ - 'if [[ "${it_2_ffmpeg_bit_rate}" != "None" ]]; then args+=(-p "ut.ffmpeg.bit_rate:=${it_2_ffmpeg_bit_rate}"); fi'
581
+ - 'if [[ "${it_2_foxglove_gop_size}" != "None" ]]; then args+=(-p "ut.foxglove.gop_size:=${it_2_foxglove_gop_size}"); fi'
582
+ - 'if [[ "${it_2_foxglove_encoder_av_options}" != "None" ]]; then args+=(-p "ut.foxglove.encoder_av_options:=${it_2_foxglove_encoder_av_options}"); fi'
583
+ - 'if [[ "${it_2_foxglove_bit_rate}" != "None" ]]; then args+=(-p "ut.foxglove.bit_rate:=${it_2_foxglove_bit_rate}"); fi'
584
+ - 'if [[ "${it_2_foxglove_qmax}" != "None" ]]; then args+=(-p "ut.foxglove.qmax:=${it_2_foxglove_qmax}"); fi'
572
585
  - if [[ "${it_2_outgoing_suffix}" != "None" ]]; then outgoing_suffix="${it_2_outgoing_suffix}"; else outgoing_suffix="${it_2_transport}"; fi
573
586
  - if [[ "${it_in_transport}" == "raw" ]]; then in_remap="in"; else in_remap="in/${it_in_transport}"; fi
574
587
  - args+=(--remap "${in_remap}:=${it_2_topic}" --remap "out/${it_2_transport}:=${it_2_topic}/${outgoing_suffix}")
@@ -578,15 +591,15 @@ windows:
578
591
  - if [ '${it_3_topic}' = "None" ]; then exit; fi
579
592
  - if [[ "${it_3_topic}" == *"/compressed"* ]]; then it_in_transport="compressed"; else it_in_transport="raw"; fi
580
593
  - args=(ros2 run image_transport republish --ros-args -r __node:=republish3 -p in_transport:=${it_in_transport})
581
- - '[[ "${it_3_transport}" != "None" ]] && args+=(-p "out_transport:=${it_3_transport}")'
582
- - '[[ "${it_3_compressed_jpeg_quality}" != "None" ]] && args+=(-p "ut.compressed.jpeg_quality:=${it_3_compressed_jpeg_quality}")'
583
- - '[[ "${it_3_ffmpeg_gop_size}" != "None" ]] && args+=(-p "ut.ffmpeg.gop_size:=${it_3_ffmpeg_gop_size}")'
584
- - '[[ "${it_3_ffmpeg_encoder_av_options}" != "None" ]] && args+=(-p "ut.ffmpeg.encoder_av_options:=${it_3_ffmpeg_encoder_av_options}")'
585
- - '[[ "${it_3_ffmpeg_bit_rate}" != "None" ]] && args+=(-p "ut.ffmpeg.bit_rate:=${it_3_ffmpeg_bit_rate}")'
586
- - '[[ "${it_3_foxglove_gop_size}" != "None" ]] && args+=(-p "ut.foxglove.gop_size:=${it_3_foxglove_gop_size}")'
587
- - '[[ "${it_3_foxglove_encoder_av_options}" != "None" ]] && args+=(-p "ut.foxglove.encoder_av_options:=${it_3_foxglove_encoder_av_options}")'
588
- - '[[ "${it_3_foxglove_bit_rate}" != "None" ]] && args+=(-p "ut.foxglove.bit_rate:=${it_3_foxglove_bit_rate}")'
589
- - '[[ "${it_3_foxglove_qmax}" != "None" ]] && args+=(-p "ut.foxglove.qmax:=${it_3_foxglove_qmax}")'
594
+ - 'if [[ "${it_3_transport}" != "None" ]]; then args+=(-p "out_transport:=${it_3_transport}"); fi'
595
+ - 'if [[ "${it_3_compressed_jpeg_quality}" != "None" ]]; then args+=(-p "ut.compressed.jpeg_quality:=${it_3_compressed_jpeg_quality}"); fi'
596
+ - 'if [[ "${it_3_ffmpeg_gop_size}" != "None" ]]; then args+=(-p "ut.ffmpeg.gop_size:=${it_3_ffmpeg_gop_size}"); fi'
597
+ - 'if [[ "${it_3_ffmpeg_encoder_av_options}" != "None" ]]; then args+=(-p "ut.ffmpeg.encoder_av_options:=${it_3_ffmpeg_encoder_av_options}"); fi'
598
+ - 'if [[ "${it_3_ffmpeg_bit_rate}" != "None" ]]; then args+=(-p "ut.ffmpeg.bit_rate:=${it_3_ffmpeg_bit_rate}"); fi'
599
+ - 'if [[ "${it_3_foxglove_gop_size}" != "None" ]]; then args+=(-p "ut.foxglove.gop_size:=${it_3_foxglove_gop_size}"); fi'
600
+ - 'if [[ "${it_3_foxglove_encoder_av_options}" != "None" ]]; then args+=(-p "ut.foxglove.encoder_av_options:=${it_3_foxglove_encoder_av_options}"); fi'
601
+ - 'if [[ "${it_3_foxglove_bit_rate}" != "None" ]]; then args+=(-p "ut.foxglove.bit_rate:=${it_3_foxglove_bit_rate}"); fi'
602
+ - 'if [[ "${it_3_foxglove_qmax}" != "None" ]]; then args+=(-p "ut.foxglove.qmax:=${it_3_foxglove_qmax}"); fi'
590
603
  - if [[ "${it_3_outgoing_suffix}" != "None" ]]; then outgoing_suffix="${it_3_outgoing_suffix}"; else outgoing_suffix="${it_3_transport}"; fi
591
604
  - if [[ "${it_in_transport}" == "raw" ]]; then in_remap="in"; else in_remap="in/${it_in_transport}"; fi
592
605
  - args+=(--remap "${in_remap}:=${it_3_topic}" --remap "out/${it_3_transport}:=${it_3_topic}/${outgoing_suffix}")
@@ -596,15 +609,15 @@ windows:
596
609
  - if [ '${it_4_topic}' = "None" ]; then exit; fi
597
610
  - if [[ "${it_4_topic}" == *"/compressed"* ]]; then it_in_transport="compressed"; else it_in_transport="raw"; fi
598
611
  - args=(ros2 run image_transport republish --ros-args -r __node:=republish4 -p in_transport:=${it_in_transport})
599
- - '[[ "${it_4_transport}" != "None" ]] && args+=(-p "out_transport:=${it_4_transport}")'
600
- - '[[ "${it_4_compressed_jpeg_quality}" != "None" ]] && args+=(-p "ut.compressed.jpeg_quality:=${it_4_compressed_jpeg_quality}")'
601
- - '[[ "${it_4_ffmpeg_gop_size}" != "None" ]] && args+=(-p "ut.ffmpeg.gop_size:=${it_4_ffmpeg_gop_size}")'
602
- - '[[ "${it_4_ffmpeg_encoder_av_options}" != "None" ]] && args+=(-p "ut.ffmpeg.encoder_av_options:=${it_4_ffmpeg_encoder_av_options}")'
603
- - '[[ "${it_4_ffmpeg_bit_rate}" != "None" ]] && args+=(-p "ut.ffmpeg.bit_rate:=${it_4_ffmpeg_bit_rate}")'
604
- - '[[ "${it_4_foxglove_gop_size}" != "None" ]] && args+=(-p "ut.foxglove.gop_size:=${it_4_foxglove_gop_size}")'
605
- - '[[ "${it_4_foxglove_encoder_av_options}" != "None" ]] && args+=(-p "ut.foxglove.encoder_av_options:=${it_4_foxglove_encoder_av_options}")'
606
- - '[[ "${it_4_foxglove_bit_rate}" != "None" ]] && args+=(-p "ut.foxglove.bit_rate:=${it_4_foxglove_bit_rate}")'
607
- - '[[ "${it_4_foxglove_qmax}" != "None" ]] && args+=(-p "ut.foxglove.qmax:=${it_4_foxglove_qmax}")'
612
+ - 'if [[ "${it_4_transport}" != "None" ]]; then args+=(-p "out_transport:=${it_4_transport}"); fi'
613
+ - 'if [[ "${it_4_compressed_jpeg_quality}" != "None" ]]; then args+=(-p "ut.compressed.jpeg_quality:=${it_4_compressed_jpeg_quality}"); fi'
614
+ - 'if [[ "${it_4_ffmpeg_gop_size}" != "None" ]]; then args+=(-p "ut.ffmpeg.gop_size:=${it_4_ffmpeg_gop_size}"); fi'
615
+ - 'if [[ "${it_4_ffmpeg_encoder_av_options}" != "None" ]]; then args+=(-p "ut.ffmpeg.encoder_av_options:=${it_4_ffmpeg_encoder_av_options}"); fi'
616
+ - 'if [[ "${it_4_ffmpeg_bit_rate}" != "None" ]]; then args+=(-p "ut.ffmpeg.bit_rate:=${it_4_ffmpeg_bit_rate}"); fi'
617
+ - 'if [[ "${it_4_foxglove_gop_size}" != "None" ]]; then args+=(-p "ut.foxglove.gop_size:=${it_4_foxglove_gop_size}"); fi'
618
+ - 'if [[ "${it_4_foxglove_encoder_av_options}" != "None" ]]; then args+=(-p "ut.foxglove.encoder_av_options:=${it_4_foxglove_encoder_av_options}"); fi'
619
+ - 'if [[ "${it_4_foxglove_bit_rate}" != "None" ]]; then args+=(-p "ut.foxglove.bit_rate:=${it_4_foxglove_bit_rate}"); fi'
620
+ - 'if [[ "${it_4_foxglove_qmax}" != "None" ]]; then args+=(-p "ut.foxglove.qmax:=${it_4_foxglove_qmax}"); fi'
608
621
  - if [[ "${it_4_outgoing_suffix}" != "None" ]]; then outgoing_suffix="${it_4_outgoing_suffix}"; else outgoing_suffix="${it_4_transport}"; fi
609
622
  - if [[ "${it_in_transport}" == "raw" ]]; then in_remap="in"; else in_remap="in/${it_in_transport}"; fi
610
623
  - args+=(--remap "${in_remap}:=${it_4_topic}" --remap "out/${it_4_transport}:=${it_4_topic}/${outgoing_suffix}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev38
3
+ Version: 2.5.dev39
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -337,6 +337,8 @@ tests/unit/test_ota_unwrapper.py
337
337
  tests/unit/test_ota_wrapper.py
338
338
  tests/unit/test_ota_xml.py
339
339
  tests/unit/test_plots.py
340
+ tests/unit/test_qos_fallback_logging.py
341
+ tests/unit/test_relay_optional_parameters.py
340
342
  tests/unit/test_session_pipeline.py
341
343
  tests/unit/test_stage_latency.py
342
344
  tests/unit/test_status_eval.py
@@ -332,6 +332,8 @@
332
332
  "tests/unit/test_ota_wrapper.py",
333
333
  "tests/unit/test_ota_xml.py",
334
334
  "tests/unit/test_plots.py",
335
+ "tests/unit/test_qos_fallback_logging.py",
336
+ "tests/unit/test_relay_optional_parameters.py",
335
337
  "tests/unit/test_session_pipeline.py",
336
338
  "tests/unit/test_stage_latency.py",
337
339
  "tests/unit/test_status_eval.py",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.4",
3
- "distance": 38,
4
- "node": "g7225afdbd611cdd9749c876bb0193dc2911dfd81",
3
+ "distance": 39,
4
+ "node": "g17e7c753489cb153f8066e2911d37fdd85dcdb2f",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-14"
@@ -111,3 +111,77 @@ def test_the_hook_is_installed_once_even_if_sourced_twice(pane_env: tuple[Path,
111
111
 
112
112
  (line,) = [ln for ln in result.stdout.splitlines() if ln.startswith("PC=")]
113
113
  assert line.count("__rosotacom_note_exit") == 1
114
+
115
+
116
+ # What the recorder cannot know, and why the session template has to care.
117
+ #
118
+ # The hook runs from PROMPT_COMMAND, so it sees every line a pane shell runs --
119
+ # including the lines that only assemble an argument list before the node
120
+ # starts. A line of the form `[[ cond ]] && args+=(...)` exits 1 whenever the
121
+ # condition is false, which is the normal case for every option a session leaves
122
+ # unset, so each one recorded itself as a dead pane. A peer then reported four
123
+ # exited panes at every start while nothing was wrong.
124
+
125
+ _UNSET_OPTION = '[[ "None" != "None" ]]'
126
+ _SET_OPTION = '[[ "22" != "None" ]]'
127
+
128
+
129
+ def _failure_log(catmux_dir: Path) -> Path:
130
+ return catmux_dir.parent / "pane_failures.log"
131
+
132
+
133
+ def test_the_short_circuit_form_records_a_false_failure(pane_env: tuple[Path, dict[str, str]]) -> None:
134
+ """The shape that was in the template: correct behaviour, status 1."""
135
+ catmux_dir, env = pane_env
136
+
137
+ _run(f'args=(); {_UNSET_OPTION} && args+=(-p "bit_rate:=None")', env)
138
+
139
+ assert _failure_log(catmux_dir).is_file()
140
+ assert "exit=1" in _failure_log(catmux_dir).read_text(encoding="utf-8")
141
+
142
+
143
+ def test_the_if_form_leaves_an_unset_option_silent(pane_env: tuple[Path, dict[str, str]]) -> None:
144
+ """The shape it was replaced with: same behaviour, status 0."""
145
+ catmux_dir, env = pane_env
146
+
147
+ _run(f'args=(); if {_UNSET_OPTION}; then args+=(-p "bit_rate:=None"); fi', env)
148
+
149
+ assert not _failure_log(catmux_dir).exists()
150
+
151
+
152
+ def test_the_if_form_still_appends_a_set_option(pane_env: tuple[Path, dict[str, str]]) -> None:
153
+ """Silencing the status must not silence the argument."""
154
+ catmux_dir, env = pane_env
155
+
156
+ result = _run(
157
+ f'args=(); if {_SET_OPTION}; then args+=(-p "bit_rate:=22"); fi\necho "ARGS=${{args[*]}}"',
158
+ env,
159
+ )
160
+
161
+ assert "ARGS=-p bit_rate:=22" in result.stdout
162
+ assert not _failure_log(catmux_dir).exists()
163
+
164
+
165
+ def test_no_session_template_line_uses_the_short_circuit_form() -> None:
166
+ """The template is where this reaches a running session, so it is checked there."""
167
+ template = (
168
+ REPO_ROOT
169
+ / "src"
170
+ / "rosotacom"
171
+ / "resources"
172
+ / "ws"
173
+ / "session"
174
+ / "content"
175
+ / "base"
176
+ / "session_plugin_base.yaml"
177
+ )
178
+ offenders = [
179
+ line.strip()
180
+ for line in template.read_text(encoding="utf-8").splitlines()
181
+ if line.lstrip().startswith("- '[[") and "&&" in line
182
+ ]
183
+
184
+ assert offenders == [], (
185
+ "these lines exit non-zero when their option is unset and land in "
186
+ f"pane_failures.log; use `if ...; then ...; fi`: {offenders}"
187
+ )
@@ -0,0 +1,139 @@
1
+ """At which level `get_topic_qos` reports a base-topic fallback.
2
+
3
+ A session declares QoS on the base topic (`/tf_static`), and every processing
4
+ stage appends a suffix to the name that is actually published
5
+ (`/tf_static/globalframe`). Resolving the suffixed name through its base is
6
+ therefore the designed path, not a degradation — and it was logged as a warning,
7
+ twice per topic per peer, at every bring-up. That is the noise a real message has
8
+ to compete with while a peer is starting, which is exactly when somebody is
9
+ reading the pane.
10
+
11
+ The module imports rclpy, which the host suite does not have, so its ROS surface
12
+ is stubbed before the module is loaded by file path (same approach as
13
+ `tests/unit/test_ota_unwrapper.py`).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import importlib.util
19
+ import sys
20
+ from collections.abc import Iterator
21
+ from pathlib import Path
22
+ from types import ModuleType
23
+
24
+ import pytest
25
+
26
+ REPO_ROOT = Path(__file__).resolve().parents[2]
27
+ QOS_PY = REPO_ROOT / "src" / "rosotacom" / "resources" / "ws" / "ros2src" / "com_py" / "com_py" / "qos.py"
28
+
29
+ _STUBBED = ("rclpy", "rclpy.node", "rclpy.qos", "rclpy.duration", "rosidl_runtime_py", "rosidl_runtime_py.utilities")
30
+
31
+
32
+ class _QoSProfile:
33
+ def __init__(self, depth: int = 10) -> None:
34
+ self.depth = depth
35
+
36
+
37
+ class _Policy:
38
+ BEST_EFFORT = "best_effort"
39
+ RELIABLE = "reliable"
40
+ KEEP_ALL = "keep_all"
41
+ KEEP_LAST = "keep_last"
42
+ TRANSIENT_LOCAL = "transient_local"
43
+ VOLATILE = "volatile"
44
+ AUTOMATIC = "automatic"
45
+ MANUAL_BY_TOPIC = "manual_by_topic"
46
+
47
+
48
+ def _stub_module(name: str, **attrs: object) -> ModuleType:
49
+ module = ModuleType(name)
50
+ for key, value in attrs.items():
51
+ setattr(module, key, value)
52
+ sys.modules[name] = module
53
+ return module
54
+
55
+
56
+ class _RecordingLogger:
57
+ """Counts what was said and at which level."""
58
+
59
+ def __init__(self) -> None:
60
+ self.warnings: list[str] = []
61
+ self.debugs: list[str] = []
62
+
63
+ def warning(self, message: str) -> None:
64
+ self.warnings.append(message)
65
+
66
+ def debug(self, message: str) -> None:
67
+ self.debugs.append(message)
68
+
69
+ def info(self, message: str) -> None:
70
+ pass
71
+
72
+ def error(self, message: str) -> None:
73
+ pass
74
+
75
+
76
+ @pytest.fixture(scope="module")
77
+ def qos_module() -> Iterator[ModuleType]:
78
+ saved = {name: sys.modules.get(name) for name in _STUBBED}
79
+ _stub_module("rclpy")
80
+ _stub_module("rclpy.node", Node=object)
81
+ _stub_module(
82
+ "rclpy.qos",
83
+ QoSProfile=_QoSProfile,
84
+ ReliabilityPolicy=_Policy,
85
+ HistoryPolicy=_Policy,
86
+ DurabilityPolicy=_Policy,
87
+ LivelinessPolicy=_Policy,
88
+ )
89
+ _stub_module("rclpy.duration", Duration=lambda **kwargs: kwargs)
90
+ _stub_module("rosidl_runtime_py", utilities=None)
91
+ _stub_module("rosidl_runtime_py.utilities", get_message=lambda type_str: None)
92
+
93
+ spec = importlib.util.spec_from_file_location("rosotacom_qos_fallback", QOS_PY)
94
+ assert spec and spec.loader
95
+ module = importlib.util.module_from_spec(spec)
96
+ sys.modules["rosotacom_qos_fallback"] = module
97
+ spec.loader.exec_module(module)
98
+ yield module
99
+
100
+ sys.modules.pop("rosotacom_qos_fallback", None)
101
+ for name, previous in saved.items():
102
+ if previous is None:
103
+ sys.modules.pop(name, None)
104
+ else:
105
+ sys.modules[name] = previous
106
+
107
+
108
+ CONFIG = {
109
+ "default": {"depth": 10},
110
+ "topics": {"/tf_static": {"reliability": "reliable", "durability": "transient_local"}},
111
+ }
112
+
113
+
114
+ def test_a_processed_name_resolves_through_its_base_without_warning(qos_module: ModuleType) -> None:
115
+ logger = _RecordingLogger()
116
+
117
+ qos_module.get_topic_qos(logger, CONFIG, "/tf_static/globalframe", "ota_pub")
118
+
119
+ assert logger.warnings == []
120
+ assert any("Falling back to base topic='/tf_static'" in line for line in logger.debugs)
121
+
122
+
123
+ def test_the_fallback_still_delivers_the_base_topic_settings(qos_module: ModuleType) -> None:
124
+ """Quiet must not mean ignored: the resolved profile is the base topic's."""
125
+ logger = _RecordingLogger()
126
+
127
+ profile = qos_module.get_topic_qos(logger, CONFIG, "/tf_static/globalframe", "ota_pub")
128
+
129
+ assert profile.reliability == _Policy.RELIABLE
130
+ assert profile.durability == _Policy.TRANSIENT_LOCAL
131
+
132
+
133
+ def test_an_exact_match_says_nothing_at_all(qos_module: ModuleType) -> None:
134
+ logger = _RecordingLogger()
135
+
136
+ qos_module.get_topic_qos(logger, CONFIG, "/tf_static", "ota_pub")
137
+
138
+ assert logger.warnings == []
139
+ assert not any("Falling back" in line for line in logger.debugs)
@@ -0,0 +1,176 @@
1
+ """Optional relay parameters must read as empty, not as unset.
2
+
3
+ `source_names` and `target_names` are optional: a peer that names no sources
4
+ simply has none. They were declared with a type and no value, which leaves them
5
+ PARAMETER_NOT_SET. The node itself never noticed, because it reads them once
6
+ through `.value or []`. Every *other* reader did: reading a not-set parameter
7
+ raises, and rclpy's parameter service logs one warning per read against the
8
+ node, so anything that enumerates parameters on a timer — a Foxglove bridge with
9
+ a parameter panel open is the usual one — filled the pane with
10
+
11
+ [WARN] [ota_relay_out]: Failed to get parameters: The parameter
12
+ 'source_names' is not initialized: source_names
13
+
14
+ An empty list cannot be a plain default: `declare_parameter(name, [])` infers
15
+ the type from the value and an empty sequence satisfies the BYTE_ARRAY test
16
+ first, so the parameter ends up typed BYTE_ARRAY and a real string array is
17
+ refused later. The fakes below reproduce that rclpy behaviour, which was
18
+ confirmed against a live rclpy node before this was written.
19
+
20
+ rclpy is not available to the host suite, so the ROS surface is stubbed and the
21
+ module is loaded by file path (same approach as `tests/unit/test_ota_unwrapper.py`).
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import importlib.util
27
+ import sys
28
+ from collections.abc import Iterator
29
+ from pathlib import Path
30
+ from types import ModuleType
31
+
32
+ import pytest
33
+
34
+ REPO_ROOT = Path(__file__).resolve().parents[2]
35
+ COM_PY = REPO_ROOT / "src" / "rosotacom" / "resources" / "ws" / "ros2src" / "com_py" / "com_py"
36
+ RELAY_PY = COM_PY / "base_bridge_relay.py"
37
+
38
+ _STUBBED = (
39
+ "rclpy",
40
+ "rclpy.node",
41
+ "com_py",
42
+ "com_py.topic_resolution",
43
+ "com_py.qos",
44
+ "com_py.pub_sub_pair",
45
+ "com_py.pair_management",
46
+ )
47
+
48
+
49
+ class _Type:
50
+ NOT_SET = "not_set"
51
+ STRING_ARRAY = "string_array"
52
+ STRING = "string"
53
+
54
+
55
+ class _Parameter:
56
+ Type = _Type
57
+
58
+ def __init__(self, name: str, type_: str = _Type.NOT_SET, value: object = None) -> None:
59
+ self.name = name
60
+ self.type_ = type_
61
+ self.value = value
62
+
63
+
64
+ class _FakeNode:
65
+ """rclpy's declare/get/set semantics, only as far as this method uses them."""
66
+
67
+ def __init__(self, overrides: dict[str, list[str]] | None = None) -> None:
68
+ self._overrides = overrides or {}
69
+ self._parameters: dict[str, _Parameter] = {}
70
+ self.set_calls: list[tuple[str, str, object]] = []
71
+
72
+ def declare_parameter(self, name: str, type_: str) -> _Parameter:
73
+ if name in self._overrides:
74
+ parameter = _Parameter(name, type_, self._overrides[name])
75
+ else:
76
+ parameter = _Parameter(name, _Type.NOT_SET, None)
77
+ self._parameters[name] = parameter
78
+ return parameter
79
+
80
+ def get_parameter_or(self, name: str) -> _Parameter:
81
+ return self._parameters.get(name, _Parameter(name))
82
+
83
+ def get_parameter(self, name: str) -> _Parameter:
84
+ parameter = self._parameters[name]
85
+ if parameter.type_ == _Type.NOT_SET:
86
+ raise RuntimeError(f"The parameter '{name}' is not initialized: {name}")
87
+ return parameter
88
+
89
+ def set_parameters(self, parameters: list[_Parameter]) -> None:
90
+ for parameter in parameters:
91
+ self.set_calls.append((parameter.name, parameter.type_, parameter.value))
92
+ self._parameters[parameter.name] = parameter
93
+
94
+
95
+ def _stub_module(name: str, **attrs: object) -> ModuleType:
96
+ module = ModuleType(name)
97
+ for key, value in attrs.items():
98
+ setattr(module, key, value)
99
+ sys.modules[name] = module
100
+ return module
101
+
102
+
103
+ @pytest.fixture(scope="module")
104
+ def relay_module() -> Iterator[ModuleType]:
105
+ saved = {name: sys.modules.get(name) for name in _STUBBED}
106
+ _stub_module("rclpy", Parameter=_Parameter)
107
+ # Distinct classes: `BaseBridgeRelay(Node, PairRefreshMixin)` cannot be built
108
+ # from one base twice.
109
+ _stub_module("rclpy.node", Node=type("Node", (), {}))
110
+ _stub_module("com_py")
111
+ _stub_module("com_py.topic_resolution", resolve_topics=lambda *a, **k: [])
112
+ _stub_module("com_py.qos", load_qos_config=lambda *a, **k: {})
113
+ _stub_module("com_py.pub_sub_pair", PubSubPair=type("PubSubPair", (), {}))
114
+ _stub_module("com_py.pair_management", PairRefreshMixin=type("PairRefreshMixin", (), {}))
115
+
116
+ spec = importlib.util.spec_from_file_location("rosotacom_base_bridge_relay", RELAY_PY)
117
+ assert spec and spec.loader
118
+ module = importlib.util.module_from_spec(spec)
119
+ sys.modules["rosotacom_base_bridge_relay"] = module
120
+ spec.loader.exec_module(module)
121
+ yield module
122
+
123
+ sys.modules.pop("rosotacom_base_bridge_relay", None)
124
+ for name, previous in saved.items():
125
+ if previous is None:
126
+ sys.modules.pop(name, None)
127
+ else:
128
+ sys.modules[name] = previous
129
+
130
+
131
+ def _declare(module: ModuleType, node: _FakeNode, name: str = "source_names") -> object:
132
+ """The method under test, unbound — the surrounding __init__ needs a live ROS graph."""
133
+ return module.BaseBridgeRelay.declare_optional_string_array(node, name)
134
+
135
+
136
+ def test_an_unsupplied_parameter_becomes_an_empty_string_array(relay_module: ModuleType) -> None:
137
+ node = _FakeNode()
138
+
139
+ value = _declare(relay_module, node)
140
+
141
+ assert value == []
142
+ assert node.set_calls == [("source_names", _Type.STRING_ARRAY, [])]
143
+
144
+
145
+ def test_the_parameter_is_readable_afterwards(relay_module: ModuleType) -> None:
146
+ """The whole point: a read no longer raises, so nothing logs a warning."""
147
+ node = _FakeNode()
148
+
149
+ _declare(relay_module, node)
150
+
151
+ assert node.get_parameter("source_names").value == []
152
+
153
+
154
+ def test_supplied_names_are_left_alone(relay_module: ModuleType) -> None:
155
+ node = _FakeNode(overrides={"source_names": ["center", "vehicle"]})
156
+
157
+ value = _declare(relay_module, node)
158
+
159
+ assert value == ["center", "vehicle"]
160
+ assert node.set_calls == [], "an override must not be overwritten by the default"
161
+
162
+
163
+ def test_it_stays_a_string_array(relay_module: ModuleType) -> None:
164
+ """Not BYTE_ARRAY, which is what a plain `[]` default would have inferred."""
165
+ node = _FakeNode()
166
+
167
+ _declare(relay_module, node)
168
+
169
+ assert node.get_parameter("source_names").type_ == _Type.STRING_ARRAY
170
+
171
+
172
+ def test_both_optional_parameters_use_it(relay_module: ModuleType) -> None:
173
+ source = RELAY_PY.read_text(encoding="utf-8")
174
+
175
+ for name in ("source_names", "target_names"):
176
+ assert f"self.declare_optional_string_array('{name}')" in source