rosotacom-dev 2.5.dev76__tar.gz → 2.5.dev78__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 (399) hide show
  1. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/neither-a-queue-nor-a-burst-tilts-the-size-law.md +60 -17
  3. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/cli.py +180 -14
  4. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/cli_benchmark.py +27 -21
  5. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/profiles.yaml +71 -0
  6. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  7. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/SOURCES.txt +1 -0
  8. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/scm_file_list.json +1 -0
  9. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  10. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_cli_benchmark.py +9 -1
  11. rosotacom_dev-2.5.dev78/tests/unit/test_ota_benchmark_peer_exec.py +134 -0
  12. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.devcontainer/devcontainer.json.template +0 -0
  13. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  14. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/CODEOWNERS +0 -0
  15. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  16. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  17. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  18. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  19. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  20. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  21. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  22. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  23. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/actions/setup-just/action.yml +0 -0
  25. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/dependabot.yml +0 -0
  26. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/benchmark-calibrate.yml +0 -0
  27. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/benchmark-gate.yml +0 -0
  28. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/dev-release.yml +0 -0
  29. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/image-scan.yml +0 -0
  30. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/nightly-e2e.yml +0 -0
  31. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/pr-lightweight.yml +0 -0
  32. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/pr-merge-gate.yml +0 -0
  33. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.github/workflows/release.yml +0 -0
  34. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/.pre-commit-config.yaml +0 -0
  35. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/AGENTS.md +0 -0
  36. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/CONCURRENCY.md +0 -0
  37. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/CONTRIBUTING.md +0 -0
  38. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/DEVELOPMENT_PRINCIPLES.md +0 -0
  39. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/LICENSE.txt +0 -0
  40. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/MANIFEST.in +0 -0
  41. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/README.md +0 -0
  42. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/budgets.jsonl +0 -0
  43. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/README.md +0 -0
  44. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/capacity-size-tight-cyclone.json +0 -0
  45. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  46. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/probe-loss-nominal-cyclone.json +0 -0
  47. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/probe-loss-tight-cyclone.json +0 -0
  48. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/probe-loss-tight-fastdds.json +0 -0
  49. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/calibration/probe-loss-tight-zenoh.json +0 -0
  50. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/codecov.yml +0 -0
  51. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/deployment-configuration.md +0 -0
  52. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/benchmark-ab.md +0 -0
  53. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/benchmark-bag-as-load.md +0 -0
  54. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/ci.md +0 -0
  55. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/ffmpeg-keyframes.md +0 -0
  56. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/README.md +0 -0
  57. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/a-fitted-delay-table-never-reached-the-shaper.md +0 -0
  58. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/a-shorter-group-does-not-lose-fewer-key-frames.md +0 -0
  59. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/a-transport-element-doubles-every-fastdds-sample.md +0 -0
  60. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/cyclone-fragment-equal-to-max-drops-fragmented-samples.md +0 -0
  61. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  62. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/delay-alone-no-loss.md +0 -0
  63. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
  64. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
  65. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/head-of-line-irregularity.md +0 -0
  66. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  67. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
  68. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/lifespan-is-enforced-by-one-reader-and-not-the-other.md +0 -0
  69. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/lighter-message-loss.md +0 -0
  70. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/one-domain-two-cyclone-configs-stop-interoperating.md +0 -0
  71. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/oversubscription-queues-not-losses.md +0 -0
  72. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/reorder-becomes-reader-loss.md +0 -0
  73. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/repro/decode_chain_repro.py +0 -0
  74. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/repro/reorder_drop_repro.py +0 -0
  75. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
  76. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/the-size-law-belongs-to-the-transport-not-the-link.md +0 -0
  77. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  78. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/forensics-report.md +0 -0
  79. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/geo-link-map.md +0 -0
  80. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/github-repository-settings.md +0 -0
  81. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/link-bandwidth.md +0 -0
  82. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/link-trace.md +0 -0
  83. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/ota-pcap.md +0 -0
  84. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/owner-runbook.md +0 -0
  85. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/performance-bands.md +0 -0
  86. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/playbooks/README.md +0 -0
  87. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/quality-model.md +0 -0
  88. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/release-notes/TEMPLATE.md +0 -0
  89. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/release-notes/v2.1.md +0 -0
  90. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/release-notes/v2.2.md +0 -0
  91. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/release-notes/v2.3.md +0 -0
  92. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/release-notes/v2.4.md +0 -0
  93. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/release.md +0 -0
  94. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  95. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0002-expectation-concepts.md +0 -0
  96. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0003-metric-backbone.md +0 -0
  97. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0004-network-profiles.md +0 -0
  98. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  99. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0006-dynamic-qos.md +0 -0
  100. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/0007-regression-gate.md +0 -0
  101. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/rfcs/README.md +0 -0
  102. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/testing.md +0 -0
  103. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/docs/work-items.md +0 -0
  104. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/install.sh +0 -0
  105. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/justfile +0 -0
  106. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/media/control_center.png +0 -0
  107. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/media/echo_test.png +0 -0
  108. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/media/forensics-report-example.png +0 -0
  109. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/media/host.png +0 -0
  110. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/media/vehicle.png +0 -0
  111. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/pyproject.toml +0 -0
  112. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/requirements.txt +0 -0
  113. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/rosotacom.yaml +0 -0
  114. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/session-configuration.md +0 -0
  115. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/setup.cfg +0 -0
  116. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/__init__.py +0 -0
  117. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/__main__.py +0 -0
  118. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/anonymize.py +0 -0
  119. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/bag_ground_truth.py +0 -0
  120. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/benched_set.py +0 -0
  121. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/benchmark.py +0 -0
  122. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/benchmark_delta.py +0 -0
  123. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/bundle_check.py +0 -0
  124. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/deployment.py +0 -0
  125. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/ffmpeg_packet.py +0 -0
  126. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/forensics.py +0 -0
  127. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/geomap.py +0 -0
  128. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/image_cache.py +0 -0
  129. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/netem_dist.py +0 -0
  130. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/network_preflight.py +0 -0
  131. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/network_profiles.py +0 -0
  132. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/network_shaper.py +0 -0
  133. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/plots.py +0 -0
  134. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/py.typed +0 -0
  135. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/benched-set.yaml +0 -0
  136. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/.gitignore +0 -0
  137. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/README.md +0 -0
  138. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/__init__.py +0 -0
  139. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  140. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  141. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
  142. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  143. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/ros2docker.lyrical.json +0 -0
  144. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  145. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  146. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  147. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  148. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.lyrical.ros2docker.json +0 -0
  149. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  150. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  151. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  152. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  153. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.lyrical.ros2docker.json +0 -0
  154. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  155. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  156. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  157. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  158. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  159. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  160. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  161. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  162. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  163. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  164. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  165. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  166. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  167. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  168. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  169. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  170. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  171. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  172. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  173. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  174. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  175. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  176. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  177. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  178. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  179. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  180. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  181. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  182. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  183. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  184. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  185. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  186. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  187. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  188. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  189. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  190. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  191. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  192. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  193. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  204. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  205. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  206. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  207. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  208. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  209. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  210. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  211. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  212. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  213. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  214. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  215. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  216. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ros2docker.json.example +0 -0
  217. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ros2docker.lyrical.json.example +0 -0
  218. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  219. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  220. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  221. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  222. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  223. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  224. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  225. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  226. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  227. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  228. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  229. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/cyclonedds_scoped.xml.template +0 -0
  230. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  231. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
  232. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template +0 -0
  233. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
  234. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  235. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  236. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  237. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  238. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  239. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  240. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  241. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  242. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  243. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  244. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  245. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  246. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  247. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  248. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  249. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  250. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  251. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  252. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  253. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  254. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  255. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  256. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  257. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  258. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
  259. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  260. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  261. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  262. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  263. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  264. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  265. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  266. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  267. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  268. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  269. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  270. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  271. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  272. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  273. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  274. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  275. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  276. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ota_pcap.py +0 -0
  277. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  278. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
  279. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
  280. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  281. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  282. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  283. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  284. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  285. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  286. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  287. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  288. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  289. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  290. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  291. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  292. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  293. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  294. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  295. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  296. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  297. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  298. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  299. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  300. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  301. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  302. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  303. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  304. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  305. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
  306. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  307. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  308. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  309. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  310. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  311. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  312. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  313. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  314. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  315. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/status_eval.py +0 -0
  316. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/stream_stats.py +0 -0
  317. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/trace_profiles.py +0 -0
  318. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/transit.py +0 -0
  319. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom/video_quality.py +0 -0
  320. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  321. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  322. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  323. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  324. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/terminology.md +0 -0
  325. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_benched_set_registry.py +0 -0
  326. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_findings.py +0 -0
  327. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_generated_topics_have_a_producer.py +0 -0
  328. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_lyrical_runtime_compat.py +0 -0
  329. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_markdown_links.py +0 -0
  330. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_ota_source_bundle.py +0 -0
  331. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_package_metadata.py +0 -0
  332. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_packaged_resources.py +0 -0
  333. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  334. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_pytest_policy.py +0 -0
  335. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_readme_examples.py +0 -0
  336. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_security_maintenance_config.py +0 -0
  337. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_session_configuration_doc.py +0 -0
  338. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_session_template_substitution.py +0 -0
  339. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/contract/test_workflow_contracts.py +0 -0
  340. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/conftest.py +0 -0
  341. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_anonymize_e2e.py +0 -0
  342. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_benchmark_ab.py +0 -0
  343. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_benchmark_capacity.py +0 -0
  344. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_benchmark_replay.py +0 -0
  345. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_parallel_smoke.py +0 -0
  346. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_smoke.py +0 -0
  347. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_timeline_stepping.py +0 -0
  348. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/e2e/test_video_quality_e2e.py +0 -0
  349. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/generate_rmw_matrix.py +0 -0
  350. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  351. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  352. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  353. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  354. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
  355. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  356. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_anonymize.py +0 -0
  357. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_bag_ground_truth.py +0 -0
  358. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_benched_set.py +0 -0
  359. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_benchmark.py +0 -0
  360. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_benchmark_delta.py +0 -0
  361. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_bundle_check.py +0 -0
  362. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_catmux_pane_logging.py +0 -0
  363. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_cli.py +0 -0
  364. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_concurrency.py +0 -0
  365. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_container_shaping.py +0 -0
  366. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_deployment.py +0 -0
  367. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ffmpeg_flags.py +0 -0
  368. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ffmpeg_packet.py +0 -0
  369. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_field_profiles.py +0 -0
  370. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_forensics.py +0 -0
  371. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_geomap.py +0 -0
  372. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_image_cache.py +0 -0
  373. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_link_bytes.py +0 -0
  374. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_link_trace.py +0 -0
  375. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_netem_dist.py +0 -0
  376. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_network_preflight.py +0 -0
  377. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_network_profiles.py +0 -0
  378. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_network_shaper.py +0 -0
  379. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ota_link_configs.py +0 -0
  380. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ota_pcap.py +0 -0
  381. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ota_unwrapper.py +0 -0
  382. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ota_wrapper.py +0 -0
  383. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ota_xml.py +0 -0
  384. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_playout_pacer.py +0 -0
  385. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_plots.py +0 -0
  386. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_pub_sub_pair_types.py +0 -0
  387. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_qos_fallback_logging.py +0 -0
  388. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_relay_optional_parameters.py +0 -0
  389. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_ros_node_entity_ownership.py +0 -0
  390. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_session_pipeline.py +0 -0
  391. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_stage_latency.py +0 -0
  392. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_status_eval.py +0 -0
  393. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_status_overview.py +0 -0
  394. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_status_overview_observer.py +0 -0
  395. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_stream_stats.py +0 -0
  396. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_terminology_stage_ownership.py +0 -0
  397. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_trace_profiles.py +0 -0
  398. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/tests/unit/test_transit.py +0 -0
  399. {rosotacom_dev-2.5.dev76 → rosotacom_dev-2.5.dev78}/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.dev76
3
+ Version: 2.5.dev78
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -10,6 +10,7 @@ were emulated against the same measurement chain, and none of them produces it.
10
10
  | | 300 B | 1.1 kB | 3.1 kB | 6.1 kB | 12.1 kB | 38.1 kB |
11
11
  |---|---:|---:|---:|---:|---:|---:|
12
12
  | independent per packet | 1.05 % | 1.47 % | 3.47 % | 6.28 % | 11.23 % | 31.92 % |
13
+ | a two-state process, same mean, 105 ms bad runs | 1.47 % | 1.76 % | 2.76 % | 5.09 % | 10.00 % | **21.98 %** |
13
14
  | + a token bucket a burst can fill (4 Mbit/s) | 1.14 % | 1.33 % | 2.57 % | 4.99 % | 13.43 % | 32.92 % |
14
15
  | a serialisation constraint (1.5 Mbit/s) | — | 1.62 % | — | — | 11.60 % | **97.54 %** |
15
16
 
@@ -17,6 +18,16 @@ were emulated against the same measurement chain, and none of them produces it.
17
18
  law is reproduced with residuals of 1.20, 0.85, 1.01, 1.06, 0.97 and 1.00 —
18
19
  flat over three decades of size. Whatever tilts the field, it is not the
19
20
  wrapper, the join, or the way the fit is taken.
21
+ - **Clustered loss is not the cause, and it bends the curve the other way.**
22
+ A two-state process at the same 1 % mean, with its chain rescaled per cell so
23
+ that a bad run is the same 105 ms of wall clock in every one of them, makes
24
+ the largest class lose **31 % less** than the independent law rather than
25
+ more: 21.98 % against 31.92 %, intervals 20.3–23.8 against 30.0–33.9. The
26
+ reason is the one that makes it a mechanism rather than a coincidence — a
27
+ 38-packet message occupies about the length of one bad run, so its packets
28
+ share a fate and the message is lost once instead of thirty-eight times
29
+ independently. The field's residual has the opposite sign, so burstiness at
30
+ this scale does not explain it; it makes it harder to explain.
20
31
  - **A burst meeting a queue is not the cause.** A token bucket at 4 Mbit/s —
21
32
  above the largest class's mean offered rate of 3.0 Mbit/s and far below its
22
33
  instantaneous one, so a 38-packet message meets a queue and a 1-packet
@@ -81,17 +92,30 @@ The queued arm on the same ladder: 24, 28, 54, 105, 283 and 693 losses of about
81
92
  2100 offered, giving the second row of the claim table. The tight arm: 34 of
82
93
  2104, 244 of 2103, and 1826 of 1872.
83
94
 
84
- **A two-state loss process could not be measured on this ladder, and the reason
85
- is worth recording.** `netem`'s Gilbert–Elliott model advances its chain once
86
- per *packet*, not once per second, so a size ladder at a fixed message rate runs
87
- the chain at a different speed in every cell — 10 packets/s for a one-fragment
95
+ **Measuring the two-state arm took two attempts, and the reason is worth
96
+ recording.** `netem`'s Gilbert–Elliott model advances its chain once per
97
+ *packet*, not once per second, so a size ladder at a fixed message rate runs the
98
+ chain at a different speed in every cell — 10 packets/s for a one-fragment
88
99
  message against 380 for a 38-fragment one. The cells then do not share a loss
89
- process and cannot be compared; the arm measured at a fixed 10 Hz duly reported
90
- a per-packet mean of 0.33 % in its smallest cell and 0.69 % fitted over the
91
- ladder, against a stationary 1.00 %, because the small cells never sampled
92
- enough of the chain. Holding the offered *packet* rate constant is what makes
93
- such a ladder one experiment, and the profiles file says so beside the fitted
94
- parameters.
100
+ process. The arm measured that way duly reported a per-packet mean of 0.33 % in
101
+ its smallest cell against a stationary 1.00 %, because that cell never sampled
102
+ enough of the chain.
103
+
104
+ Holding the offered *packet* rate constant instead was the obvious fix and it
105
+ measured something else entirely: **the pipeline serves messages, not packets.**
106
+ At 60 messages/s the one-packet cell lost 10.774 % under the bursty profile and
107
+ 10.626 % under the independent one, and at 380 messages/s 48.391 % and 48.430 %.
108
+ Two different loss processes cannot agree to three digits; what both were
109
+ reporting is the depth-1 OTA queue overwriting its own backlog before the link
110
+ sees it. That is the depth-1 overwrite of bundled publications, which the
111
+ operator harness's ledger records against a real drive, reached here by
112
+ accident and against a synthetic one.
113
+
114
+ What works is to leave the message rate at the field's 10 Hz and rescale the
115
+ chain: multiplying `p` and `r` by `380/(N × 10)` in each cell leaves the
116
+ stationary bad fraction at 3.92 % and the mean loss at 1 % while making the bad
117
+ run 105 ms of wall clock everywhere, which is the drive's own figure. The six
118
+ `bp-frag-*` profiles are that ladder.
95
119
 
96
120
  Verification: manual, from a source checkout with Docker:
97
121
 
@@ -109,9 +133,19 @@ rosotacom benchmark probe --project src/rosotacom/resources/examples/rosotacom.y
109
133
  --repeats 1 --sudo-mode container --no-plot
110
134
  ```
111
135
 
136
+ ```bash
137
+ for pair in 200:one 1000:two 3000:four 6000:six 12000:twelve 38000:thirtyeight; do
138
+ rosotacom benchmark probe \
139
+ --project src/rosotacom/resources/examples/rosotacom.yaml \
140
+ --profile "bp-frag-${pair##*:}" --size "${pair%%:*}" --rate-hz 10 \
141
+ --duration 240 --repeats 1 --sudo-mode container --no-plot
142
+ done
143
+ ```
144
+
112
145
  The independent ladder must fit one `(p, F)` to within about ten per cent at
113
- every point; the queued ladder must not differ from it systematically; and the
114
- tight cell at 38 kB must be near-total loss while its 1 kB cell is not.
146
+ every point; the queued ladder must not differ from it systematically; the
147
+ rescaled two-state ladder must fall below it at the large end and not above; and
148
+ the tight cell at 38 kB must be near-total loss while its 1 kB cell is not.
115
149
 
116
150
  ## Status
117
151
 
@@ -126,11 +160,20 @@ the tilt is not an artefact of the accounting, it is not what a queue does to a
126
160
  burst, and it is not a serialisation limit, because a serialisation limit does
127
161
  not tilt a curve, it truncates it.
128
162
 
129
- What is left to attack is correlation *inside* one message's own burst on a
130
- real radio loss events shorter than the time a large message spends on the
131
- wire, which `netem`'s per-packet chain cannot express at all. The
132
- Gilbert–Elliott note above is the reason: the tool models a process in packets,
133
- and the candidate mechanism lives in milliseconds.
163
+ The clustered arm is the one that changes the reading rather than confirming
164
+ it. A reviewer's first guess for a size-systematic residual is burstiness, and
165
+ burstiness at the drive's own time scale produces the **opposite sign**: it
166
+ protects large messages, because a message long enough to span a bad run is lost
167
+ once rather than in each of its packets. So the field's residual is not
168
+ explained by the mechanism that the field's own two-layer model already
169
+ contains, which is worth saying out loud.
170
+
171
+ What is left to attack is correlation at a scale *shorter* than a large
172
+ message's own time on the wire — short enough that a message straddles a loss
173
+ event more often than a single packet does, without its packets sharing one.
174
+ `netem` cannot express that at all: its chain advances per packet, so a run is
175
+ always measured in packets rather than in milliseconds, and the two coincide
176
+ only at one message size.
134
177
 
135
178
  The serialisation row deserves one sentence wherever a duty-cycled uplink is
136
179
  discussed. It shows that the observable signature of such a link — small
@@ -293,6 +293,12 @@ class OtaSmokePlan:
293
293
  peers: dict[str, OtaSmokePeer]
294
294
  install_mode: str = "source"
295
295
  install_pin: str | None = None
296
+ #: Where the peers write session instances, relative to the project file's
297
+ #: own directory. `session-instances` is the default a project that says
298
+ #: nothing gets; a project that sets `session_instances_dir` to anything
299
+ #: else -- `../instances`, say -- used to have its artifacts looked for in a
300
+ #: directory nothing writes to, and the miss was silent.
301
+ instances_rel: str = "session-instances"
296
302
 
297
303
 
298
304
  @dataclass(frozen=True)
@@ -1835,6 +1841,26 @@ def _ota_checkout_project_path(project_config: Path) -> tuple[Path, str]:
1835
1841
  )
1836
1842
 
1837
1843
 
1844
+ def _ota_instances_rel(runtime: RuntimeConfig) -> str:
1845
+ """Where this project writes session instances, relative to its own file.
1846
+
1847
+ The peers resolve the project from their own checkout, so the *relative*
1848
+ answer is the one that travels; an absolute path is the orchestrator's and
1849
+ means nothing on the far side. A project that says nothing gets
1850
+ `session-instances`, which is what the collection used to assume for every
1851
+ project -- and a project that says `../instances` had its artifacts looked
1852
+ for in a directory nothing writes to.
1853
+ """
1854
+ default = "session-instances"
1855
+ if not runtime.rosotacom_config:
1856
+ return default
1857
+ raw = _load_yaml_file(runtime.rosotacom_config)
1858
+ value = raw.get("session_instances_dir")
1859
+ if not value:
1860
+ return default
1861
+ return str(value)
1862
+
1863
+
1838
1864
  def _ota_plan_from_bindings(
1839
1865
  bindings: dict[str, PeerBinding],
1840
1866
  *,
@@ -1845,6 +1871,7 @@ def _ota_plan_from_bindings(
1845
1871
  project: str | None = None,
1846
1872
  exec_commands: Mapping[str, str] | None = None,
1847
1873
  cpusets: Mapping[str, str] | None = None,
1874
+ instances_rel: str = "session-instances",
1848
1875
  ) -> OtaSmokePlan:
1849
1876
  _ota_validate_install_mode(install_mode)
1850
1877
  if cpusets:
@@ -1923,6 +1950,7 @@ def _ota_plan_from_bindings(
1923
1950
  peers=peers,
1924
1951
  install_mode=install_mode,
1925
1952
  install_pin=install_pin,
1953
+ instances_rel=instances_rel,
1926
1954
  )
1927
1955
 
1928
1956
 
@@ -1935,6 +1963,7 @@ def _ota_write_state(instance: SessionInstance, plan: OtaSmokePlan) -> OtaSmokeP
1935
1963
  "project": plan.project,
1936
1964
  "install_mode": plan.install_mode,
1937
1965
  "install_pin": plan.install_pin,
1966
+ "instances_rel": plan.instances_rel,
1938
1967
  "peers": {
1939
1968
  name: {
1940
1969
  "address": peer.address,
@@ -1979,6 +2008,7 @@ def _ota_load_state(path_arg: str) -> OtaSmokePlan:
1979
2008
  peers=peers,
1980
2009
  install_mode=_ota_validate_install_mode(str(raw["install_mode"])),
1981
2010
  install_pin=str(install_pin) if install_pin else None,
2011
+ instances_rel=str(raw.get("instances_rel") or "session-instances"),
1982
2012
  )
1983
2013
 
1984
2014
 
@@ -2404,12 +2434,26 @@ def _ota_stop_parts(
2404
2434
  return parts
2405
2435
 
2406
2436
 
2437
+ #: How a benchmark's load maps onto `publish-test-topics` flags. The keys are
2438
+ #: `_sized_publisher_param_args`'s own, so a load dict travels from the
2439
+ #: benchmark to the peer's publisher without being rewritten on the way.
2440
+ _OTA_LOAD_FLAGS: tuple[tuple[str, str], ...] = (
2441
+ ("size", "--load-size"),
2442
+ ("pattern", "--load-size-pattern"),
2443
+ ("rate", "--load-rate-hz"),
2444
+ ("streams", "--load-streams"),
2445
+ ("interval_jitter_ms", "--load-interval-jitter-ms"),
2446
+ ("interval_jitter_seed", "--load-interval-jitter-seed"),
2447
+ )
2448
+
2449
+
2407
2450
  def _ota_publish_parts(
2408
2451
  target: InteractiveSmokeTarget,
2409
2452
  identity: str,
2410
2453
  peer_args: list[str],
2411
2454
  *,
2412
2455
  stop: bool = False,
2456
+ load: Mapping[str, Any] | None = None,
2413
2457
  ) -> list[str]:
2414
2458
  parts = [
2415
2459
  "publish-test-topics",
@@ -2421,6 +2465,13 @@ def _ota_publish_parts(
2421
2465
  ]
2422
2466
  if stop:
2423
2467
  parts.append("--stop")
2468
+ else:
2469
+ merged = dict(load or {})
2470
+ if "size" not in merged and merged.get("size_a") is not None:
2471
+ merged["size"] = merged["size_a"]
2472
+ for key, flag in _OTA_LOAD_FLAGS:
2473
+ if merged.get(key) is not None:
2474
+ parts.extend([flag, str(merged[key])])
2424
2475
  for override in peer_args:
2425
2476
  parts.extend(["--peer-address", override])
2426
2477
  return parts
@@ -2769,6 +2820,7 @@ def _resolve_ota_smoke_context(
2769
2820
  project=project,
2770
2821
  exec_commands=exec_commands,
2771
2822
  cpusets=_parse_peer_cpuset_overrides(getattr(args, "peer_cpuset", None)),
2823
+ instances_rel=_ota_instances_rel(runtime),
2772
2824
  )
2773
2825
  plan_peers = set(plan.peers)
2774
2826
  target_peers = set(_peer_keys_from_cfg(target.cfg))
@@ -3732,14 +3784,41 @@ def _resolve_ota_profile(
3732
3784
  return name, profile
3733
3785
 
3734
3786
 
3735
- def _profile_directions(plan: OtaSmokePlan, cfg: dict[str, Any]) -> dict[str, str]:
3787
+ def _loaded_peer(cfg: dict[str, Any]) -> str | None:
3788
+ """Which peer's egress carries this session's dominant direction.
3789
+
3790
+ A profile's ``uplink`` leg is the impaired one in almost every profile this
3791
+ project ships, so which peer it lands on decides what the run measures. The
3792
+ session already says: `topics.a_to_b` and `topics.b_to_a` are its two
3793
+ directions, and the one with more topics is the one the load runs over.
3794
+ Reading it here is what keeps a one-host and a two-host run of the *same*
3795
+ session under the *same* profile from shaping opposite directions -- which
3796
+ they did, silently, until 2026-08-31.
3797
+ """
3798
+ topics = cfg.get("topics")
3799
+ if not isinstance(topics, dict):
3800
+ return None
3801
+ a_to_b = len(topics.get("a_to_b") or ())
3802
+ b_to_a = len(topics.get("b_to_a") or ())
3803
+ if a_to_b == b_to_a:
3804
+ return None
3805
+ return "a" if a_to_b > b_to_a else "b"
3806
+
3807
+
3808
+ def _profile_directions_for(peers: list[str], cfg: dict[str, Any]) -> dict[str, str]:
3736
3809
  """Map each peer to the profile direction its egress carries (RFC 0004).
3737
3810
 
3738
- Default for the a/b convention: the first peer (center / receiver) shapes
3739
- ``downlink``, the second (vehicle / sender) shapes ``uplink`` — the dominant
3740
- telemetry direction. Override with ``shared.profile_directions: { a: …, b: }``."""
3741
- peers = sorted(plan.peers)
3742
- directions: dict[str, str] = {peers[0]: "downlink", peers[1]: "uplink"} if len(peers) == 2 else {}
3811
+ The sender of the session's dominant direction shapes ``uplink``; the other
3812
+ peer shapes ``downlink``. Where the two directions carry the same number of
3813
+ topics the session does not say, and the a/b convention decides: the first
3814
+ peer is the centre and receives, the second is the vehicle and sends.
3815
+ ``shared.profile_directions: { a: …, b: }`` overrides either.
3816
+ """
3817
+ directions: dict[str, str] = {}
3818
+ if len(peers) == 2:
3819
+ sender = _loaded_peer(cfg) or peers[1]
3820
+ other = next(name for name in peers if name != sender)
3821
+ directions = {sender: "uplink", other: "downlink"}
3743
3822
  shared = cfg.get("shared")
3744
3823
  override = shared.get("profile_directions") if isinstance(shared, dict) else None
3745
3824
  if isinstance(override, dict):
@@ -3751,6 +3830,11 @@ def _profile_directions(plan: OtaSmokePlan, cfg: dict[str, Any]) -> dict[str, st
3751
3830
  return directions
3752
3831
 
3753
3832
 
3833
+ def _profile_directions(plan: OtaSmokePlan, cfg: dict[str, Any]) -> dict[str, str]:
3834
+ """`_profile_directions_for` against a plan's peers."""
3835
+ return _profile_directions_for(sorted(plan.peers), cfg)
3836
+
3837
+
3754
3838
  def _ota_resolve_interfaces(peer: OtaSmokePeer, peer_addr: str, *, dry_run: bool) -> tuple[str, str | None]:
3755
3839
  """Discover ``(OTA egress, control)`` interfaces on ``peer`` from the kernel, not
3756
3840
  guesses: the OTA interface is the route to the other peer's data address; the
@@ -3935,6 +4019,7 @@ def _ota_start_session_publishers(
3935
4019
  plan: OtaSmokePlan,
3936
4020
  *,
3937
4021
  dry_run: bool,
4022
+ load: Mapping[str, Any] | None = None,
3938
4023
  ) -> None:
3939
4024
  if target.target_type != "session":
3940
4025
  print("OTA smoke: target is a scenario; using its application publishers.")
@@ -3942,7 +4027,7 @@ def _ota_start_session_publishers(
3942
4027
  peer_args = _ota_peer_address_args(plan)
3943
4028
  for peer_name in sorted(plan.peers):
3944
4029
  peer = plan.peers[peer_name]
3945
- command = _ota_rosotacom_command(plan, _ota_publish_parts(target, peer_name, peer_args), peer)
4030
+ command = _ota_rosotacom_command(plan, _ota_publish_parts(target, peer_name, peer_args, load=load), peer)
3946
4031
  result = _ota_run(peer, command, label=f"{peer_name}: start synthetic publishers", dry_run=dry_run, check=False)
3947
4032
  _print_completed_output(result)
3948
4033
  if result.returncode != 0:
@@ -4068,21 +4153,44 @@ def _ota_collect_logs(
4068
4153
  dry_run: bool,
4069
4154
  ) -> None:
4070
4155
  project = shlex.quote(plan.project)
4156
+ instances_rel = shlex.quote(plan.instances_rel)
4071
4157
  instance_suffix = shlex.quote(f"*_{instance.instance_id}")
4158
+ collected = 0
4072
4159
  for peer in plan.peers.values():
4073
4160
  script = (
4074
4161
  f"cd {shlex.quote(_ota_peer_workdir(plan, peer))} && "
4075
4162
  f"project_dir=$(dirname {project}) && "
4076
- f'instance_dir=$(find "$project_dir/session-instances" -mindepth 2 -maxdepth 2 '
4163
+ f'instances_root=$(cd "$project_dir" && cd "$(dirname {instances_rel})" 2>/dev/null '
4164
+ f'&& printf %s "$PWD/$(basename {instances_rel})") && '
4165
+ f'instance_dir=$(find "$instances_root" -mindepth 2 -maxdepth 2 '
4077
4166
  f"-type d -name {instance_suffix} -print -quit 2>/dev/null) && "
4078
4167
  'if [ -n "$instance_dir" ]; then '
4079
- 'relative=${instance_dir#"$project_dir"/}; '
4080
- 'tar cz -C "$project_dir" "$relative" 2>/dev/null | base64; fi'
4168
+ 'root=$(dirname "$(dirname "$instance_dir")"); '
4169
+ 'relative=${instance_dir#"$root"/}; '
4170
+ 'tar cz -C "$root" "$relative" 2>/dev/null | base64; fi'
4081
4171
  )
4082
4172
  result = _ota_run(peer, script, label=f"{peer.name}: collect session-instances", dry_run=dry_run, check=False)
4083
- if dry_run or not result.stdout.strip():
4173
+ if dry_run:
4174
+ continue
4175
+ if not result.stdout.strip():
4176
+ # Silence here used to end as "no status/events.jsonl files found"
4177
+ # after the run was already over, which describes the symptom and
4178
+ # not the cause. Say which peer, and where it was looked for.
4179
+ print(
4180
+ f"OTA smoke: peer {peer.name} returned no session instance matching "
4181
+ f"*_{instance.instance_id} under its {plan.instances_rel!r} "
4182
+ f"(relative to {plan.project!r}).",
4183
+ file=sys.stderr,
4184
+ )
4084
4185
  continue
4085
4186
  _ota_extract_peer_artifacts(instance, peer, result.stdout)
4187
+ collected += 1
4188
+ if not dry_run and not collected:
4189
+ print(
4190
+ "OTA smoke: nothing was collected from any peer -- the run produced no local records. "
4191
+ "Check that the project's session_instances_dir is the one the peers write to.",
4192
+ file=sys.stderr,
4193
+ )
4086
4194
 
4087
4195
 
4088
4196
  def _ota_extract_peer_artifacts(instance: SessionInstance, peer: OtaSmokePeer, encoded: str) -> None:
@@ -7372,9 +7480,30 @@ def _smoke_topic_pub_qos_args(qos: dict[str, Any] | None) -> str:
7372
7480
  return " ".join(shlex.quote(arg) for arg in args) + (" " if args else "")
7373
7481
 
7374
7482
 
7375
- def _smoke_publisher_command(spec: SmokeTopicSpec, ros_setup: str, duration: float) -> str:
7483
+ def _smoke_publisher_command(
7484
+ spec: SmokeTopicSpec,
7485
+ ros_setup: str,
7486
+ duration: float,
7487
+ load: Mapping[str, Any] | None = None,
7488
+ ) -> str:
7489
+ """The publisher command for one crossed topic.
7490
+
7491
+ `load` overrides what the session's own `expect` says, and only for a
7492
+ `SizedPayload` stream: a benchmark chooses its load, and a session that also
7493
+ declares one is describing what it expects to see rather than what a
7494
+ measurement should offer. Without this the benchmark's `--size` and
7495
+ `--rate-hz` reached the local runner and stopped there, so a two-host run
7496
+ measured the session's defaults while reporting the load that was asked for.
7497
+ """
7376
7498
  assert spec.publish_topic is not None and spec.publish_type is not None
7377
7499
  if spec.publish_type == "com_msgs/msg/SizedPayload":
7500
+ from .cli_benchmark import _sized_publisher_param_args
7501
+
7502
+ if load:
7503
+ args = " ".join(
7504
+ shlex.quote(str(part)) for part in _sized_publisher_param_args(spec.publish_topic, dict(load))
7505
+ )
7506
+ return f"{ros_setup} && timeout {duration} ros2 run com_py sized_publisher --ros-args {args}"
7378
7507
  size = spec.expected_size or SMOKE_SIZED_PAYLOAD_BYTES
7379
7508
  return (
7380
7509
  f"{ros_setup} && timeout {duration} ros2 run com_py sized_publisher --ros-args "
@@ -7427,16 +7556,18 @@ def _start_smoke_topic_publishers(
7427
7556
  log_line: Callable[[str], None] = print,
7428
7557
  duration: float = 180.0,
7429
7558
  source_peer_key: str | None = None,
7559
+ load: Mapping[str, Any] | None = None,
7430
7560
  ) -> list[SmokeTopicSpec]:
7431
7561
  started: list[SmokeTopicSpec] = []
7432
7562
  for spec in _smoke_publish_specs(cfg, source_peer_key=source_peer_key):
7433
7563
  assert spec.publish_topic is not None and spec.publish_type is not None
7434
7564
  container = containers[spec.source_peer_key]
7435
7565
  ros_setup = ros_setups[spec.source_peer_key]
7436
- cmd = _smoke_publisher_command(spec, ros_setup, duration)
7566
+ cmd = _smoke_publisher_command(spec, ros_setup, duration, load)
7567
+ offered = f" at {load.get('rate', spec.publish_rate)} Hz (benchmark load)" if load else ""
7437
7568
  log_line(
7438
7569
  f"Starting smoke publisher {spec.source_peer_key}->{spec.receiver_peer_key} "
7439
- f"{spec.publish_topic} ({spec.publish_type}) in {container}"
7570
+ f"{spec.publish_topic} ({spec.publish_type}) in {container}{offered}"
7440
7571
  )
7441
7572
  subprocess.run(
7442
7573
  ["docker", "exec", "-d", container, "bash", "-c", cmd],
@@ -7801,6 +7932,30 @@ def probe_content_command(args: argparse.Namespace) -> int:
7801
7932
  return 0 if ok else 1
7802
7933
 
7803
7934
 
7935
+ def _publish_load_override(args: argparse.Namespace) -> dict[str, Any] | None:
7936
+ """The benchmark's load as `_sized_publisher_param_args` wants it, or None.
7937
+
7938
+ None means "use what the session expects", which is what an ordinary smoke
7939
+ run wants. A benchmark always passes something, because choosing the load is
7940
+ the whole point of it.
7941
+ """
7942
+ load: dict[str, Any] = {}
7943
+ if getattr(args, "load_size", None) is not None:
7944
+ load["size"] = int(args.load_size)
7945
+ load["size_a"] = int(args.load_size)
7946
+ if getattr(args, "load_size_pattern", None):
7947
+ load["pattern"] = args.load_size_pattern
7948
+ if getattr(args, "load_rate_hz", None) is not None:
7949
+ load["rate"] = float(args.load_rate_hz)
7950
+ if getattr(args, "load_streams", None) is not None:
7951
+ load["streams"] = int(args.load_streams)
7952
+ if getattr(args, "load_interval_jitter_ms", None) is not None:
7953
+ load["interval_jitter_ms"] = float(args.load_interval_jitter_ms)
7954
+ if getattr(args, "load_interval_jitter_seed", None) is not None:
7955
+ load["interval_jitter_seed"] = int(args.load_interval_jitter_seed)
7956
+ return load or None
7957
+
7958
+
7804
7959
  def publish_test_topics_command(args: argparse.Namespace) -> int:
7805
7960
  """Start or stop synthetic local app publishers for the peer's crossed topics.
7806
7961
 
@@ -7826,6 +7981,7 @@ def publish_test_topics_command(args: argparse.Namespace) -> int:
7826
7981
  cfg,
7827
7982
  duration=args.duration,
7828
7983
  source_peer_key=args.identity,
7984
+ load=_publish_load_override(args),
7829
7985
  )
7830
7986
  except RuntimeError as exc:
7831
7987
  print(f"ERROR: {exc}", file=sys.stderr)
@@ -10357,6 +10513,16 @@ def main(argv: list[str] | None = None) -> int:
10357
10513
  publish_test_topics_parser.add_argument(
10358
10514
  "--stop", action="store_true", help="Stop running synthetic test topic publishers."
10359
10515
  )
10516
+ # The benchmark's load, for the peer that publishes it. Without these a
10517
+ # two-host benchmark offered the session's own expected load and reported
10518
+ # the one that was asked for -- a run that reads as a success and measures
10519
+ # something else.
10520
+ publish_test_topics_parser.add_argument("--load-size", type=int, help=argparse.SUPPRESS)
10521
+ publish_test_topics_parser.add_argument("--load-size-pattern", help=argparse.SUPPRESS)
10522
+ publish_test_topics_parser.add_argument("--load-rate-hz", type=float, help=argparse.SUPPRESS)
10523
+ publish_test_topics_parser.add_argument("--load-streams", type=int, help=argparse.SUPPRESS)
10524
+ publish_test_topics_parser.add_argument("--load-interval-jitter-ms", type=float, help=argparse.SUPPRESS)
10525
+ publish_test_topics_parser.add_argument("--load-interval-jitter-seed", type=int, help=argparse.SUPPRESS)
10360
10526
  publish_test_topics_parser.set_defaults(func=publish_test_topics_command)
10361
10527
 
10362
10528
  list_parser = subparsers.add_parser("list-sessions", help="List configured sessions.")
@@ -5163,6 +5163,7 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
5163
5163
  _peer_command_runner,
5164
5164
  _peer_watchdog_launcher,
5165
5165
  _profile_directions,
5166
+ _profile_directions_for,
5166
5167
  _remove_smoke_network,
5167
5168
  _resolve_ota_profile,
5168
5169
  _resolve_ota_smoke_context,
@@ -5327,7 +5328,10 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
5327
5328
  _ota_start_peers(target, plan, instance.instance_id, dry_run=dry_run)
5328
5329
  if not dry_run:
5329
5330
  time.sleep(12)
5330
- _ota_start_session_publishers(target, plan, dry_run=dry_run)
5331
+ # The load is the benchmark's, not the session's expectation:
5332
+ # without this the peer published what its own `expect` declares
5333
+ # and the run reported the load that was asked for.
5334
+ _ota_start_session_publishers(target, plan, dry_run=dry_run, load=load)
5331
5335
 
5332
5336
  if not dry_run:
5333
5337
  if profile_obj is not None and profile_obj.is_timeline:
@@ -5651,34 +5655,36 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
5651
5655
  print(
5652
5656
  f" netem distribution {table_name!r} installed in {dist_container}: {dist_target}"
5653
5657
  )
5658
+ # Which peer's egress carries `uplink` is the session's
5659
+ # answer, not the runner's: the same session under the same
5660
+ # profile must be shaped identically here and across two
5661
+ # machines, and hard-coding a=uplink here while the OTA path
5662
+ # hard-coded b=uplink meant it was not.
5663
+ lab_directions = _profile_directions_for(["a", "b"], cfg)
5664
+ lab_containers = {"a": a_container, "b": b_container}
5654
5665
  if profile_obj.is_timeline:
5655
- # Peer A shapes uplink (egress to B)
5656
- shaper_a = ProfileShaper("eth0", make_container_runner(a_container))
5657
- lab_shapers.append(shaper_a)
5658
- steps_a = expand_timeline(profile_obj, "eth0", direction="uplink")
5659
- lab_peer_steps["a"] = (shaper_a, steps_a)
5660
-
5661
- # Peer B shapes downlink (egress to A)
5662
- shaper_b = ProfileShaper("eth0", make_container_runner(b_container))
5663
- lab_shapers.append(shaper_b)
5664
- steps_b = expand_timeline(profile_obj, "eth0", direction="downlink")
5665
- lab_peer_steps["b"] = (shaper_b, steps_b)
5666
+ for peer_name in ("a", "b"):
5667
+ shaper = ProfileShaper("eth0", make_container_runner(lab_containers[peer_name]))
5668
+ lab_shapers.append(shaper)
5669
+ steps = expand_timeline(profile_obj, "eth0", direction=lab_directions[peer_name])
5670
+ lab_peer_steps[peer_name] = (shaper, steps)
5666
5671
 
5667
5672
  for shaper in lab_shapers:
5668
5673
  shaper.arm([])
5669
5674
  else:
5670
- if profile_obj.uplink and not profile_obj.uplink.is_empty:
5671
- shaper_a = ProfileShaper("eth0", make_container_runner(a_container))
5672
- lab_shapers.append(shaper_a)
5673
- shaper_a.arm(shaping_commands("eth0", profile_obj.uplink))
5674
- if profile_obj.downlink and not profile_obj.downlink.is_empty:
5675
- shaper_b = ProfileShaper("eth0", make_container_runner(b_container))
5676
- lab_shapers.append(shaper_b)
5677
- shaper_b.arm(shaping_commands("eth0", profile_obj.downlink))
5675
+ for peer_name in ("a", "b"):
5676
+ leg = profile_obj.uplink if lab_directions[peer_name] == "uplink" else profile_obj.downlink
5677
+ if leg is None or leg.is_empty:
5678
+ continue
5679
+ shaper = ProfileShaper("eth0", make_container_runner(lab_containers[peer_name]))
5680
+ lab_shapers.append(shaper)
5681
+ shaper.arm(shaping_commands("eth0", leg))
5678
5682
 
5679
5683
  # 4. Verify shaping was applied (diagnostic)
5680
5684
  if profile_obj is not None and not getattr(args, "dry_run", False):
5681
- for label, container in [("A (uplink)", a_container), ("B (downlink)", b_container)]:
5685
+ for peer_name in ("a", "b"):
5686
+ label = f"{peer_name.upper()} ({lab_directions[peer_name]})"
5687
+ container = lab_containers[peer_name]
5682
5688
  tc_binary = container_tc_overrides.get(container, "tc")
5683
5689
  res = subprocess.run(
5684
5690
  ["docker", "exec", "-u", "root", container, tc_binary, "qdisc", "show", "dev", "eth0"],
@@ -245,3 +245,74 @@ profiles:
245
245
  distribution_file: dist/field-20260817.dist
246
246
  loss: 1%
247
247
  downlink: { delay: 7ms, jitter: 5ms }
248
+
249
+ # B1-prime -- the two-state process with its chain rescaled per cell.
250
+ #
251
+ # netem advances a Gilbert-Elliott chain once per *packet*, so the fitted p
252
+ # and r describe a process in packets and not in time. At the drive's own
253
+ # 380 fragments/s they mean a bad run of about 105 ms; on a size ladder at a
254
+ # fixed 10 Hz the packet rate is N x 10, so the same p and r stretch the bad
255
+ # run by 380/(N x 10) and every cell meets a different link. Forcing the
256
+ # packet rate constant instead was tried and measured the wrong thing: the
257
+ # pipeline serves messages, not packets, and at 60 messages/s the depth-1 OTA
258
+ # queue overwrites its own backlog -- both loss processes then reported
259
+ # 10.7 % to two digits.
260
+ #
261
+ # So the chain is rescaled instead: p and r are multiplied by 380/(N x 10) in
262
+ # each cell, which leaves the stationary bad fraction at 3.92 % and the mean
263
+ # loss at 1 % while making the bad run 105 ms of wall clock everywhere. The
264
+ # comparison against `tilt-iid` is then a comparison of one thing.
265
+
266
+ bp-frag-one:
267
+ uplink:
268
+ delay: 25ms
269
+ jitter: 18ms
270
+ distribution: field-20260817
271
+ distribution_file: dist/field-20260817.dist
272
+ loss_gemodel: { p: 3.876%, r: 95%, loss_bad: 13.5%, loss_good: 0.49% }
273
+ downlink: { delay: 7ms, jitter: 5ms }
274
+
275
+ bp-frag-two:
276
+ uplink:
277
+ delay: 25ms
278
+ jitter: 18ms
279
+ distribution: field-20260817
280
+ distribution_file: dist/field-20260817.dist
281
+ loss_gemodel: { p: 1.938%, r: 47.5%, loss_bad: 13.5%, loss_good: 0.49% }
282
+ downlink: { delay: 7ms, jitter: 5ms }
283
+
284
+ bp-frag-four:
285
+ uplink:
286
+ delay: 25ms
287
+ jitter: 18ms
288
+ distribution: field-20260817
289
+ distribution_file: dist/field-20260817.dist
290
+ loss_gemodel: { p: 0.969%, r: 23.75%, loss_bad: 13.5%, loss_good: 0.49% }
291
+ downlink: { delay: 7ms, jitter: 5ms }
292
+
293
+ bp-frag-six:
294
+ uplink:
295
+ delay: 25ms
296
+ jitter: 18ms
297
+ distribution: field-20260817
298
+ distribution_file: dist/field-20260817.dist
299
+ loss_gemodel: { p: 0.646%, r: 15.83%, loss_bad: 13.5%, loss_good: 0.49% }
300
+ downlink: { delay: 7ms, jitter: 5ms }
301
+
302
+ bp-frag-twelve:
303
+ uplink:
304
+ delay: 25ms
305
+ jitter: 18ms
306
+ distribution: field-20260817
307
+ distribution_file: dist/field-20260817.dist
308
+ loss_gemodel: { p: 0.323%, r: 7.917%, loss_bad: 13.5%, loss_good: 0.49% }
309
+ downlink: { delay: 7ms, jitter: 5ms }
310
+
311
+ bp-frag-thirtyeight:
312
+ uplink:
313
+ delay: 25ms
314
+ jitter: 18ms
315
+ distribution: field-20260817
316
+ distribution_file: dist/field-20260817.dist
317
+ loss_gemodel: { p: 0.102%, r: 2.5%, loss_bad: 13.5%, loss_good: 0.49% }
318
+ downlink: { delay: 7ms, jitter: 5ms }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev76
3
+ Version: 2.5.dev78
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -373,6 +373,7 @@ tests/unit/test_netem_dist.py
373
373
  tests/unit/test_network_preflight.py
374
374
  tests/unit/test_network_profiles.py
375
375
  tests/unit/test_network_shaper.py
376
+ tests/unit/test_ota_benchmark_peer_exec.py
376
377
  tests/unit/test_ota_link_configs.py
377
378
  tests/unit/test_ota_pcap.py
378
379
  tests/unit/test_ota_unwrapper.py
@@ -368,6 +368,7 @@
368
368
  "tests/unit/test_network_preflight.py",
369
369
  "tests/unit/test_network_profiles.py",
370
370
  "tests/unit/test_network_shaper.py",
371
+ "tests/unit/test_ota_benchmark_peer_exec.py",
371
372
  "tests/unit/test_ota_link_configs.py",
372
373
  "tests/unit/test_ota_pcap.py",
373
374
  "tests/unit/test_ota_unwrapper.py",