rosotacom-dev 2.5.dev70__tar.gz → 2.5.dev72__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 (394) hide show
  1. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/cyclone-fragment-equal-to-max-drops-fragmented-samples.md +15 -0
  3. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/ota-pcap.md +10 -0
  4. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/cyclonedds_scoped.xml.template +3 -2
  5. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +9 -2
  6. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +81 -2
  7. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ota_pcap.py +42 -5
  8. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +6 -4
  9. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +25 -2
  10. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  11. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  12. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_generated_topics_have_a_producer.py +1 -1
  13. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_cli.py +82 -0
  14. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ota_link_configs.py +42 -0
  15. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ota_pcap.py +42 -0
  16. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ota_xml.py +55 -0
  17. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.devcontainer/devcontainer.json.template +0 -0
  18. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  19. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/CODEOWNERS +0 -0
  20. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  21. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  22. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  23. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  24. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  25. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  26. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  27. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  28. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  29. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/actions/setup-just/action.yml +0 -0
  30. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/dependabot.yml +0 -0
  31. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/benchmark-calibrate.yml +0 -0
  32. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/benchmark-gate.yml +0 -0
  33. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/dev-release.yml +0 -0
  34. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/image-scan.yml +0 -0
  35. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/nightly-e2e.yml +0 -0
  36. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/pr-lightweight.yml +0 -0
  37. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/pr-merge-gate.yml +0 -0
  38. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.github/workflows/release.yml +0 -0
  39. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/.pre-commit-config.yaml +0 -0
  40. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/AGENTS.md +0 -0
  41. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/CONCURRENCY.md +0 -0
  42. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/CONTRIBUTING.md +0 -0
  43. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/DEVELOPMENT_PRINCIPLES.md +0 -0
  44. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/LICENSE.txt +0 -0
  45. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/MANIFEST.in +0 -0
  46. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/README.md +0 -0
  47. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/budgets.jsonl +0 -0
  48. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/README.md +0 -0
  49. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/capacity-size-tight-cyclone.json +0 -0
  50. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  51. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/probe-loss-nominal-cyclone.json +0 -0
  52. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/probe-loss-tight-cyclone.json +0 -0
  53. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/probe-loss-tight-fastdds.json +0 -0
  54. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/calibration/probe-loss-tight-zenoh.json +0 -0
  55. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/codecov.yml +0 -0
  56. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/deployment-configuration.md +0 -0
  57. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/benchmark-ab.md +0 -0
  58. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/benchmark-bag-as-load.md +0 -0
  59. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/ci.md +0 -0
  60. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/ffmpeg-keyframes.md +0 -0
  61. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/README.md +0 -0
  62. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/a-transport-element-doubles-every-fastdds-sample.md +0 -0
  63. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  64. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/delay-alone-no-loss.md +0 -0
  65. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
  66. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
  67. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/head-of-line-irregularity.md +0 -0
  68. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  69. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
  70. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/lifespan-is-enforced-by-one-reader-and-not-the-other.md +0 -0
  71. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/lighter-message-loss.md +0 -0
  72. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/one-domain-two-cyclone-configs-stop-interoperating.md +0 -0
  73. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/oversubscription-queues-not-losses.md +0 -0
  74. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/reorder-becomes-reader-loss.md +0 -0
  75. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/repro/decode_chain_repro.py +0 -0
  76. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/repro/reorder_drop_repro.py +0 -0
  77. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
  78. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  79. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/forensics-report.md +0 -0
  80. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/geo-link-map.md +0 -0
  81. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/github-repository-settings.md +0 -0
  82. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/link-bandwidth.md +0 -0
  83. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/link-trace.md +0 -0
  84. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/owner-runbook.md +0 -0
  85. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/performance-bands.md +0 -0
  86. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/playbooks/README.md +0 -0
  87. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/quality-model.md +0 -0
  88. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/release-notes/TEMPLATE.md +0 -0
  89. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/release-notes/v2.1.md +0 -0
  90. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/release-notes/v2.2.md +0 -0
  91. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/release-notes/v2.3.md +0 -0
  92. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/release-notes/v2.4.md +0 -0
  93. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/release.md +0 -0
  94. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  95. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0002-expectation-concepts.md +0 -0
  96. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0003-metric-backbone.md +0 -0
  97. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0004-network-profiles.md +0 -0
  98. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  99. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0006-dynamic-qos.md +0 -0
  100. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/0007-regression-gate.md +0 -0
  101. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/rfcs/README.md +0 -0
  102. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/testing.md +0 -0
  103. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/docs/work-items.md +0 -0
  104. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/install.sh +0 -0
  105. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/justfile +0 -0
  106. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/media/control_center.png +0 -0
  107. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/media/echo_test.png +0 -0
  108. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/media/forensics-report-example.png +0 -0
  109. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/media/host.png +0 -0
  110. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/media/vehicle.png +0 -0
  111. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/pyproject.toml +0 -0
  112. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/requirements.txt +0 -0
  113. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/rosotacom.yaml +0 -0
  114. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/session-configuration.md +0 -0
  115. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/setup.cfg +0 -0
  116. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/__init__.py +0 -0
  117. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/__main__.py +0 -0
  118. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/anonymize.py +0 -0
  119. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/bag_ground_truth.py +0 -0
  120. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/benched_set.py +0 -0
  121. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/benchmark.py +0 -0
  122. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/benchmark_delta.py +0 -0
  123. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/bundle_check.py +0 -0
  124. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/cli.py +0 -0
  125. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/cli_benchmark.py +0 -0
  126. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/deployment.py +0 -0
  127. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/ffmpeg_packet.py +0 -0
  128. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/forensics.py +0 -0
  129. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/geomap.py +0 -0
  130. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/image_cache.py +0 -0
  131. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/netem_dist.py +0 -0
  132. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/network_preflight.py +0 -0
  133. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/network_profiles.py +0 -0
  134. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/network_shaper.py +0 -0
  135. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/plots.py +0 -0
  136. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/py.typed +0 -0
  137. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/benched-set.yaml +0 -0
  138. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/.gitignore +0 -0
  139. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/README.md +0 -0
  140. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/__init__.py +0 -0
  141. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  142. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  143. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
  144. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  145. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  146. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/ros2docker.lyrical.json +0 -0
  147. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  148. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  149. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  150. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  151. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.lyrical.ros2docker.json +0 -0
  152. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  153. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  154. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  155. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  156. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.lyrical.ros2docker.json +0 -0
  157. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  158. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  159. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  160. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  161. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  162. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  163. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  164. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  165. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  166. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  167. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  168. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  169. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  170. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  171. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  172. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  173. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  174. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  175. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  176. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  177. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  178. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  179. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  180. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  181. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  182. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  183. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  184. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  185. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  186. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  187. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  188. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  189. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  190. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  191. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  192. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  193. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  194. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  195. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  196. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  204. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  205. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  206. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  207. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  208. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  209. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  210. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  211. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  212. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  213. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  214. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  215. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  216. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  217. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  218. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  219. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ros2docker.json.example +0 -0
  220. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ros2docker.lyrical.json.example +0 -0
  221. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  222. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  223. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  224. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  225. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  226. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  227. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  228. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  229. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  230. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  231. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  232. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
  233. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template +0 -0
  234. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
  235. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  236. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  237. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  238. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  239. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  240. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  241. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  242. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  243. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  244. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  245. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  246. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  247. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  248. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  249. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  250. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  251. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  252. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  253. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  254. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  255. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  256. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  257. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  258. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
  259. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  260. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  261. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  262. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  263. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  264. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  265. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  266. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  267. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  268. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  269. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  270. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  271. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  272. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  273. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  274. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  275. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  276. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  277. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
  278. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
  279. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  280. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  281. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  282. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  283. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  284. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  285. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  286. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  287. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  288. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  289. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  290. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  291. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  292. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  293. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  294. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  295. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  296. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  297. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  298. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  299. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  300. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  301. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  302. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  303. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  304. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  305. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  306. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  307. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  308. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  309. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  310. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  311. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  312. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/status_eval.py +0 -0
  313. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/stream_stats.py +0 -0
  314. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/trace_profiles.py +0 -0
  315. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/transit.py +0 -0
  316. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom/video_quality.py +0 -0
  317. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/SOURCES.txt +0 -0
  318. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  319. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  320. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  321. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/scm_file_list.json +0 -0
  322. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  323. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/terminology.md +0 -0
  324. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_benched_set_registry.py +0 -0
  325. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_findings.py +0 -0
  326. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_lyrical_runtime_compat.py +0 -0
  327. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_markdown_links.py +0 -0
  328. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_ota_source_bundle.py +0 -0
  329. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_package_metadata.py +0 -0
  330. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_packaged_resources.py +0 -0
  331. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  332. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_pytest_policy.py +0 -0
  333. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_readme_examples.py +0 -0
  334. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_security_maintenance_config.py +0 -0
  335. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_session_configuration_doc.py +0 -0
  336. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_session_template_substitution.py +0 -0
  337. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/contract/test_workflow_contracts.py +0 -0
  338. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/conftest.py +0 -0
  339. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_anonymize_e2e.py +0 -0
  340. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_benchmark_ab.py +0 -0
  341. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_benchmark_capacity.py +0 -0
  342. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_benchmark_replay.py +0 -0
  343. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_parallel_smoke.py +0 -0
  344. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_smoke.py +0 -0
  345. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_timeline_stepping.py +0 -0
  346. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/e2e/test_video_quality_e2e.py +0 -0
  347. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/generate_rmw_matrix.py +0 -0
  348. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  349. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  350. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  351. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  352. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
  353. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  354. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_anonymize.py +0 -0
  355. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_bag_ground_truth.py +0 -0
  356. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_benched_set.py +0 -0
  357. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_benchmark.py +0 -0
  358. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_benchmark_delta.py +0 -0
  359. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_bundle_check.py +0 -0
  360. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_catmux_pane_logging.py +0 -0
  361. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_cli_benchmark.py +0 -0
  362. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_concurrency.py +0 -0
  363. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_container_shaping.py +0 -0
  364. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_deployment.py +0 -0
  365. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ffmpeg_flags.py +0 -0
  366. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ffmpeg_packet.py +0 -0
  367. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_field_profiles.py +0 -0
  368. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_forensics.py +0 -0
  369. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_geomap.py +0 -0
  370. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_image_cache.py +0 -0
  371. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_link_bytes.py +0 -0
  372. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_link_trace.py +0 -0
  373. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_netem_dist.py +0 -0
  374. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_network_preflight.py +0 -0
  375. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_network_profiles.py +0 -0
  376. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_network_shaper.py +0 -0
  377. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ota_unwrapper.py +0 -0
  378. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ota_wrapper.py +0 -0
  379. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_playout_pacer.py +0 -0
  380. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_plots.py +0 -0
  381. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_pub_sub_pair_types.py +0 -0
  382. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_qos_fallback_logging.py +0 -0
  383. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_relay_optional_parameters.py +0 -0
  384. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_ros_node_entity_ownership.py +0 -0
  385. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_session_pipeline.py +0 -0
  386. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_stage_latency.py +0 -0
  387. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_status_eval.py +0 -0
  388. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_status_overview.py +0 -0
  389. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_status_overview_observer.py +0 -0
  390. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_stream_stats.py +0 -0
  391. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_terminology_stage_ownership.py +0 -0
  392. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_trace_profiles.py +0 -0
  393. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/tests/unit/test_transit.py +0 -0
  394. {rosotacom_dev-2.5.dev70 → rosotacom_dev-2.5.dev72}/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.dev70
3
+ Version: 2.5.dev72
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -73,6 +73,21 @@ the same MTU budget the 1200 B was chosen for — only the header room is added
73
73
  and the value works on both versions, so it is one number rather than a branch
74
74
  on the distribution.
75
75
 
76
+ The 1200 B profile stays reachable, deliberately. It is what every 0.10.5 run
77
+ before 2026-08-20 carried, so a comparison against one of those runs has to be
78
+ able to reproduce it rather than approximate it:
79
+
80
+ ```yaml
81
+ shared:
82
+ rmw:
83
+ ota:
84
+ cyclone: {config: cyclonedds_tuned.xml, fragment_size: 1200B}
85
+ ```
86
+
87
+ `get_ota_xml.py` warns on stderr whenever the rendered fragment reaches the
88
+ datagram cap, naming 11.0.1 — a profile worth choosing on purpose is not one
89
+ worth choosing by accident.
90
+
76
91
  ## Publication notes
77
92
 
78
93
  A distribution bump silently changed what a profile *means*, and the profile
@@ -87,6 +87,16 @@ reached the packet.
87
87
  }
88
88
  ```
89
89
 
90
+ `stopped_because` reads `running` until something stops the capture, and that
91
+ is deliberate: **neither file depends on a clean shutdown.** A session usually
92
+ ends by its container being stopped, which does not reach this process as a
93
+ signal it can act on — the first bench run ended exactly that way and left a
94
+ pcap truncated at a buffer boundary and no sidecar at all. The pcap is flushed
95
+ once a second and the sidecar is written at the start and rewritten on the same
96
+ interval, so a killed capture still leaves a readable file and a description of
97
+ it. Verified by `SIGKILL`ing a running capture: `capinfos` reads the pcap and
98
+ the sidecar reports the packets up to the last checkpoint.
99
+
90
100
  **`kernel_drops` is a gap in the file, not on the link.** A capture that fell
91
101
  behind looks exactly like radio loss to anyone reading the pcap months later,
92
102
  so subtract it before reading any loss out of the capture. Zero is the normal
@@ -47,8 +47,9 @@ template, not a replacement, and a session that does not name it is unchanged.
47
47
  </Interfaces>
48
48
  <MulticastRecvNetworkInterfaceAddresses>none</MulticastRecvNetworkInterfaceAddresses>
49
49
  <!-- Strictly below MaxMessageSize; see cyclonedds_tuned.xml for what
50
- making them equal cost. -->
51
- <FragmentSize>1024B</FragmentSize>
50
+ making them equal cost, and for why the number is settable
51
+ (`shared.rmw.<side>.cyclone.fragment_size`) rather than literal. -->
52
+ <FragmentSize>#fragment_size</FragmentSize>
52
53
  <MaxMessageSize>1200B</MaxMessageSize>
53
54
  <MaxRexmitMessageSize>1200B</MaxRexmitMessageSize>
54
55
  </General>
@@ -16,8 +16,15 @@
16
16
  sample: 1200/1200 delivers nothing on 11.0.1 and everything on
17
17
  0.10.5; 1024/1200 delivers on both. The datagram stays inside the
18
18
  same MTU budget the 1200B was chosen for; only the header room is
19
- added. -->
20
- <FragmentSize>1024B</FragmentSize>
19
+ added.
20
+
21
+ The default is that 1024B. It is a placeholder rather than a literal
22
+ because both values are correct on 0.10.5, so which one a run used is
23
+ a property of the session and not of the installed package version:
24
+ `shared.rmw.<side>.cyclone.fragment_size`. Asking for one that fills
25
+ the datagram is allowed — it is what the Kilted-era link ran — and
26
+ get_ota_xml.py says so on stderr rather than refusing. -->
27
+ <FragmentSize>#fragment_size</FragmentSize>
21
28
  <MaxMessageSize>1200B</MaxMessageSize>
22
29
  <MaxRexmitMessageSize>1200B</MaxRexmitMessageSize>
23
30
  </General>
@@ -2,8 +2,9 @@
2
2
  """Unified OTA DDS XML generator.
3
3
 
4
4
  Loads an XML template from /ws/ota_configs/<config>(.template), detects which
5
- placeholders it uses (#host_ip, #peer, #easy_mode_ip, #spdp_interval),
6
- substitutes already resolved addresses, and prints the resulting XML to stdout.
5
+ placeholders it uses (#host_ip, #peer, #easy_mode_ip, #spdp_interval,
6
+ #fragment_size), substitutes already resolved addresses, and prints the
7
+ resulting XML to stdout.
7
8
 
8
9
  Replaces the old per-template scripts (get_fastdds_xml.py,
9
10
  get_fastdds_easy_mode_xml.py, get_cyclonedds_xml.py).
@@ -28,9 +29,22 @@ KNOWN_PLACEHOLDERS = {
28
29
  #: profile and half the local one.
29
30
  "#local_domain",
30
31
  "#ota_domain",
32
+ #: How large an RTPS fragment this link sends. A template resolves it so
33
+ #: that the number is a property of the session rather than of the
34
+ #: installed package: 1024B and 1200B are both correct on CycloneDDS
35
+ #: 0.10.5, only the smaller one is correct on 11.0.1, and a run comparing
36
+ #: the two ROS distributions has to be able to say which it used.
37
+ "#fragment_size",
31
38
  }
32
39
  PLACEHOLDER_PATTERN = re.compile(r"#[A-Za-z_][A-Za-z0-9_]*")
33
40
  DEFAULT_SPDP_INTERVAL = "30s"
41
+ #: Strictly below the templates' MaxMessageSize; see cyclonedds_tuned.xml for
42
+ #: what making the two equal cost under ROS 2 Lyrical.
43
+ DEFAULT_FRAGMENT_SIZE = "1024B"
44
+
45
+ #: CycloneDDS size suffixes, and what each one is worth in bytes.
46
+ SIZE_UNITS = {"B": 1, "KB": 1000, "KIB": 1024, "MB": 1000_000, "MIB": 1024 * 1024}
47
+ SIZE_PATTERN = re.compile(r"(\d+)\s*(B|kB|KB|KiB|MB|MiB)?\Z")
34
48
 
35
49
 
36
50
  def _template_path(name: str) -> str:
@@ -86,6 +100,60 @@ def _resolved_spdp_interval(value: str) -> str:
86
100
  return interval
87
101
 
88
102
 
103
+ def _parse_size(value: str) -> int:
104
+ """A CycloneDDS size, in bytes, or None when it is not one this script wrote.
105
+
106
+ Used both to validate what a session asked for and to read the template's
107
+ own MaxMessageSize back out of the rendered XML, so the warning below can
108
+ compare the two.
109
+ """
110
+ match = SIZE_PATTERN.fullmatch(value.strip())
111
+ if not match:
112
+ return None
113
+ return int(match.group(1)) * SIZE_UNITS[(match.group(2) or "B").upper()]
114
+
115
+
116
+ def _resolved_fragment_size(value: str) -> str:
117
+ size = value.strip()
118
+ if not size:
119
+ raise ValueError("Fragment size must be a non-empty size.")
120
+ parsed = _parse_size(size)
121
+ if parsed is None:
122
+ raise ValueError("Fragment size must be a byte size, e.g. '1024B', '1200B' or '4KiB'.")
123
+ if parsed <= 0:
124
+ raise ValueError("Fragment size must be > 0.")
125
+ return size
126
+
127
+
128
+ def _warn_if_fragment_fills_the_datagram(content: str, path: str) -> None:
129
+ """Say so when the rendered profile is the one Lyrical cannot carry.
130
+
131
+ An RTPS message carries headers as well as its fragment, so a fragment of
132
+ exactly MaxMessageSize cannot be sent at all. CycloneDDS 0.10.5 coped with
133
+ that; 11.0.1 drops every fragmented sample and drops it silently, with the
134
+ endpoints matched and small samples still arriving. That combination is a
135
+ legitimate thing to ask for -- it is what the Kilted-era link ran, and
136
+ reproducing it is the reason the fragment size is settable -- so this warns
137
+ rather than refuses. Refusing would make the older profile unreachable; not
138
+ saying anything would let it be chosen by accident.
139
+ """
140
+ fragment = re.search(r"<FragmentSize>([^<]+)</FragmentSize>", content)
141
+ maximum = re.search(r"<MaxMessageSize>([^<]+)</MaxMessageSize>", content)
142
+ if not fragment or not maximum:
143
+ return
144
+ fragment_bytes = _parse_size(fragment.group(1))
145
+ maximum_bytes = _parse_size(maximum.group(1))
146
+ if fragment_bytes is None or maximum_bytes is None or fragment_bytes < maximum_bytes:
147
+ return
148
+ print(
149
+ f"WARNING - {os.path.basename(path)} renders FragmentSize {fragment.group(1)} against "
150
+ f"MaxMessageSize {maximum.group(1)}. A fragment leaves no room for the RTPS header, so "
151
+ "on CycloneDDS 11.0.1 (ROS 2 Lyrical) no fragmented sample will arrive, silently. "
152
+ "Correct on 0.10.5 (Kilted), which is what this profile reproduces.",
153
+ file=sys.stderr,
154
+ )
155
+
156
+
89
157
  def _resolve_peer_ips(value: str) -> list:
90
158
  seen = set()
91
159
  unique = []
@@ -162,6 +230,7 @@ def main(
162
230
  peer: str = None,
163
231
  easy_mode_ip: str = None,
164
232
  spdp_interval: str = DEFAULT_SPDP_INTERVAL,
233
+ fragment_size: str = DEFAULT_FRAGMENT_SIZE,
165
234
  local_domain: str = None,
166
235
  ota_domain: str = None,
167
236
  ) -> str:
@@ -195,6 +264,9 @@ def main(
195
264
  if "#spdp_interval" in found:
196
265
  content = content.replace("#spdp_interval", _resolved_spdp_interval(spdp_interval))
197
266
 
267
+ if "#fragment_size" in found:
268
+ content = content.replace("#fragment_size", _resolved_fragment_size(fragment_size))
269
+
198
270
  for placeholder, value, option in (
199
271
  ("#local_domain", local_domain, "--local-domain"),
200
272
  ("#ota_domain", ota_domain, "--ota-domain"),
@@ -211,6 +283,7 @@ def main(
211
283
  raise RuntimeError(f"Template '{os.path.basename(path)}' uses #peer but --peer was not provided.")
212
284
  content = _expand_peer_block(content, _resolve_peer_ips(peer))
213
285
 
286
+ _warn_if_fragment_fills_the_datagram(content, path)
214
287
  return content
215
288
 
216
289
 
@@ -268,6 +341,12 @@ if __name__ == "__main__":
268
341
  default=DEFAULT_SPDP_INTERVAL,
269
342
  help=f"CycloneDDS SPDP interval duration (#spdp_interval, default: {DEFAULT_SPDP_INTERVAL}).",
270
343
  )
344
+ parser.add_argument(
345
+ "--fragment-size",
346
+ dest="fragment_size",
347
+ default=DEFAULT_FRAGMENT_SIZE,
348
+ help=f"CycloneDDS RTPS fragment size (#fragment_size, default: {DEFAULT_FRAGMENT_SIZE}).",
349
+ )
271
350
  args = parser.parse_args()
272
351
  output = args.output
273
352
  rendered = main(**{k: v for k, v in vars(args).items() if v is not None and k != "output"})
@@ -79,6 +79,15 @@ A capture that quietly drops packets is worse than none: the analysis would
79
79
  read the gap as radio loss. So `PACKET_STATISTICS` is read from the kernel and
80
80
  the sidecar `.json` reports `kernel_drops` next to `packets`. A non-zero drop
81
81
  count means this process fell behind, not that the link did.
82
+
83
+ Neither file depends on a clean shutdown, and that is deliberate. A session
84
+ usually ends by the container being stopped, which does not reach this process
85
+ as a signal it can act on — the first bench run ended exactly that way and left
86
+ a pcap truncated at a buffer boundary and no sidecar at all. So the pcap is
87
+ flushed on an interval and the sidecar is written at the *start* and rewritten
88
+ on the same interval, with `stopped_because: "running"` until something stops
89
+ it. A killed capture therefore still leaves a readable pcap and a sidecar that
90
+ says what it was and how much it had seen, rather than an unexplained file.
82
91
  """
83
92
 
84
93
  from __future__ import annotations
@@ -135,6 +144,12 @@ DEFAULT_MAX_MB = 2000
135
144
  #: is where `kernel_drops` comes from on a burst.
136
145
  RCVBUF_BYTES = 4 << 20
137
146
 
147
+ #: How often the pcap is flushed and the sidecar rewritten. A session that ends
148
+ #: by having its container stopped never reaches this process, so what is on
149
+ #: disk at any moment is what the run will be read from. One second costs a
150
+ #: ~300-byte write and bounds the loss to the packets of that second.
151
+ CHECKPOINT_S = 1.0
152
+
138
153
 
139
154
  def log(message: str) -> None:
140
155
  print(f"[ota_pcap] {message}", flush=True)
@@ -326,7 +341,7 @@ class Capture:
326
341
  self.kernel_seen = 0
327
342
  self.first_ts: float | None = None
328
343
  self.last_ts: float | None = None
329
- self.stopped_because = "signal"
344
+ self.stopped_because = "running"
330
345
 
331
346
  def sidecar(self) -> dict:
332
347
  """What the file is, and what it is missing. Written next to the pcap."""
@@ -348,6 +363,14 @@ class Capture:
348
363
  "stopped_because": self.stopped_because,
349
364
  }
350
365
 
366
+ def write_sidecar(self) -> None:
367
+ """The sidecar as of now. Cheap enough to call on every checkpoint."""
368
+ try:
369
+ self.out.with_suffix(".json").write_text(
370
+ json.dumps(self.sidecar(), indent=2) + "\n", encoding="utf-8")
371
+ except OSError as error:
372
+ log(f"WARN could not write the sidecar: {error}")
373
+
351
374
  def run(self, stop: list) -> None:
352
375
  sock = open_socket(self.iface)
353
376
  # Root was needed for that one call and for nothing after it.
@@ -360,8 +383,22 @@ class Capture:
360
383
  with open(self.out, "wb", buffering=1 << 20) as handle:
361
384
  handle.write(pcap_header(self.snaplen))
362
385
  self.bytes_written = 24
386
+ handle.flush()
387
+ # Written before a single packet arrives, so a capture that is
388
+ # killed with its container still explains itself.
389
+ self.write_sidecar()
390
+ next_checkpoint = time.monotonic() + CHECKPOINT_S
363
391
  sock.settimeout(0.5)
364
392
  while not stop:
393
+ # `tick`, not `now`: the timestamp fallback below rebinds `now`
394
+ # to wall-clock time, and comparing that against a monotonic
395
+ # deadline would checkpoint on every single packet.
396
+ tick = time.monotonic()
397
+ if tick >= next_checkpoint:
398
+ next_checkpoint = tick + CHECKPOINT_S
399
+ self._collect(sock)
400
+ handle.flush()
401
+ self.write_sidecar()
365
402
  try:
366
403
  packet, ancdata, _flags, address = sock.recvmsg(65535, ancillary)
367
404
  except socket.timeout:
@@ -395,8 +432,9 @@ class Capture:
395
432
  f"after {self.packets} packets — the recording is untouched")
396
433
  self.stopped_because = "size ceiling"
397
434
  break
398
- if self.packets % 20000 == 0:
399
- self._collect(sock)
435
+ handle.flush()
436
+ if self.stopped_because == "running":
437
+ self.stopped_because = "signal"
400
438
  self._collect(sock)
401
439
  sock.close()
402
440
 
@@ -445,9 +483,8 @@ def main(argv: list[str] | None = None) -> int:
445
483
  log(f"FAIL {error}")
446
484
  return 3
447
485
 
486
+ capture.write_sidecar()
448
487
  sidecar = capture.sidecar()
449
- args.out.with_suffix(".json").write_text(
450
- json.dumps(sidecar, indent=2) + "\n", encoding="utf-8")
451
488
  log(f"{sidecar['packets']} packets, {sidecar['bytes'] / 1e6:.1f} MB, "
452
489
  f"{sidecar['kernel_drops']} dropped by the kernel")
453
490
  if sidecar["kernel_drops"]:
@@ -7,6 +7,7 @@ parameters:
7
7
  local_config_file: ${peer_dir}/local_dds.xml # destination path for the resolved local DDS XML
8
8
  local_easy_mode_ip: # resolved address for #easy_mode_ip (only honored when the template uses it)
9
9
  local_spdp_interval: # CycloneDDS SPDP interval override, e.g. 150s. Empty => template default.
10
+ local_fragment_size: # CycloneDDS RTPS fragment size, e.g. 1200B. Empty => template default.
10
11
  use_domain_bridge: false
11
12
  local_domain_id: # pinned per-peer ROS_DOMAIN_ID (from peer_settings.<peer>.domain_id)
12
13
  ota_domain_id:
@@ -39,6 +40,7 @@ parameters:
39
40
  ota_config_file: ${peer_dir}/ota_dds.xml # destination path for the resolved DDS XML
40
41
  ota_easy_mode_ip: # resolved address for #easy_mode_ip (only honored when the template uses it)
41
42
  ota_spdp_interval: # CycloneDDS SPDP interval override, e.g. 150s. Empty => template default.
43
+ ota_fragment_size: # CycloneDDS RTPS fragment size, e.g. 1200B. Empty => template default.
42
44
  # Z2D (zenoh_ros2dds) parameters — used only when use_zenoh_ros2dds=true
43
45
  zen_mode: # e.g. router, peer or client
44
46
  zen_endpoint_role: # e.g. listen or connect
@@ -299,7 +301,7 @@ common:
299
301
  before_commands:
300
302
  - if [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ]; then export ROS_DOMAIN_ID='${local_domain_id}'; fi
301
303
  - if [ '${rmw_local}' != "None" ]; then case ${rmw_local} in cyclone) export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp;; fastdds) export RMW_IMPLEMENTATION=rmw_fastrtps_cpp; export FASTDDS_BUILTIN_TRANSPORTS=${FASTDDS_BUILTIN_TRANSPORTS:-UDPv4};; zenoh) export RMW_IMPLEMENTATION=rmw_zenoh_cpp;; *) export RMW_IMPLEMENTATION='${rmw_local}';; esac; fi
302
- - if [ '${local_config_template}' != "None" ] && [ -n '${local_config_template}' ]; then local_args=(--config "${local_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && local_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && local_args+=(--ota-domain "${ota_domain_id}"); [ '${local_easy_mode_ip}' != "None" ] && [ -n '${local_easy_mode_ip}' ] && local_args+=(--easy-mode-ip "${local_easy_mode_ip}"); [ '${local_spdp_interval}' != "None" ] && [ -n '${local_spdp_interval}' ] && local_args+=(--spdp-interval "${local_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${local_args[@]}" --output "${local_config_file}" || { echo "FATAL - could not render the local DDS profile ${local_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_local} in cyclone) export CYCLONEDDS_URI="file://${local_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${local_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
304
+ - if [ '${local_config_template}' != "None" ] && [ -n '${local_config_template}' ]; then local_args=(--config "${local_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && local_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && local_args+=(--ota-domain "${ota_domain_id}"); [ '${local_easy_mode_ip}' != "None" ] && [ -n '${local_easy_mode_ip}' ] && local_args+=(--easy-mode-ip "${local_easy_mode_ip}"); [ '${local_spdp_interval}' != "None" ] && [ -n '${local_spdp_interval}' ] && local_args+=(--spdp-interval "${local_spdp_interval}"); [ '${local_fragment_size}' != "None" ] && [ -n '${local_fragment_size}' ] && local_args+=(--fragment-size "${local_fragment_size}"); python3 /ws/ota_configs/get_ota_xml.py "${local_args[@]}" --output "${local_config_file}" || { echo "FATAL - could not render the local DDS profile ${local_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_local} in cyclone) export CYCLONEDDS_URI="file://${local_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${local_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
303
305
  - if [ '${before_command}' != "None" ]; then ${before_command}; fi
304
306
 
305
307
  windows:
@@ -345,7 +347,7 @@ windows:
345
347
  if: use_domain_bridge
346
348
  splits:
347
349
  - commands:
348
- - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && ota_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && ota_args+=(--ota-domain "${ota_domain_id}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
350
+ - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && ota_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && ota_args+=(--ota-domain "${ota_domain_id}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); [ '${ota_fragment_size}' != "None" ] && [ -n '${ota_fragment_size}' ] && ota_args+=(--fragment-size "${ota_fragment_size}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
349
351
  - ros2 run domain_bridge domain_bridge --wait-for-publisher false ${domain_bridge_config_file}
350
352
  - name: IN
351
353
  if: in
@@ -353,7 +355,7 @@ windows:
353
355
  - commands:
354
356
  - if [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ]; then export ROS_DOMAIN_ID='${ota_domain_id}'; fi
355
357
  - case ${rmw_ota} in cyclone) export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp;; fastdds) export RMW_IMPLEMENTATION=rmw_fastrtps_cpp; export FASTDDS_BUILTIN_TRANSPORTS=${FASTDDS_BUILTIN_TRANSPORTS:-UDPv4};; zenoh) export RMW_IMPLEMENTATION=rmw_zenoh_cpp;; esac
356
- - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && ota_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && ota_args+=(--ota-domain "${ota_domain_id}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
358
+ - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && ota_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && ota_args+=(--ota-domain "${ota_domain_id}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); [ '${ota_fragment_size}' != "None" ] && [ -n '${ota_fragment_size}' ] && ota_args+=(--fragment-size "${ota_fragment_size}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
357
359
  # For zenoh_ros2dds the OTA domain is loopback-only (zenoh is the inter-host
358
360
  # transport), so confine discovery to localhost instead of statically peering
359
361
  # the remote host — otherwise the OTA-domain DDS double-delivers alongside zenoh.
@@ -380,7 +382,7 @@ windows:
380
382
  - commands:
381
383
  - if [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ]; then export ROS_DOMAIN_ID='${ota_domain_id}'; fi
382
384
  - case ${rmw_ota} in cyclone) export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp;; fastdds) export RMW_IMPLEMENTATION=rmw_fastrtps_cpp; export FASTDDS_BUILTIN_TRANSPORTS=${FASTDDS_BUILTIN_TRANSPORTS:-UDPv4};; zenoh) export RMW_IMPLEMENTATION=rmw_zenoh_cpp;; esac
383
- - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && ota_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && ota_args+=(--ota-domain "${ota_domain_id}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
385
+ - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ] && ota_args+=(--local-domain "${local_domain_id}"); [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ] && ota_args+=(--ota-domain "${ota_domain_id}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); [ '${ota_fragment_size}' != "None" ] && [ -n '${ota_fragment_size}' ] && ota_args+=(--fragment-size "${ota_fragment_size}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
384
386
  # For zenoh_ros2dds the OTA domain is loopback-only (zenoh is the inter-host
385
387
  # transport), so confine discovery to localhost instead of statically peering
386
388
  # the remote host — otherwise the OTA-domain DDS double-delivers alongside zenoh.
@@ -194,7 +194,8 @@ class YamlBlockScalar:
194
194
  # - a string: cyclone | fastdds | zenoh_connect_endpoints | zenoh_ros2dds | <raw rmw string>
195
195
  # (zenoh_ros2dds is allowed for ota only; raw rmw strings are for local only)
196
196
  # - a tagged-union mapping with exactly one key (the RMW name):
197
- # {cyclone: {config?: <fname.xml>, easy_mode_ip?: <address>, spdp_interval?: <duration>}}
197
+ # {cyclone: {config?: <fname.xml>, easy_mode_ip?: <address>, spdp_interval?: <duration>,
198
+ # fragment_size?: <size, e.g. 1200B>}}
198
199
  # {fastdds: {config?: <fname.xml>, easy_mode_ip?: <address>}}
199
200
  # {zenoh_connect_endpoints: {transport?: tcp|udp, main_peer?: <peer_key>, main_port?: 7447}}
200
201
  # {zenoh_ros2dds: {transport?: udp|tcp, main_peer?: <peer_key>, main_port?: 7447}}
@@ -207,7 +208,11 @@ _OTA_SHORTS = {"cyclone", "fastdds", *_NATIVE_ZENOH_OTA_SHORTS, "zenoh_ros2dds"}
207
208
  _LOCAL_SHORTS = {"cyclone", "fastdds", "zenoh"}
208
209
  _SHORTCUT_ALLOWED = {"cyclone", "fastdds", "zenoh"}
209
210
 
210
- _DDS_CFG_KEYS = {"config", "easy_mode_ip", "spdp_interval"}
211
+ _DDS_CFG_KEYS = {"config", "easy_mode_ip", "spdp_interval", "fragment_size"}
212
+ #: A CycloneDDS size: digits and an optional unit. Validated here so an
213
+ #: unusable value is a session error rather than a profile the DDS stack
214
+ #: silently ignores on its way to defaults.
215
+ _DDS_SIZE_RE = re.compile(r"(\d+)\s*(B|kB|KB|KiB|MB|MiB)?\Z")
211
216
  _ZEN_OTA_CFG_KEYS = {"transport", "main_peer", "main_port"}
212
217
  #: Inter-host transports the native zenoh router may be pointed at. Both are
213
218
  #: plain zenoh locator schemes; tls/quic would additionally need certificates
@@ -280,6 +285,7 @@ class RmwSideSpec:
280
285
  dds_config: Optional[str] = None
281
286
  dds_easy_mode_ip: Optional[str] = None
282
287
  dds_spdp_interval: Optional[str] = None
288
+ dds_fragment_size: Optional[str] = None
283
289
  # zenoh (native) and zenoh_ros2dds
284
290
  zen_main_peer: Optional[str] = None
285
291
  zen_transport: Optional[str] = None
@@ -345,6 +351,19 @@ def _parse_rmw_side(value: Any, ctx: str, *, is_local: bool) -> RmwSideSpec:
345
351
  raise RuntimeError(f"{ctx}.{impl}.spdp_interval must be a non-empty duration if provided.")
346
352
  _validate_positive_seconds(cfg["spdp_interval"], f"{ctx}.{impl}.spdp_interval")
347
353
  spec.dds_spdp_interval = cfg["spdp_interval"].strip()
354
+ if cfg.get("fragment_size") is not None:
355
+ if impl != "cyclone":
356
+ raise RuntimeError(f"{ctx}.{impl}.fragment_size is only supported for CycloneDDS.")
357
+ if not isinstance(cfg["fragment_size"], str) or not cfg["fragment_size"].strip():
358
+ raise RuntimeError(f"{ctx}.{impl}.fragment_size must be a non-empty size if provided.")
359
+ size = cfg["fragment_size"].strip()
360
+ match = _DDS_SIZE_RE.fullmatch(size)
361
+ if not match or int(match.group(1)) <= 0:
362
+ raise RuntimeError(
363
+ f"{ctx}.{impl}.fragment_size must be a positive byte size, e.g. '1024B' or '1200B'; "
364
+ f"got {cfg['fragment_size']!r}."
365
+ )
366
+ spec.dds_fragment_size = size
348
367
  elif _is_native_zenoh_ota(impl):
349
368
  extra = set(cfg.keys()) - _ZEN_OTA_CFG_KEYS
350
369
  if extra:
@@ -2279,6 +2298,8 @@ def func(
2279
2298
  )
2280
2299
  if side.impl == "cyclone" and side.dds_spdp_interval:
2281
2300
  items.append(("local_spdp_interval", side.dds_spdp_interval))
2301
+ if side.impl == "cyclone" and side.dds_fragment_size:
2302
+ items.append(("local_fragment_size", side.dds_fragment_size))
2282
2303
  return items
2283
2304
 
2284
2305
  def _build_ota_rmw_items() -> List[Tuple[str, Any]]:
@@ -2306,6 +2327,8 @@ def func(
2306
2327
  )
2307
2328
  if ota.impl == "cyclone" and ota.dds_spdp_interval:
2308
2329
  items.append(("ota_spdp_interval", ota.dds_spdp_interval))
2330
+ if ota.impl == "cyclone" and ota.dds_fragment_size:
2331
+ items.append(("ota_fragment_size", ota.dds_fragment_size))
2309
2332
  return items
2310
2333
 
2311
2334
  def _build_zenoh_block(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev70
3
+ Version: 2.5.dev72
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.4",
3
- "distance": 70,
4
- "node": "g2a4d43769fad290b84ccf9e3a25455c6e45ce093",
3
+ "distance": 72,
4
+ "node": "g35a828b2f75e38e29c67649e1ce73dafee8c561a",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-21"
@@ -315,7 +315,7 @@ SPEC_OWNS = (
315
315
  #: options. Listed by pattern so an unclassified newcomer fails the guard.
316
316
  NO_TOPIC = (
317
317
  r"dir_path|before_command|peer_dir|session_dir",
318
- r"(rmw|ota|local)_(local|ota|config_template|config_file|easy_mode_ip|spdp_interval|domain_id|name)",
318
+ r"(rmw|ota|local)_(local|ota|config_template|config_file|easy_mode_ip|spdp_interval|fragment_size|domain_id|name)",
319
319
  r"domain_bridge_config_file|use_domain_bridge",
320
320
  r"ip_(local|remote)|remote_name|qos_config_file|topic_types_file",
321
321
  r"in|out|use_zenoh_(rmw|ros2dds)",
@@ -3166,6 +3166,88 @@ def test_run_session_passes_cyclone_spdp_interval_to_plugin(tmp_path: Path) -> N
3166
3166
  assert plugin["parameters"]["ota_spdp_interval"] == "150s"
3167
3167
 
3168
3168
 
3169
+ def test_run_session_passes_cyclone_fragment_size_to_plugin(tmp_path: Path) -> None:
3170
+ """The number reaches the peer that renders the profile, not just the YAML."""
3171
+ from session.creation import run_session
3172
+
3173
+ source = tmp_path / "sessions" / "fragment"
3174
+ output = tmp_path / "session-instances" / "2026-01-01" / "fragment_run" / "config"
3175
+ source.mkdir(parents=True)
3176
+ (source / "session-definition.yaml").write_text(
3177
+ "\n".join(
3178
+ [
3179
+ "peers:",
3180
+ " a: {}",
3181
+ " b: {}",
3182
+ "shared:",
3183
+ " rmw:",
3184
+ " local: cyclone",
3185
+ " ota:",
3186
+ " cyclone:",
3187
+ " fragment_size: 1200B",
3188
+ "topics:",
3189
+ " a_to_b:",
3190
+ " - topic: /x",
3191
+ " type: std_msgs/msg/String",
3192
+ "",
3193
+ ]
3194
+ ),
3195
+ encoding="utf-8",
3196
+ )
3197
+
3198
+ peer_dir = run_session._resolve_peer_dir(
3199
+ str(source),
3200
+ str(output),
3201
+ "a",
3202
+ force=True,
3203
+ rewrite_formatting=False,
3204
+ peer_address=["a=127.0.0.1", "b=127.0.0.2"],
3205
+ )
3206
+
3207
+ plugin = yaml.safe_load((Path(peer_dir) / "plugin.yaml").read_text(encoding="utf-8"))
3208
+ assert plugin["parameters"]["ota_fragment_size"] == "1200B"
3209
+
3210
+
3211
+ def test_run_session_rejects_a_fragment_size_no_template_can_use(tmp_path: Path) -> None:
3212
+ """An unusable size is a session error, not a profile that renders wrong."""
3213
+ from session.creation import run_session
3214
+
3215
+ source = tmp_path / "sessions" / "bad_fragment"
3216
+ output = tmp_path / "session-instances" / "2026-01-01" / "bad_fragment_run" / "config"
3217
+ source.mkdir(parents=True)
3218
+ (source / "session-definition.yaml").write_text(
3219
+ "\n".join(
3220
+ [
3221
+ "peers:",
3222
+ " a: {}",
3223
+ " b: {}",
3224
+ "shared:",
3225
+ " rmw:",
3226
+ " local: cyclone",
3227
+ " ota:",
3228
+ " cyclone:",
3229
+ " fragment_size: plenty",
3230
+ "topics:",
3231
+ " a_to_b:",
3232
+ " - topic: /x",
3233
+ " type: std_msgs/msg/String",
3234
+ "",
3235
+ ]
3236
+ ),
3237
+ encoding="utf-8",
3238
+ )
3239
+
3240
+ with pytest.raises(RuntimeError, match="positive byte size"):
3241
+ run_session._resolve_peer_dir(
3242
+ str(source),
3243
+ str(output),
3244
+ "a",
3245
+ force=True,
3246
+ rewrite_formatting=False,
3247
+ peer_address=["a=127.0.0.1", "b=127.0.0.2"],
3248
+ )
3249
+
3250
+
3169
3251
  def test_create_session_yaml_injects_catmux_logging_command(tmp_path: Path) -> None:
3170
3252
  from session.creation import create_session_yaml
3171
3253
 
@@ -91,6 +91,48 @@ def test_only_cyclone_caps_the_datagram_and_fast_dds_must_not() -> None:
91
91
  assert "max_msg_size=" not in resolved, config
92
92
 
93
93
 
94
+ def test_every_bootstrap_forwards_the_fragment_size_it_was_given() -> None:
95
+ """One forgotten window renders the default and nothing says so.
96
+
97
+ The OTA profile is rendered in three windows (COM, IN, OUT) and the local
98
+ one in a fourth. A session that names a fragment size and gets it in three
99
+ of the four has two profiles on one host, which is the failure mode
100
+ cyclonedds_scoped.xml exists to remove — and it would look healthy.
101
+ """
102
+ plugin_base = PLUGIN_BASE.read_text(encoding="utf-8")
103
+
104
+ assert plugin_base.count('ota_args+=(--fragment-size "${ota_fragment_size}")') == 3
105
+ assert plugin_base.count('local_args+=(--fragment-size "${local_fragment_size}")') == 1
106
+ # Declared, so an unset one renders as "None" and is skipped rather than
107
+ # left as a literal ${...} in the command line.
108
+ assert "\n ota_fragment_size:" in plugin_base
109
+ assert "\n local_fragment_size:" in plugin_base
110
+
111
+
112
+ def test_a_session_may_ask_the_packaged_templates_for_the_older_profile() -> None:
113
+ """Both shipped Cyclone OTA templates resolve the size, not just one.
114
+
115
+ The two-profile arrangement renders cyclonedds_tuned.xml and the scoped one
116
+ renders cyclonedds_scoped.xml. A Kilted-versus-Lyrical comparison needs the
117
+ same fragment size whichever arrangement it runs, so neither template may
118
+ keep the number as a literal.
119
+ """
120
+ module = _load(OTA_CONFIGS / "get_ota_xml.py", "rosotacom_get_ota_xml_fragment")
121
+ for config in ("cyclonedds_tuned.xml", "cyclonedds_scoped.xml"):
122
+ rendered = str(
123
+ module.main(
124
+ config=config,
125
+ host_ip="10.0.0.1",
126
+ peer="10.0.0.2",
127
+ fragment_size="1200B",
128
+ local_domain="47",
129
+ ota_domain="48",
130
+ )
131
+ )
132
+ assert "<FragmentSize>1200B</FragmentSize>" in rendered, config
133
+ assert f"<MaxMessageSize>{OTA_DATAGRAM_BYTES}B</MaxMessageSize>" in rendered, config
134
+
135
+
94
136
  def test_both_default_ota_templates_pin_the_interface_and_the_peer() -> None:
95
137
  for config in ("cyclonedds_tuned.xml", "fastdds_tuned.xml"):
96
138
  resolved = _resolved(config)