rosotacom-dev 2.5.dev41__tar.gz → 2.5.dev42__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 (354) hide show
  1. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0004-network-profiles.md +44 -7
  3. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/cli_benchmark.py +24 -0
  4. rosotacom_dev-2.5.dev42/src/rosotacom/netem_dist.py +104 -0
  5. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/network_profiles.py +162 -17
  6. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  7. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/SOURCES.txt +2 -0
  8. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/scm_file_list.json +2 -0
  9. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  10. rosotacom_dev-2.5.dev42/tests/unit/test_netem_dist.py +62 -0
  11. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_network_profiles.py +87 -0
  12. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.devcontainer/devcontainer.json.template +0 -0
  13. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  14. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/CODEOWNERS +0 -0
  15. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  16. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  17. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  18. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  19. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  20. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  21. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  22. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  23. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/actions/setup-just/action.yml +0 -0
  25. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/dependabot.yml +0 -0
  26. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/benchmark-calibrate.yml +0 -0
  27. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/benchmark-gate.yml +0 -0
  28. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/dev-release.yml +0 -0
  29. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/image-scan.yml +0 -0
  30. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/nightly-e2e.yml +0 -0
  31. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/pr-lightweight.yml +0 -0
  32. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/pr-merge-gate.yml +0 -0
  33. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/release.yml +0 -0
  34. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.pre-commit-config.yaml +0 -0
  35. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/AGENTS.md +0 -0
  36. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/CONCURRENCY.md +0 -0
  37. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/CONTRIBUTING.md +0 -0
  38. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/DEVELOPMENT_PRINCIPLES.md +0 -0
  39. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/LICENSE.txt +0 -0
  40. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/MANIFEST.in +0 -0
  41. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/README.md +0 -0
  42. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/budgets.jsonl +0 -0
  43. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/README.md +0 -0
  44. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/capacity-size-tight-cyclone.json +0 -0
  45. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  46. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-nominal-cyclone.json +0 -0
  47. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-tight-cyclone.json +0 -0
  48. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-tight-fastdds.json +0 -0
  49. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-tight-zenoh.json +0 -0
  50. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/codecov.yml +0 -0
  51. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/deployment-configuration.md +0 -0
  52. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/benchmark-ab.md +0 -0
  53. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/benchmark-bag-as-load.md +0 -0
  54. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/ci.md +0 -0
  55. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/ffmpeg-keyframes.md +0 -0
  56. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/README.md +0 -0
  57. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  58. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/delay-alone-no-loss.md +0 -0
  59. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/head-of-line-irregularity.md +0 -0
  60. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  61. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/lighter-message-loss.md +0 -0
  62. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  63. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/forensics-report.md +0 -0
  64. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/geo-link-map.md +0 -0
  65. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/github-repository-settings.md +0 -0
  66. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/link-bandwidth.md +0 -0
  67. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/link-trace.md +0 -0
  68. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/owner-runbook.md +0 -0
  69. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/performance-bands.md +0 -0
  70. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/playbooks/README.md +0 -0
  71. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/quality-model.md +0 -0
  72. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/TEMPLATE.md +0 -0
  73. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.1.md +0 -0
  74. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.2.md +0 -0
  75. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.3.md +0 -0
  76. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.4.md +0 -0
  77. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release.md +0 -0
  78. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  79. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0002-expectation-concepts.md +0 -0
  80. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0003-metric-backbone.md +0 -0
  81. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  82. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0006-dynamic-qos.md +0 -0
  83. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0007-regression-gate.md +0 -0
  84. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/README.md +0 -0
  85. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/testing.md +0 -0
  86. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/work-items.md +0 -0
  87. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/install.sh +0 -0
  88. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/justfile +0 -0
  89. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/control_center.png +0 -0
  90. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/echo_test.png +0 -0
  91. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/forensics-report-example.png +0 -0
  92. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/host.png +0 -0
  93. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/vehicle.png +0 -0
  94. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/pyproject.toml +0 -0
  95. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/requirements.txt +0 -0
  96. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/rosotacom.yaml +0 -0
  97. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/session-configuration.md +0 -0
  98. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/setup.cfg +0 -0
  99. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/__init__.py +0 -0
  100. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/__main__.py +0 -0
  101. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/anonymize.py +0 -0
  102. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/bag_ground_truth.py +0 -0
  103. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/benched_set.py +0 -0
  104. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/benchmark.py +0 -0
  105. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/benchmark_delta.py +0 -0
  106. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/bundle_check.py +0 -0
  107. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/cli.py +0 -0
  108. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/deployment.py +0 -0
  109. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/ffmpeg_packet.py +0 -0
  110. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/forensics.py +0 -0
  111. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/geomap.py +0 -0
  112. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/image_cache.py +0 -0
  113. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/network_shaper.py +0 -0
  114. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/plots.py +0 -0
  115. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/py.typed +0 -0
  116. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/benched-set.yaml +0 -0
  117. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/.gitignore +0 -0
  118. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/README.md +0 -0
  119. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/__init__.py +0 -0
  120. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  121. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  122. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  123. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  124. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  125. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  126. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  127. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  128. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  129. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  130. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  131. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  132. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  133. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  134. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  135. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  136. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  137. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  138. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  139. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  140. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  141. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  142. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  143. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  144. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  145. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  146. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  147. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  148. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  149. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  150. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  151. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  152. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  153. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  154. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  155. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  156. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  157. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  158. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  159. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  160. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  161. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  162. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  163. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  164. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  165. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  166. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  167. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  168. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  169. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  170. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  171. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  172. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  173. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  174. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  175. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  176. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  177. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  178. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  179. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  180. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  181. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  182. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  183. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  184. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  185. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  186. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  187. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ros2docker.json.example +0 -0
  195. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  196. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  197. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  198. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  199. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  200. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  201. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  202. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  203. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  204. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  205. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  206. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  207. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  208. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  209. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  210. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  211. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  212. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  213. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  214. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  215. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  216. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  217. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  218. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  219. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  220. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  221. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  222. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  223. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  224. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  225. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  226. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  227. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  228. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  229. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  230. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  231. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  232. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  233. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  234. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  235. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  236. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  237. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  238. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  239. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  240. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  241. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  242. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  243. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  244. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  245. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  246. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  247. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  248. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  249. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  250. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  251. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  252. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  253. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  254. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  255. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  256. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  257. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  258. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  259. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  260. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  261. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  262. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  263. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  264. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  265. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  266. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  267. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  268. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  269. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  270. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  271. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  272. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  273. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  274. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
  275. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  276. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  277. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  278. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  279. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  280. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  281. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  282. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  283. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  284. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/status_eval.py +0 -0
  285. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/stream_stats.py +0 -0
  286. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/trace_profiles.py +0 -0
  287. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/transit.py +0 -0
  288. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/video_quality.py +0 -0
  289. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  290. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  291. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  292. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  293. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/terminology.md +0 -0
  294. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_benched_set_registry.py +0 -0
  295. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_findings.py +0 -0
  296. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_markdown_links.py +0 -0
  297. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_ota_source_bundle.py +0 -0
  298. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_package_metadata.py +0 -0
  299. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_packaged_resources.py +0 -0
  300. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  301. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_pytest_policy.py +0 -0
  302. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_readme_examples.py +0 -0
  303. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_security_maintenance_config.py +0 -0
  304. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_session_configuration_doc.py +0 -0
  305. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_session_template_substitution.py +0 -0
  306. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_workflow_contracts.py +0 -0
  307. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/conftest.py +0 -0
  308. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_anonymize_e2e.py +0 -0
  309. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_benchmark_ab.py +0 -0
  310. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_benchmark_capacity.py +0 -0
  311. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_benchmark_replay.py +0 -0
  312. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_parallel_smoke.py +0 -0
  313. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_smoke.py +0 -0
  314. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_timeline_stepping.py +0 -0
  315. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_video_quality_e2e.py +0 -0
  316. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/generate_rmw_matrix.py +0 -0
  317. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  318. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  319. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  320. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  321. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  322. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_anonymize.py +0 -0
  323. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_bag_ground_truth.py +0 -0
  324. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_benched_set.py +0 -0
  325. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_benchmark.py +0 -0
  326. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_benchmark_delta.py +0 -0
  327. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_bundle_check.py +0 -0
  328. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_catmux_pane_logging.py +0 -0
  329. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_cli.py +0 -0
  330. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_cli_benchmark.py +0 -0
  331. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_concurrency.py +0 -0
  332. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_deployment.py +0 -0
  333. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ffmpeg_packet.py +0 -0
  334. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_forensics.py +0 -0
  335. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_geomap.py +0 -0
  336. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_image_cache.py +0 -0
  337. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_link_bytes.py +0 -0
  338. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_link_trace.py +0 -0
  339. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_network_shaper.py +0 -0
  340. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ota_unwrapper.py +0 -0
  341. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ota_wrapper.py +0 -0
  342. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ota_xml.py +0 -0
  343. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_plots.py +0 -0
  344. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_qos_fallback_logging.py +0 -0
  345. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_relay_optional_parameters.py +0 -0
  346. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_session_pipeline.py +0 -0
  347. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_stage_latency.py +0 -0
  348. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_status_eval.py +0 -0
  349. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_status_overview.py +0 -0
  350. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_status_overview_observer.py +0 -0
  351. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_stream_stats.py +0 -0
  352. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_trace_profiles.py +0 -0
  353. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_transit.py +0 -0
  354. {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/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.dev41
3
+ Version: 2.5.dev42
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -91,11 +91,24 @@ profiles:
91
91
  - { for: 40s, uplink: { rate: 2.5mbit, delay: 90ms, loss: 1% } }
92
92
  ```
93
93
 
94
- - **static** — constant `{rate, delay, jitter, distribution, loss,
95
- loss_correlation, reorder, duplicate, seed}` per direction. `loss_correlation`
96
- (netem state/`gemodel`) makes loss bursty rather than independent — closer to
97
- real radio loss. `seed` maps to `tc netem seed SEED` when supported, making the
98
- generated random delay/loss draw replayable.
94
+ - **static** — constant `{rate, delay, jitter, distribution, distribution_file,
95
+ loss, loss_correlation, loss_gemodel, reorder, duplicate, seed}` per
96
+ direction. Two loss models, mutually exclusive per direction:
97
+ `loss` (+ optional `loss_correlation`) is netem's independent/correlated
98
+ per-packet loss; `loss_gemodel: {p, r, loss_bad, loss_good}` is the
99
+ Gilbert–Elliott state model (`tc netem loss gemodel`), the vocabulary a real
100
+ cellular link needs — rare transitions into a short intense bad state
101
+ (`p`/`r` per packet) with distinct loss probabilities inside (`loss_bad`)
102
+ and outside (`loss_good`) of it. The 2026-08-17 CCNG field fit is the
103
+ reference: `p 0.12%, r 2.9%, loss_bad 2.7%, loss_good 0.098%` reproduces
104
+ 113 ms bad states every ~2.8 s. `distribution` accepts the iproute2
105
+ builtins (`normal|pareto|paretonormal`) or a custom table name whose
106
+ `distribution_file` (a `.dist` built by `python -m rosotacom.netem_dist`
107
+ from measured delay samples) the runner installs into the shaping netns as
108
+ `/usr/lib/tc/<name>.dist` — because a field link's delay is
109
+ lognormal-tailed and `normal` under-tails p99 by ~2x. `seed` maps to
110
+ `tc netem seed SEED` when supported, making the generated random
111
+ delay/loss draw replayable.
99
112
  - **timeline** — an ordered list of `{ for: <dur>, <static-params> | outage }`
100
113
  segments. A recovery benchmark (RFC 0005) *is* a timeline profile; `outage` is
101
114
  the step whose recovery is measured.
@@ -189,8 +202,11 @@ RFC 0002's `calibrate` / `--suggest` machinery, **per profile**:
189
202
  the fluctuation `netem` does not reproduce by default. Emulation buys
190
203
  **reproducibility, not truth** — rung 4 (real cellular) calibrates the
191
204
  *shape* of the emulated profile; the profile then gives repeatable gating.
192
- - **Bursty loss needs the state model.** Default `netem loss` is independent; use
193
- `loss_correlation` / `gemodel` to approximate real burst loss.
205
+ - **Bursty loss needs the state model.** Default `netem loss` is independent; the
206
+ 2-arg `loss p% corr%` cannot express short-bad/long-good either. `loss_gemodel`
207
+ carries the Gilbert–Elliott parameters directly (see the schema section); fit
208
+ `p`/`r` from the measured bad/good run lengths (per packet) and the two loss
209
+ probabilities from the loss rate inside/outside bad states.
194
210
  - **Outage semantics are a choice.** `netem loss 100%` keeps the interface up (DDS
195
211
  endpoints survive, recovery is "catch up") vs link-down (forces RMW
196
212
  re-discovery, a harsher recovery). They test different recoveries — see Open
@@ -215,6 +231,12 @@ risk — a stuck `qdisc` silently corrupts every later result on the machine.
215
231
  `profiles.yaml` referenced from `rosotacom.yaml`), and resolve selection via
216
232
  `--profile <name>` / `shared.profile` / `none` (`resolve_profile_selection`,
217
233
  `cli._resolve_active_profile`).
234
+ - [x] Extend the loss/delay vocabulary to what field links actually need
235
+ (issue #278): `loss_gemodel` (Gilbert–Elliott state loss, exclusive with
236
+ `loss`) and custom delay-distribution tables (`distribution` beyond the
237
+ builtins + `distribution_file`, generated from measured samples by
238
+ `rosotacom.netem_dist`, installed into the shaping netns by the lab runner
239
+ before arming).
218
240
  - [x] Arm a named static profile on the OTA-interface egress with fail-safe
219
241
  teardown (revert on stop, on error, and via a safety max-duration watchdog);
220
242
  target the data interface only, never the SSH/control interface
@@ -268,6 +290,21 @@ off during implementation). Notes whether automation is feasible; privileged
268
290
  for a given profile/direction (rate/delay/jitter/loss/correlation → `tbf`+`netem`
269
291
  string), without touching a real interface (`test_shaping_commands_*`,
270
292
  `test_netem_arg_ordering_is_valid`). Done.
293
+ - [x] **Gilbert–Elliott loss (`loss_gemodel`)** — host unit tests on the schema
294
+ (required/unknown keys, exclusivity against `loss`) and on the argv (all four
295
+ probabilities always emitted): `test_gemodel_parse_and_exclusivity`,
296
+ `test_gemodel_argv_emits_all_four_probabilities`. The *statistical* fidelity of
297
+ the emitted process (bad-run length, in-state loss rates) stays a bench check
298
+ against a field fit — the 2026-08-17 CCNG fragment replay is the reference.
299
+ - [x] **Custom delay-distribution tables** — host unit tests: a non-builtin
300
+ `distribution` requires `distribution_file` and resolves relative to the
301
+ profiles file (`test_custom_distribution_requires_a_file_and_builtin_does_not`,
302
+ `test_load_profiles_file_resolves_distribution_files_and_lists_installs`);
303
+ table generation is normalized, monotonic, tail-preserving
304
+ (`test_netem_dist.py`). The lab runner installs required tables into the
305
+ shaping netns before arming (`required_dist_installs` consumed in
306
+ `cli_benchmark`); that a netem referencing the installed name actually loads
307
+ it is part of the same bench check as arming itself.
271
308
  - [~] **Fail-safe teardown** (revert on stop, on error, on safety max-duration; the
272
309
  idempotent `tc qdisc del … root` always runs; data-interface-only, never the
273
310
  SSH/control interface) — host unit test on the teardown/targeting logic done
@@ -5514,6 +5514,30 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
5514
5514
 
5515
5515
  # 3. Arm the network shapers (after discovery is complete)
5516
5516
  if profile_obj is not None:
5517
+ from .network_profiles import required_dist_installs
5518
+
5519
+ dist_installs = required_dist_installs(profile_obj)
5520
+ if dist_installs:
5521
+ # Custom delay-distribution tables must exist inside the
5522
+ # shaping netns before netem references them by name.
5523
+ for dist_container in (a_container, b_container):
5524
+ if not dist_container:
5525
+ continue
5526
+ for table_name, file_path in dist_installs:
5527
+ dist_target = f"/usr/lib/tc/{table_name}.dist"
5528
+ for install_cmd in (
5529
+ ["docker", "exec", "-u", "root", dist_container, "mkdir", "-p", "/usr/lib/tc"],
5530
+ ["docker", "cp", file_path, f"{dist_container}:{dist_target}"],
5531
+ ):
5532
+ res = subprocess.run(install_cmd, capture_output=True, text=True, check=False)
5533
+ if res.returncode != 0:
5534
+ raise RuntimeError(
5535
+ f"failed to install netem distribution table {table_name!r} in "
5536
+ f"{dist_container}: {' '.join(install_cmd)} -> {res.stderr or res.stdout}"
5537
+ )
5538
+ print(
5539
+ f" netem distribution {table_name!r} installed in {dist_container}: {dist_target}"
5540
+ )
5517
5541
  if profile_obj.is_timeline:
5518
5542
  # Peer A shapes uplink (egress to B)
5519
5543
  shaper_a = ProfileShaper("eth0", make_container_runner(a_container))
@@ -0,0 +1,104 @@
1
+ """Build netem delay-distribution tables (``/usr/lib/tc/*.dist``) from samples.
2
+
3
+ netem draws jitter as ``delay + jitter * table[uniform] / NETEM_DIST_SCALE``:
4
+ the ``distribution`` argument names a table of signed 16-bit values that encode
5
+ the *shape* (the inverse CDF of the normalized distribution, scaled by 8192),
6
+ while ``delay``/``jitter`` supply location and scale. iproute2 ships only
7
+ ``normal``, ``pareto`` and ``paretonormal``; a field-measured delay
8
+ distribution (e.g. the lognormal-tailed one-way delay of a cellular uplink,
9
+ which ``normal`` badly under-tails) becomes reproducible by generating its own
10
+ table from samples and installing it next to the builtin ones.
11
+
12
+ This is the same construction as iproute2's ``maketable``: sort the samples,
13
+ normalize to mean 0 / stddev 1, take :data:`TABLE_SIZE` inverse-CDF points,
14
+ scale by :data:`NETEM_DIST_SCALE` and clamp to int16.
15
+
16
+ Usage (module CLI)::
17
+
18
+ python -m rosotacom.netem_dist samples.csv --column delay_ms --out field.dist
19
+
20
+ The output installs as ``/usr/lib/tc/field.dist`` and is then addressable as
21
+ ``distribution: field`` (with ``distribution_file`` pointing at the file, so
22
+ profile runners install it into the shaping netns themselves).
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import csv
29
+ import math
30
+ from collections.abc import Sequence
31
+ from pathlib import Path
32
+
33
+ NETEM_DIST_SCALE = 8192 # kernel: delay = mu + sigma * table[r] / 8192
34
+ TABLE_SIZE = 4096 # entries in the shipped iproute2 tables
35
+ _INT16_MIN, _INT16_MAX = -32768, 32767
36
+
37
+
38
+ def build_dist_table(samples: Sequence[float], *, table_size: int = TABLE_SIZE) -> list[int]:
39
+ """Inverse-CDF table of the normalized ``samples`` (int16, netem scaling)."""
40
+ values = sorted(float(x) for x in samples if math.isfinite(float(x)))
41
+ if len(values) < 100:
42
+ raise ValueError(f"need at least 100 finite samples for a stable table, got {len(values)}")
43
+ mean = sum(values) / len(values)
44
+ var = sum((x - mean) ** 2 for x in values) / len(values)
45
+ sigma = math.sqrt(var)
46
+ if sigma <= 0:
47
+ raise ValueError("samples have zero spread; a distribution table is meaningless")
48
+ table: list[int] = []
49
+ n = len(values)
50
+ for i in range(table_size):
51
+ # midpoint quantile of bucket i, linearly interpolated in the sorted samples
52
+ q = (i + 0.5) / table_size
53
+ pos = q * (n - 1)
54
+ lo = int(pos)
55
+ frac = pos - lo
56
+ x = values[lo] if lo + 1 >= n else values[lo] * (1 - frac) + values[lo + 1] * frac
57
+ z = (x - mean) / sigma
58
+ table.append(max(_INT16_MIN, min(_INT16_MAX, round(z * NETEM_DIST_SCALE))))
59
+ return table
60
+
61
+
62
+ def format_dist_file(table: Sequence[int], *, comment: str | None = None) -> str:
63
+ """Render a table in the ``/usr/lib/tc/*.dist`` text format (8 values/line)."""
64
+ lines = [f"# {line}" for line in (comment or "").splitlines() if line]
65
+ for i in range(0, len(table), 8):
66
+ lines.append(" ".join(f"{v:d}" for v in table[i : i + 8]))
67
+ return "\n".join(lines) + "\n"
68
+
69
+
70
+ def read_samples_csv(path: str | Path, column: str) -> list[float]:
71
+ out: list[float] = []
72
+ with open(path, newline="", encoding="utf-8") as fp:
73
+ reader = csv.DictReader(fp)
74
+ if reader.fieldnames is None or column not in reader.fieldnames:
75
+ raise ValueError(f"{path}: no column {column!r}; columns: {reader.fieldnames}")
76
+ for row in reader:
77
+ text = (row.get(column) or "").strip()
78
+ if text:
79
+ try:
80
+ out.append(float(text))
81
+ except ValueError:
82
+ continue
83
+ return out
84
+
85
+
86
+ def main(argv: Sequence[str] | None = None) -> int:
87
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
88
+ parser.add_argument("samples_csv", help="CSV with one numeric column of delay samples")
89
+ parser.add_argument("--column", default="delay_ms", help="column to read (default: delay_ms)")
90
+ parser.add_argument("--out", required=True, help="output .dist path (basename = table name)")
91
+ parser.add_argument("--comment", default=None, help="provenance line(s) for the file header")
92
+ args = parser.parse_args(argv)
93
+ samples = read_samples_csv(args.samples_csv, args.column)
94
+ table = build_dist_table(samples)
95
+ comment = args.comment or (
96
+ f"generated by rosotacom.netem_dist from {Path(args.samples_csv).name} ({len(samples)} samples)"
97
+ )
98
+ Path(args.out).write_text(format_dist_file(table, comment=comment), encoding="utf-8")
99
+ print(f"wrote {args.out}: {len(table)} entries from {len(samples)} samples")
100
+ return 0
101
+
102
+
103
+ if __name__ == "__main__":
104
+ raise SystemExit(main())
@@ -136,6 +136,38 @@ def parse_seed(value: Any, what: str = "seed") -> int:
136
136
  # Schema
137
137
  # --------------------------------------------------------------------------- #
138
138
 
139
+ # Distribution tables iproute2 ships; anything else needs a ``distribution_file``
140
+ # that the runner installs into the shaping netns as ``/usr/lib/tc/<name>.dist``.
141
+ _BUILTIN_DISTRIBUTIONS = ("normal", "pareto", "paretonormal")
142
+ _DISTRIBUTION_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
143
+
144
+
145
+ @dataclass(frozen=True)
146
+ class GemodelLoss:
147
+ """netem ``loss gemodel`` — the Gilbert(-Elliott) two-state loss process.
148
+
149
+ Per-*packet* state transitions (netem evaluates the chain once per packet):
150
+ ``p_pct`` = P(good→bad), ``r_pct`` = P(bad→good), ``loss_bad_pct`` = loss
151
+ probability while bad (netem's ``1-h`` argument), ``loss_good_pct`` = loss
152
+ probability while good (netem's ``1-k``). All four are always emitted, so the
153
+ argv never depends on iproute2 defaults. This is the vocabulary the plain
154
+ 2-arg ``loss p% corr%`` cannot express: short intense bad states inside long
155
+ clean good states, as measured on real cellular links.
156
+ """
157
+
158
+ p_pct: float
159
+ r_pct: float
160
+ loss_bad_pct: float = 100.0
161
+ loss_good_pct: float = 0.0
162
+
163
+ def __post_init__(self) -> None:
164
+ for name in ("p_pct", "r_pct", "loss_bad_pct", "loss_good_pct"):
165
+ value = getattr(self, name)
166
+ if not 0.0 <= value <= 100.0:
167
+ raise ValueError(f"gemodel {name} must be within [0, 100]%, got {value!r}")
168
+ if self.p_pct == 0.0 and self.loss_good_pct == 0.0:
169
+ raise ValueError("gemodel with p=0 and loss_good=0 never loses anything; drop the block instead")
170
+
139
171
 
140
172
  @dataclass(frozen=True)
141
173
  class DirectionShaping:
@@ -145,9 +177,11 @@ class DirectionShaping:
145
177
  rate_bps: float | None = None
146
178
  delay_ms: float | None = None
147
179
  jitter_ms: float | None = None
148
- distribution: str | None = None # normal | pareto | paretonormal (needs jitter)
180
+ distribution: str | None = None # builtin (normal|pareto|paretonormal) or a custom table name
181
+ distribution_file: str | None = None # .dist file for a custom table (installed as /usr/lib/tc/<name>.dist)
149
182
  loss_pct: float | None = None
150
183
  loss_correlation_pct: float | None = None # netem correlated loss (needs loss)
184
+ loss_gemodel: GemodelLoss | None = None # netem Gilbert-Elliott loss (exclusive with loss)
151
185
  reorder_pct: float | None = None # netem reorder (needs delay)
152
186
  duplicate_pct: float | None = None
153
187
  seed: int | None = None
@@ -156,23 +190,29 @@ class DirectionShaping:
156
190
 
157
191
  @property
158
192
  def is_empty(self) -> bool:
159
- return all(
160
- getattr(self, name) is None
161
- for name in (
162
- "rate_bps",
163
- "delay_ms",
164
- "jitter_ms",
165
- "loss_pct",
166
- "reorder_pct",
167
- "duplicate_pct",
193
+ return (
194
+ all(
195
+ getattr(self, name) is None
196
+ for name in (
197
+ "rate_bps",
198
+ "delay_ms",
199
+ "jitter_ms",
200
+ "loss_pct",
201
+ "reorder_pct",
202
+ "duplicate_pct",
203
+ )
168
204
  )
205
+ and self.loss_gemodel is None
169
206
  )
170
207
 
171
208
  @property
172
209
  def has_netem(self) -> bool:
173
- return any(
174
- getattr(self, name) is not None
175
- for name in ("delay_ms", "jitter_ms", "loss_pct", "reorder_pct", "duplicate_pct")
210
+ return (
211
+ any(
212
+ getattr(self, name) is not None
213
+ for name in ("delay_ms", "jitter_ms", "loss_pct", "reorder_pct", "duplicate_pct")
214
+ )
215
+ or self.loss_gemodel is not None
176
216
  )
177
217
 
178
218
  def __post_init__(self) -> None:
@@ -182,10 +222,21 @@ class DirectionShaping:
182
222
  raise ValueError("distribution requires a jitter")
183
223
  if self.loss_correlation_pct is not None and self.loss_pct is None:
184
224
  raise ValueError("loss_correlation requires a loss")
225
+ if self.loss_gemodel is not None and self.loss_pct is not None:
226
+ raise ValueError("loss_gemodel and loss are exclusive: one loss model per direction")
185
227
  if self.reorder_pct is not None and self.delay_ms is None:
186
228
  raise ValueError("reorder requires a delay (netem reorders within the delay window)")
187
- if self.distribution is not None and self.distribution not in ("normal", "pareto", "paretonormal"):
188
- raise ValueError(f"unsupported distribution {self.distribution!r}")
229
+ if self.distribution is not None:
230
+ if not _DISTRIBUTION_NAME_RE.fullmatch(self.distribution):
231
+ raise ValueError(f"unsupported distribution name {self.distribution!r}")
232
+ if self.distribution not in _BUILTIN_DISTRIBUTIONS and self.distribution_file is None:
233
+ raise ValueError(
234
+ f"distribution {self.distribution!r} is not built into iproute2 "
235
+ f"({', '.join(_BUILTIN_DISTRIBUTIONS)}); give distribution_file so the "
236
+ "runner can install /usr/lib/tc/" + self.distribution + ".dist"
237
+ )
238
+ if self.distribution_file is not None and self.distribution is None:
239
+ raise ValueError("distribution_file requires a distribution name")
189
240
 
190
241
 
191
242
  @dataclass(frozen=True)
@@ -235,8 +286,10 @@ _DIRECTION_KEYS = frozenset(
235
286
  "delay",
236
287
  "jitter",
237
288
  "distribution",
289
+ "distribution_file",
238
290
  "loss",
239
291
  "loss_correlation",
292
+ "loss_gemodel",
240
293
  "reorder",
241
294
  "duplicate",
242
295
  "seed",
@@ -245,6 +298,26 @@ _DIRECTION_KEYS = frozenset(
245
298
  }
246
299
  )
247
300
 
301
+ _GEMODEL_KEYS = frozenset({"p", "r", "loss_bad", "loss_good"})
302
+
303
+
304
+ def parse_gemodel(spec: Mapping[str, Any]) -> GemodelLoss:
305
+ """Parse a ``loss_gemodel`` block (``p``/``r`` required, loss_bad/loss_good optional)."""
306
+ if not isinstance(spec, Mapping):
307
+ raise ValueError(f"loss_gemodel: expected a mapping with p/r[/loss_bad/loss_good], got {spec!r}")
308
+ unknown = sorted(set(spec) - _GEMODEL_KEYS)
309
+ if unknown:
310
+ raise ValueError(f"unsupported loss_gemodel keys {unknown}; allowed: {sorted(_GEMODEL_KEYS)}")
311
+ missing = sorted({"p", "r"} - set(spec))
312
+ if missing:
313
+ raise ValueError(f"loss_gemodel requires {missing}")
314
+ return GemodelLoss(
315
+ p_pct=parse_pct(spec["p"], "loss_gemodel p"),
316
+ r_pct=parse_pct(spec["r"], "loss_gemodel r"),
317
+ loss_bad_pct=parse_pct(spec["loss_bad"], "loss_gemodel loss_bad") if "loss_bad" in spec else 100.0,
318
+ loss_good_pct=parse_pct(spec["loss_good"], "loss_gemodel loss_good") if "loss_good" in spec else 0.0,
319
+ )
320
+
248
321
 
249
322
  def parse_direction(spec: Mapping[str, Any] | None) -> DirectionShaping | None:
250
323
  """Parse one ``uplink``/``downlink`` block into a :class:`DirectionShaping`."""
@@ -258,10 +331,12 @@ def parse_direction(spec: Mapping[str, Any] | None) -> DirectionShaping | None:
258
331
  delay_ms=parse_ms(spec["delay"], "delay") if "delay" in spec else None,
259
332
  jitter_ms=parse_ms(spec["jitter"], "jitter") if "jitter" in spec else None,
260
333
  distribution=str(spec["distribution"]) if "distribution" in spec else None,
334
+ distribution_file=str(spec["distribution_file"]) if "distribution_file" in spec else None,
261
335
  loss_pct=parse_pct(spec["loss"], "loss") if "loss" in spec else None,
262
336
  loss_correlation_pct=(
263
337
  parse_pct(spec["loss_correlation"], "loss_correlation") if "loss_correlation" in spec else None
264
338
  ),
339
+ loss_gemodel=parse_gemodel(spec["loss_gemodel"]) if "loss_gemodel" in spec else None,
265
340
  reorder_pct=parse_pct(spec["reorder"], "reorder") if "reorder" in spec else None,
266
341
  duplicate_pct=parse_pct(spec["duplicate"], "duplicate") if "duplicate" in spec else None,
267
342
  seed=parse_seed(spec["seed"], "seed") if "seed" in spec else None,
@@ -330,13 +405,71 @@ def parse_profiles(doc: Mapping[str, Any]) -> dict[str, Profile]:
330
405
 
331
406
 
332
407
  def load_profiles_file(path: str | Path) -> dict[str, Profile]:
333
- """Load a project-scoped ``profiles.yaml`` into ``{name: Profile}``."""
408
+ """Load a project-scoped ``profiles.yaml`` into ``{name: Profile}``.
409
+
410
+ Relative ``distribution_file`` entries are resolved against the profiles
411
+ file's own directory, so a profiles file stays self-contained wherever the
412
+ project is checked out.
413
+ """
334
414
  import yaml
335
415
 
336
416
  doc = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
337
417
  if not isinstance(doc, Mapping):
338
418
  raise ValueError(f"{path}: profiles file must be a mapping (with a top-level 'profiles:' block)")
339
- return parse_profiles(doc)
419
+ profiles = parse_profiles(doc)
420
+ base = Path(path).resolve().parent
421
+ return {name: _resolve_distribution_files(profile, base) for name, profile in profiles.items()}
422
+
423
+
424
+ def _resolve_distribution_files(profile: Profile, base: Path) -> Profile:
425
+ from dataclasses import replace
426
+
427
+ def fix(shaping: DirectionShaping | None) -> DirectionShaping | None:
428
+ if shaping is None or shaping.distribution_file is None:
429
+ return shaping
430
+ file = Path(shaping.distribution_file)
431
+ if not file.is_absolute():
432
+ file = base / file
433
+ return replace(shaping, distribution_file=str(file))
434
+
435
+ if profile.is_timeline:
436
+ segments = tuple(
437
+ replace(segment, uplink=fix(segment.uplink), downlink=fix(segment.downlink)) for segment in profile.timeline
438
+ )
439
+ return replace(profile, timeline=segments)
440
+ return replace(profile, uplink=fix(profile.uplink), downlink=fix(profile.downlink))
441
+
442
+
443
+ def required_dist_installs(profile: Profile) -> list[tuple[str, str]]:
444
+ """``(table_name, file_path)`` pairs a runner must install as
445
+ ``/usr/lib/tc/<table_name>.dist`` inside the shaping netns before arming.
446
+
447
+ Builtin distributions need nothing; duplicates collapse; conflicting files
448
+ for one name are an error (two segments must mean the same table).
449
+ """
450
+ found: dict[str, str] = {}
451
+
452
+ def visit(shaping: DirectionShaping | None) -> None:
453
+ if shaping is None or shaping.distribution is None:
454
+ return
455
+ if shaping.distribution in _BUILTIN_DISTRIBUTIONS or shaping.distribution_file is None:
456
+ return
457
+ existing = found.get(shaping.distribution)
458
+ if existing is not None and existing != shaping.distribution_file:
459
+ raise ValueError(
460
+ f"distribution {shaping.distribution!r} maps to two files: {existing!r} and "
461
+ f"{shaping.distribution_file!r}"
462
+ )
463
+ found[shaping.distribution] = shaping.distribution_file
464
+
465
+ if profile.is_timeline:
466
+ for segment in profile.timeline:
467
+ visit(segment.uplink)
468
+ visit(segment.downlink)
469
+ else:
470
+ visit(profile.uplink)
471
+ visit(profile.downlink)
472
+ return sorted(found.items())
340
473
 
341
474
 
342
475
  # Selecting nothing — the unshaped rung (0/1) or the "no emulation" sentinel.
@@ -400,6 +533,18 @@ def _netem_args(shaping: DirectionShaping) -> list[str]:
400
533
  args += ["loss", _pct(shaping.loss_pct)]
401
534
  if shaping.loss_correlation_pct is not None:
402
535
  args.append(_pct(shaping.loss_correlation_pct))
536
+ if shaping.loss_gemodel is not None:
537
+ gem = shaping.loss_gemodel
538
+ # netem's positional names are p, r, 1-h, 1-k: the 3rd/4th arguments ARE
539
+ # the loss probabilities in bad/good state. All four always emitted.
540
+ args += [
541
+ "loss",
542
+ "gemodel",
543
+ _pct(gem.p_pct),
544
+ _pct(gem.r_pct),
545
+ _pct(gem.loss_bad_pct),
546
+ _pct(gem.loss_good_pct),
547
+ ]
403
548
  if shaping.duplicate_pct is not None:
404
549
  args += ["duplicate", _pct(shaping.duplicate_pct)]
405
550
  if shaping.reorder_pct is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev41
3
+ Version: 2.5.dev42
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -101,6 +101,7 @@ src/rosotacom/ffmpeg_packet.py
101
101
  src/rosotacom/forensics.py
102
102
  src/rosotacom/geomap.py
103
103
  src/rosotacom/image_cache.py
104
+ src/rosotacom/netem_dist.py
104
105
  src/rosotacom/network_profiles.py
105
106
  src/rosotacom/network_shaper.py
106
107
  src/rosotacom/plots.py
@@ -331,6 +332,7 @@ tests/unit/test_geomap.py
331
332
  tests/unit/test_image_cache.py
332
333
  tests/unit/test_link_bytes.py
333
334
  tests/unit/test_link_trace.py
335
+ tests/unit/test_netem_dist.py
334
336
  tests/unit/test_network_profiles.py
335
337
  tests/unit/test_network_shaper.py
336
338
  tests/unit/test_ota_unwrapper.py
@@ -103,6 +103,7 @@
103
103
  "src/rosotacom/forensics.py",
104
104
  "src/rosotacom/geomap.py",
105
105
  "src/rosotacom/image_cache.py",
106
+ "src/rosotacom/netem_dist.py",
106
107
  "src/rosotacom/network_profiles.py",
107
108
  "src/rosotacom/network_shaper.py",
108
109
  "src/rosotacom/plots.py",
@@ -326,6 +327,7 @@
326
327
  "tests/unit/test_image_cache.py",
327
328
  "tests/unit/test_link_bytes.py",
328
329
  "tests/unit/test_link_trace.py",
330
+ "tests/unit/test_netem_dist.py",
329
331
  "tests/unit/test_network_profiles.py",
330
332
  "tests/unit/test_network_shaper.py",
331
333
  "tests/unit/test_ota_unwrapper.py",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.4",
3
- "distance": 41,
4
- "node": "gbe160fa2bbb0b0fe47c4bdc3c64a7334ef1ddfef",
3
+ "distance": 42,
4
+ "node": "g3227daf2639025d14d15f043afdb0b78742f7142",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-18"
@@ -0,0 +1,62 @@
1
+ """netem distribution-table generation (rosotacom.netem_dist) — host tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import random
7
+
8
+ import pytest
9
+
10
+ from rosotacom.netem_dist import (
11
+ NETEM_DIST_SCALE,
12
+ TABLE_SIZE,
13
+ build_dist_table,
14
+ format_dist_file,
15
+ read_samples_csv,
16
+ )
17
+
18
+
19
+ def test_table_shape_and_normalization() -> None:
20
+ rng = random.Random(1)
21
+ samples = [rng.gauss(30.0, 10.0) for _ in range(20000)]
22
+ table = build_dist_table(samples)
23
+ assert len(table) == TABLE_SIZE
24
+ assert table == sorted(table) # inverse CDF is nondecreasing
25
+ mean = sum(table) / len(table)
26
+ std = math.sqrt(sum((v - mean) ** 2 for v in table) / len(table))
27
+ # normalized to mean 0 / std 1 in units of NETEM_DIST_SCALE
28
+ assert abs(mean) < 0.05 * NETEM_DIST_SCALE
29
+ assert abs(std - NETEM_DIST_SCALE) < 0.05 * NETEM_DIST_SCALE
30
+
31
+
32
+ def test_table_preserves_a_heavy_tail() -> None:
33
+ # A lognormal delay (field cellular links) must keep its asymmetry: the
34
+ # upper tail reaches much further from the median than the lower tail.
35
+ rng = random.Random(2)
36
+ samples = [math.exp(rng.gauss(3.3, 0.6)) for _ in range(20000)]
37
+ table = build_dist_table(samples)
38
+ p50 = table[TABLE_SIZE // 2]
39
+ upper = table[int(TABLE_SIZE * 0.999)] - p50
40
+ lower = p50 - table[int(TABLE_SIZE * 0.001)]
41
+ assert upper > 1.8 * lower
42
+
43
+
44
+ def test_rejects_degenerate_input() -> None:
45
+ with pytest.raises(ValueError):
46
+ build_dist_table([1.0] * 500) # zero spread
47
+ with pytest.raises(ValueError):
48
+ build_dist_table([1.0, 2.0]) # too few samples
49
+
50
+
51
+ def test_format_and_csv_roundtrip(tmp_path) -> None:
52
+ csv_path = tmp_path / "samples.csv"
53
+ csv_path.write_text("delay_ms\n" + "\n".join(str(10 + (i % 50)) for i in range(500)) + "\n", encoding="utf-8")
54
+ samples = read_samples_csv(csv_path, "delay_ms")
55
+ assert len(samples) == 500
56
+ text = format_dist_file(build_dist_table(samples), comment="prov line")
57
+ lines = text.strip().splitlines()
58
+ assert lines[0] == "# prov line"
59
+ values = [int(v) for line in lines[1:] for v in line.split()]
60
+ assert len(values) == TABLE_SIZE
61
+ with pytest.raises(ValueError):
62
+ read_samples_csv(csv_path, "missing_column")