rosotacom-dev 2.5.dev48__tar.gz → 2.5.dev50__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.
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0004-network-profiles.md +18 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/session-configuration.md +29 -7
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/cli.py +155 -8
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/cli_benchmark.py +30 -5
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +66 -4
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +72 -7
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/SOURCES.txt +1 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/scm_file_list.json +1 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
- rosotacom_dev-2.5.dev50/tests/unit/test_container_shaping.py +67 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_session_pipeline.py +73 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.devcontainer/devcontainer.json.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.devcontainer/re_generate_devcontainer_json.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/CODEOWNERS +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/actions/setup-just/action.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/dependabot.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/benchmark-calibrate.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/benchmark-gate.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/dev-release.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/image-scan.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/nightly-e2e.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/pr-lightweight.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/pr-merge-gate.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.github/workflows/release.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/.pre-commit-config.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/AGENTS.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/CONCURRENCY.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/CONTRIBUTING.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/DEVELOPMENT_PRINCIPLES.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/MANIFEST.in +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/README.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/budgets.jsonl +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/README.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/capacity-size-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/probe-loss-nominal-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/probe-loss-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/probe-loss-tight-fastdds.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/calibration/probe-loss-tight-zenoh.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/codecov.yml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/deployment-configuration.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/benchmark-ab.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/benchmark-bag-as-load.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/ci.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/ffmpeg-keyframes.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/README.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/delay-alone-no-loss.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/head-of-line-irregularity.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/lighter-message-loss.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/reorder-becomes-reader-loss.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/repro/reorder_drop_repro.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/forensics-report.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/geo-link-map.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/github-repository-settings.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/link-bandwidth.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/link-trace.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/owner-runbook.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/performance-bands.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/playbooks/README.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/quality-model.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/release-notes/TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/release-notes/v2.1.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/release-notes/v2.2.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/release-notes/v2.3.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/release-notes/v2.4.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/release.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0002-expectation-concepts.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0003-metric-backbone.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0006-dynamic-qos.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/0007-regression-gate.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/rfcs/README.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/testing.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/docs/work-items.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/install.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/justfile +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/media/control_center.png +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/media/echo_test.png +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/media/forensics-report-example.png +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/media/host.png +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/media/vehicle.png +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/pyproject.toml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/requirements.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/__init__.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/__main__.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/anonymize.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/benched_set.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/benchmark.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/deployment.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/forensics.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/geomap.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/image_cache.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/plots.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/py.typed +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/benched-set.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/.gitignore +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/README.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/__init__.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/profiles.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ros2docker.json.example +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/status_eval.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/transit.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom/video_quality.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/requires.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/terminology.md +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_benched_set_registry.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_findings.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_markdown_links.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_ota_source_bundle.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_package_metadata.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_packaged_resources.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_profile_covers_generated_commands.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_pytest_policy.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_readme_examples.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_security_maintenance_config.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_session_configuration_doc.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_session_template_substitution.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/contract/test_workflow_contracts.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/conftest.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_anonymize_e2e.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_benchmark_ab.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_benchmark_capacity.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_benchmark_replay.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_parallel_smoke.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_smoke.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_timeline_stepping.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/e2e/test_video_quality_e2e.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/sessions/generate_rmw_matrix.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_anonymize.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_benched_set.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_catmux_pane_logging.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_cli.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_concurrency.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_deployment.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_field_profiles.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_forensics.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_geomap.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_image_cache.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_link_trace.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_playout_pacer.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_plots.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_qos_fallback_logging.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_relay_optional_parameters.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_status_eval.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_status_overview.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_status_overview_observer.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_transit.py +0 -0
- {rosotacom_dev-2.5.dev48 → rosotacom_dev-2.5.dev50}/tests/unit/test_video_quality.py +0 -0
|
@@ -266,6 +266,17 @@ risk — a stuck `qdisc` silently corrupts every later result on the machine.
|
|
|
266
266
|
and outage steps, plus static distillation from a full trace or window
|
|
267
267
|
(`trace_profiles.convert_trace_to_profile_yaml`). Passive throughput is omitted
|
|
268
268
|
as `rate` unless the sample is marked saturated/probed/capacity-like.
|
|
269
|
+
- [x] Shape without host sudo (issue #279): `--sudo-mode container` runs every
|
|
270
|
+
tc/ip argv in a short-lived `docker run --rm --network host --cap-add
|
|
271
|
+
NET_ADMIN` container on the peer, so docker-group membership is the whole
|
|
272
|
+
privilege. `--network host` keeps the target identical to the sudo modes
|
|
273
|
+
(the host netns is the netns the OTA com containers use); the safety
|
|
274
|
+
watchdog detaches as `docker run -d` under a fixed name and replaces a
|
|
275
|
+
stale predecessor at arm time; the image is `--shaping-image` or resolved
|
|
276
|
+
on the peer (running `rosotacom_` container's image, else the newest local
|
|
277
|
+
`ros-communication*` image, else an actionable exit-69). The lab benchmark
|
|
278
|
+
path needed none of this — it has always armed tc inside the peer
|
|
279
|
+
containers' own netns (`--cap-add NET_ADMIN` on the isolated network).
|
|
269
280
|
- [ ] Confirm the `/proc/net/dev` link sampler (RFC 0003 / `link_bytes.py`) reports
|
|
270
281
|
post-shaping wire bytes so link-overhead stays meaningful under a profile. *(Bench
|
|
271
282
|
check.)*
|
|
@@ -342,6 +353,13 @@ off during implementation). Notes whether automation is feasible; privileged
|
|
|
342
353
|
load generated YAML through `load_profiles_file`, and feed the resulting
|
|
343
354
|
timeline into dry-run `ProfileShaper` command generation
|
|
344
355
|
(`tests/unit/test_trace_profiles.py`). Done.
|
|
356
|
+
- [x] **Sudo-free container shaping** (`--sudo-mode container`) — host unit tests
|
|
357
|
+
pin the generated shell: host-netns + NET_ADMIN on every tc/ip run, the
|
|
358
|
+
watchdog's `docker run -d` detachment and stale-predecessor replacement, the
|
|
359
|
+
on-peer image resolution with its actionable failure, and that the watchdog
|
|
360
|
+
name stays out of the `rosotacom_` conflict scan
|
|
361
|
+
(`tests/unit/test_container_shaping.py`). Arming it against a live remote pair
|
|
362
|
+
stays a bench check (same standing as the sudo modes' live half).
|
|
345
363
|
- [ ] **Emulated-profile gate (rung 2)** — an example session run under one
|
|
346
364
|
canonical profile in the per-promotion CI smoke, asserting a conditional bound
|
|
347
365
|
that differs from the unshaped run. Automatable in the smoke matrix.
|
|
@@ -568,13 +568,39 @@ number being reported.
|
|
|
568
568
|
- `bit_rate` (int)
|
|
569
569
|
- `encoder_av_options` (string)
|
|
570
570
|
|
|
571
|
-
#### Receiver-side playout pacing
|
|
571
|
+
#### Receiver-side playout pacing
|
|
572
572
|
|
|
573
573
|
A receiver that hands every packet to the decoder the moment it arrives turns
|
|
574
574
|
network delay jitter into display stutter. `com_py playout_pacer` re-times a
|
|
575
575
|
received packet stream to `stamp + budget` (budget adaptive above the fastest
|
|
576
576
|
observed path, clock-offset-proof; late packets pass through immediately, order
|
|
577
|
-
always preserved — the decoder chain sees every packet)
|
|
577
|
+
always preserved — the decoder chain sees every packet). On the 2026-08-17 CCNG
|
|
578
|
+
field trace this removes ~95 % of delay-caused >200 ms display gaps at ~100 ms
|
|
579
|
+
median added age (adaptive mode).
|
|
580
|
+
|
|
581
|
+
Declaratively, add a `playout` block to the transport:
|
|
582
|
+
|
|
583
|
+
```yaml
|
|
584
|
+
transport:
|
|
585
|
+
type: "ffmpeg"
|
|
586
|
+
gop_size: 3
|
|
587
|
+
remote_republish: compressed
|
|
588
|
+
playout: # receiver-side; requires remote_republish
|
|
589
|
+
adaptive: true # default true; false uses target_ms as a fixed budget
|
|
590
|
+
target_ms: 350 # fixed budget when adaptive: false
|
|
591
|
+
min_ms: 100 # adaptive clamp above the observed delay floor
|
|
592
|
+
max_ms: 800
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
The receiving peer then runs the pacer in its own `PACE` window against the
|
|
596
|
+
arrived encoded stream, and the reverse republish decodes `<encoded>/paced`
|
|
597
|
+
instead of the raw arrival — the delivered topic name is unchanged, which is
|
|
598
|
+
why `playout` requires `remote_republish`: without a republish the pacer's
|
|
599
|
+
`/paced` suffix would rename what the receiving application subscribes to.
|
|
600
|
+
The sender's own preview decode (`local_republish`) is never paced — it reads
|
|
601
|
+
the local copy of what was just encoded, so there is no jitter to absorb.
|
|
602
|
+
|
|
603
|
+
The node also runs standalone against any packet stream:
|
|
578
604
|
|
|
579
605
|
```bash
|
|
580
606
|
ros2 run com_py playout_pacer --ros-args \
|
|
@@ -583,11 +609,7 @@ ros2 run com_py playout_pacer --ros-args \
|
|
|
583
609
|
```
|
|
584
610
|
|
|
585
611
|
It republishes on `<topic>/paced` plus `.../paced/budget_ms` and
|
|
586
|
-
`.../paced/queue_depth` debug topics
|
|
587
|
-
decoder) at the paced name. On the 2026-08-17 CCNG field trace this removes
|
|
588
|
-
~95 % of delay-caused >200 ms display gaps at ~100 ms median added age
|
|
589
|
-
(adaptive mode). Declarative wiring as a `transport.playout` block is tracked
|
|
590
|
-
in issue #284.
|
|
612
|
+
`.../paced/queue_depth` debug topics.
|
|
591
613
|
|
|
592
614
|
`type: foxglove` supports (CompressedVideo):
|
|
593
615
|
- `gop_size` (int)
|
|
@@ -121,7 +121,15 @@ EXTERNAL_SESSION_CONTAINER_DIR = "/session/current"
|
|
|
121
121
|
# `anonymize_bag.py` two thousand lines below already does it this way.
|
|
122
122
|
RUN_SESSION_CONTAINER_ARGV = ("python3", "/ws/session/creation/run_session.py")
|
|
123
123
|
DEFAULT_SMOKE_SESSION = "1_heartbeat"
|
|
124
|
-
|
|
124
|
+
# How profile shaping obtains tc/ip privileges on a peer. `passwordless` and
|
|
125
|
+
# `askpass` are the two sudo shapes. `container` needs no sudo at all: every
|
|
126
|
+
# tc/ip argv runs in a short-lived `docker run --rm --network host --cap-add
|
|
127
|
+
# NET_ADMIN` container on the peer, so docker-group membership is the whole
|
|
128
|
+
# privilege — the mode that makes profiles runnable on a machine that has
|
|
129
|
+
# Docker but no passwordless sudo (issue #279). `--network host` puts tc in
|
|
130
|
+
# the host netns, which is the netns the OTA com containers use, so it shapes
|
|
131
|
+
# exactly the interface sudo would.
|
|
132
|
+
OTA_SUDO_MODES = ("passwordless", "askpass", "container")
|
|
125
133
|
|
|
126
134
|
# How ota-smoke puts rosotacom on each peer.
|
|
127
135
|
#
|
|
@@ -2663,6 +2671,59 @@ def _sudo_command(argv: Sequence[str], *, askpass: bool = False) -> str:
|
|
|
2663
2671
|
return f"{mode} {shlex.join(list(argv))}"
|
|
2664
2672
|
|
|
2665
2673
|
|
|
2674
|
+
#: Deterministic name for the container-mode safety watchdog on a peer. Fixed
|
|
2675
|
+
#: (not per-run) on purpose: arming replaces a stale predecessor with
|
|
2676
|
+
#: `docker rm -f`, so a crashed run's watchdog cannot accumulate — and the
|
|
2677
|
+
#: dash spelling keeps it out of the `rosotacom_` conflict scan, whose signal
|
|
2678
|
+
#: for a live run is the qdisc itself, not the sleeping watchdog.
|
|
2679
|
+
_SHAPING_WATCHDOG_NAME = "rosotacom-shaping-watchdog"
|
|
2680
|
+
|
|
2681
|
+
|
|
2682
|
+
def _shaping_image_resolution(image: str | None) -> str:
|
|
2683
|
+
"""Shell preamble that leaves the shaping image in ``$img`` on the peer.
|
|
2684
|
+
|
|
2685
|
+
An explicit image wins. Otherwise resolve on the peer itself, because the
|
|
2686
|
+
scoped image name (`<base>-<install_id>`) is a property of the peer's own
|
|
2687
|
+
install that the orchestrator cannot know before that install ran: prefer
|
|
2688
|
+
the image of a running `rosotacom_` container, fall back to the newest
|
|
2689
|
+
local `ros-communication*` image. Exit 69 with an actionable message when
|
|
2690
|
+
neither exists (a machine that never ran rosotacom): shaping needs *an*
|
|
2691
|
+
image with iproute2, and the com image is the one guaranteed relevant."""
|
|
2692
|
+
if image:
|
|
2693
|
+
return f"img={shlex.quote(image)}; "
|
|
2694
|
+
return (
|
|
2695
|
+
"img=\"$(docker ps --filter name=rosotacom_ --format '{{.Image}}' | head -n1)\"; "
|
|
2696
|
+
'[ -n "$img" ] || img="$(docker image ls --format \'{{.Repository}}:{{.Tag}}\''
|
|
2697
|
+
" | grep -m1 '^ros-communication' || true)\"; "
|
|
2698
|
+
'[ -n "$img" ] || { echo "no shaping image on this peer: run rosotacom here once'
|
|
2699
|
+
' (any smoke/start builds the com image) or pass --shaping-image" >&2; exit 69; }; '
|
|
2700
|
+
)
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
def _container_shaping_command(argv: Sequence[str], *, image: str | None, watchdog: bool = False) -> str:
|
|
2704
|
+
"""One privileged tc/ip argv as a short-lived NET_ADMIN container run.
|
|
2705
|
+
|
|
2706
|
+
``--network host`` is the point: the ephemeral container shares the host
|
|
2707
|
+
netns, so ``tc`` acts on the same interface sudo would — but the privilege
|
|
2708
|
+
comes from docker-group membership instead of a sudoers entry. A watchdog
|
|
2709
|
+
argv (``sh -c 'sleep …; tc …'``) runs detached (``-d``) under the fixed
|
|
2710
|
+
name above, replacing any stale predecessor; docker itself keeps it alive
|
|
2711
|
+
across an orchestrator crash, which is the fail-safe RFC 0004 demands."""
|
|
2712
|
+
argv = list(argv)
|
|
2713
|
+
resolve = _shaping_image_resolution(image)
|
|
2714
|
+
run = "docker run --rm --network host --cap-add NET_ADMIN"
|
|
2715
|
+
if watchdog:
|
|
2716
|
+
run = (
|
|
2717
|
+
f"docker rm -f {_SHAPING_WATCHDOG_NAME} >/dev/null 2>&1 || true; "
|
|
2718
|
+
f"docker run -d --rm --name {_SHAPING_WATCHDOG_NAME} --network host --cap-add NET_ADMIN"
|
|
2719
|
+
)
|
|
2720
|
+
if len(argv) >= 3 and argv[0] == "sh" and argv[1] == "-c":
|
|
2721
|
+
tail = f'--entrypoint sh "$img" -c {shlex.quote(str(argv[2]))}'
|
|
2722
|
+
else:
|
|
2723
|
+
tail = f'--entrypoint {shlex.quote(argv[0])} "$img" {shlex.join(argv[1:])}'
|
|
2724
|
+
return f"{resolve}{run} {tail}"
|
|
2725
|
+
|
|
2726
|
+
|
|
2666
2727
|
def _passwordless_watchdog_command(argv: Sequence[str]) -> str:
|
|
2667
2728
|
argv = list(argv)
|
|
2668
2729
|
if len(argv) >= 3 and argv[0] == "sh" and argv[1] == "-c":
|
|
@@ -2744,6 +2805,7 @@ def _ota_preflight(
|
|
|
2744
2805
|
require_network_shaping_sudo: bool = False,
|
|
2745
2806
|
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
2746
2807
|
sudo_passwords: Mapping[str, str] | None = None,
|
|
2808
|
+
shaping_image: str | None = None,
|
|
2747
2809
|
check_conflicts: bool = True,
|
|
2748
2810
|
) -> None:
|
|
2749
2811
|
sudo_mode = _validate_ota_sudo_mode(sudo_mode)
|
|
@@ -2771,6 +2833,27 @@ def _ota_preflight(
|
|
|
2771
2833
|
)
|
|
2772
2834
|
_ota_run(peer, "docker ps >/dev/null", label=f"{peer.name}: docker access", dry_run=dry_run, batch=True)
|
|
2773
2835
|
if require_network_shaping_sudo:
|
|
2836
|
+
if sudo_mode == "container":
|
|
2837
|
+
# tc/ip live in the shaping image, not on the host, and no sudo
|
|
2838
|
+
# is involved: the one thing to prove is that a NET_ADMIN
|
|
2839
|
+
# host-netns container can run and sees the tools. This also
|
|
2840
|
+
# fails early — with the actionable exit-69 message — on a
|
|
2841
|
+
# machine that has no candidate image yet.
|
|
2842
|
+
_ota_run(
|
|
2843
|
+
peer,
|
|
2844
|
+
_container_shaping_command(
|
|
2845
|
+
[
|
|
2846
|
+
"sh",
|
|
2847
|
+
"-c",
|
|
2848
|
+
"command -v tc >/dev/null && command -v ip >/dev/null && tc qdisc show >/dev/null",
|
|
2849
|
+
],
|
|
2850
|
+
image=shaping_image,
|
|
2851
|
+
),
|
|
2852
|
+
label=f"{peer.name}: containerized network shaping (docker run, NET_ADMIN, tc in image)",
|
|
2853
|
+
dry_run=dry_run,
|
|
2854
|
+
batch=True,
|
|
2855
|
+
)
|
|
2856
|
+
continue
|
|
2774
2857
|
_ota_run(
|
|
2775
2858
|
peer,
|
|
2776
2859
|
"command -v tc >/dev/null 2>&1 && command -v ip >/dev/null 2>&1",
|
|
@@ -3241,13 +3324,21 @@ def _ota_resolve_interfaces(peer: OtaSmokePeer, peer_addr: str, *, dry_run: bool
|
|
|
3241
3324
|
|
|
3242
3325
|
|
|
3243
3326
|
def _peer_command_runner(
|
|
3244
|
-
peer: OtaSmokePeer,
|
|
3327
|
+
peer: OtaSmokePeer,
|
|
3328
|
+
*,
|
|
3329
|
+
dry_run: bool,
|
|
3330
|
+
sudo_password: str | None = None,
|
|
3331
|
+
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
3332
|
+
shaping_image: str | None = None,
|
|
3245
3333
|
) -> Callable[[Sequence[str]], None]:
|
|
3246
3334
|
"""A CommandRunner that runs one privileged argv on ``peer`` via the SSH path."""
|
|
3247
3335
|
|
|
3248
3336
|
def run(argv: Sequence[str]) -> None:
|
|
3249
3337
|
label = f"{peer.name}: tc/netem"
|
|
3250
|
-
if
|
|
3338
|
+
if sudo_mode == "container":
|
|
3339
|
+
script = _container_shaping_command(argv, image=shaping_image)
|
|
3340
|
+
_ota_run(peer, script, label=label, dry_run=dry_run)
|
|
3341
|
+
elif sudo_password is None:
|
|
3251
3342
|
script = _sudo_command(argv)
|
|
3252
3343
|
_ota_run(peer, script, label=label, dry_run=dry_run)
|
|
3253
3344
|
else:
|
|
@@ -3265,12 +3356,23 @@ def _peer_command_runner(
|
|
|
3265
3356
|
|
|
3266
3357
|
|
|
3267
3358
|
def _peer_watchdog_launcher(
|
|
3268
|
-
peer: OtaSmokePeer,
|
|
3359
|
+
peer: OtaSmokePeer,
|
|
3360
|
+
*,
|
|
3361
|
+
dry_run: bool,
|
|
3362
|
+
sudo_password: str | None = None,
|
|
3363
|
+
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
3364
|
+
shaping_image: str | None = None,
|
|
3269
3365
|
) -> Callable[[Sequence[str]], None]:
|
|
3270
3366
|
"""Launch the safety-watchdog argv detached on ``peer`` so it survives a crash."""
|
|
3271
3367
|
|
|
3272
3368
|
def launch(argv: Sequence[str]) -> None:
|
|
3273
|
-
if
|
|
3369
|
+
if sudo_mode == "container":
|
|
3370
|
+
# `docker run -d` is the detachment: the daemon owns the sleeping
|
|
3371
|
+
# watchdog, so it survives the SSH session, the orchestrator, and
|
|
3372
|
+
# every rosotacom container on the machine.
|
|
3373
|
+
detached = _container_shaping_command(argv, image=shaping_image, watchdog=True)
|
|
3374
|
+
_ota_run(peer, detached, label=f"{peer.name}: profile safety watchdog", dry_run=dry_run, check=False)
|
|
3375
|
+
elif sudo_password is None:
|
|
3274
3376
|
detached = _passwordless_watchdog_command(argv)
|
|
3275
3377
|
_ota_run(peer, detached, label=f"{peer.name}: profile safety watchdog", dry_run=dry_run, check=False)
|
|
3276
3378
|
else:
|
|
@@ -3297,6 +3399,8 @@ def _ota_arm_profile(
|
|
|
3297
3399
|
*,
|
|
3298
3400
|
dry_run: bool,
|
|
3299
3401
|
sudo_passwords: Mapping[str, str] | None = None,
|
|
3402
|
+
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
3403
|
+
shaping_image: str | None = None,
|
|
3300
3404
|
) -> list[Any]:
|
|
3301
3405
|
"""Arm the static profile per direction on every peer's OTA egress (RFC 0004),
|
|
3302
3406
|
returning the ``ProfileShaper`` handles to revert in the run's ``finally``."""
|
|
@@ -3320,10 +3424,22 @@ def _ota_arm_profile(
|
|
|
3320
3424
|
sudo_password = (sudo_passwords or {}).get(peer_name)
|
|
3321
3425
|
shaper = ProfileShaper(
|
|
3322
3426
|
ota_iface,
|
|
3323
|
-
_peer_command_runner(
|
|
3427
|
+
_peer_command_runner(
|
|
3428
|
+
peer,
|
|
3429
|
+
dry_run=dry_run,
|
|
3430
|
+
sudo_password=sudo_password,
|
|
3431
|
+
sudo_mode=sudo_mode,
|
|
3432
|
+
shaping_image=shaping_image,
|
|
3433
|
+
),
|
|
3324
3434
|
control_interface=control_iface,
|
|
3325
3435
|
safety_max_duration_s=_PROFILE_SAFETY_MAX_S,
|
|
3326
|
-
watchdog_launcher=_peer_watchdog_launcher(
|
|
3436
|
+
watchdog_launcher=_peer_watchdog_launcher(
|
|
3437
|
+
peer,
|
|
3438
|
+
dry_run=dry_run,
|
|
3439
|
+
sudo_password=sudo_password,
|
|
3440
|
+
sudo_mode=sudo_mode,
|
|
3441
|
+
shaping_image=shaping_image,
|
|
3442
|
+
),
|
|
3327
3443
|
)
|
|
3328
3444
|
# Register before arming so a mid-arm failure is still reverted in `finally`.
|
|
3329
3445
|
shapers.append(shaper)
|
|
@@ -4085,7 +4201,20 @@ def _start_noninteractive_ota_smoke(args: argparse.Namespace) -> int:
|
|
|
4085
4201
|
errors: list[str] = []
|
|
4086
4202
|
try:
|
|
4087
4203
|
if profile is not None:
|
|
4088
|
-
shapers = _ota_arm_profile(
|
|
4204
|
+
shapers = _ota_arm_profile(
|
|
4205
|
+
plan,
|
|
4206
|
+
profile,
|
|
4207
|
+
directions,
|
|
4208
|
+
dry_run=dry_run,
|
|
4209
|
+
sudo_passwords=_ota_network_sudo_passwords(
|
|
4210
|
+
plan,
|
|
4211
|
+
sudo_mode=getattr(args, "sudo_mode", OTA_DEFAULT_SUDO_MODE),
|
|
4212
|
+
require_network_shaping_sudo=True,
|
|
4213
|
+
dry_run=dry_run,
|
|
4214
|
+
),
|
|
4215
|
+
sudo_mode=getattr(args, "sudo_mode", OTA_DEFAULT_SUDO_MODE),
|
|
4216
|
+
shaping_image=getattr(args, "shaping_image", None),
|
|
4217
|
+
)
|
|
4089
4218
|
_ota_start_peers(
|
|
4090
4219
|
target,
|
|
4091
4220
|
plan,
|
|
@@ -9039,6 +9168,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
9039
9168
|
action="store_true",
|
|
9040
9169
|
help="Keep staged source and project files after cleanup.",
|
|
9041
9170
|
)
|
|
9171
|
+
ota_smoke_parser.add_argument(
|
|
9172
|
+
"--sudo-mode",
|
|
9173
|
+
choices=OTA_SUDO_MODES,
|
|
9174
|
+
default=OTA_DEFAULT_SUDO_MODE,
|
|
9175
|
+
help=(
|
|
9176
|
+
"How profile shaping obtains tc/ip privileges on the peers "
|
|
9177
|
+
"(passwordless: require sudo -n; askpass: prompt locally per peer and feed sudo -S; "
|
|
9178
|
+
"container: no sudo -- run each tc/ip in a short-lived NET_ADMIN host-netns container, "
|
|
9179
|
+
"for peers with Docker but no passwordless sudo)."
|
|
9180
|
+
),
|
|
9181
|
+
)
|
|
9182
|
+
ota_smoke_parser.add_argument(
|
|
9183
|
+
"--shaping-image",
|
|
9184
|
+
help=(
|
|
9185
|
+
"Image for --sudo-mode container (must contain tc/ip). Default: resolve on the peer -- "
|
|
9186
|
+
"the image of a running rosotacom_ container, else the newest local ros-communication* image."
|
|
9187
|
+
),
|
|
9188
|
+
)
|
|
9042
9189
|
ota_smoke_parser.add_argument("--skip-preflight", action="store_true", help="Skip SSH/Docker readiness checks.")
|
|
9043
9190
|
ota_smoke_parser.add_argument(
|
|
9044
9191
|
"--skip-conflict-check",
|
|
@@ -4981,8 +4981,17 @@ def _add_benchmark_common_args(parser: argparse.ArgumentParser, *, ota_benchmark
|
|
|
4981
4981
|
choices=OTA_SUDO_MODES,
|
|
4982
4982
|
default=OTA_DEFAULT_SUDO_MODE,
|
|
4983
4983
|
help=(
|
|
4984
|
-
"How OTA benchmark profile shaping obtains
|
|
4985
|
-
"(passwordless: require sudo -n; askpass: prompt locally per peer and feed sudo -S
|
|
4984
|
+
"How OTA benchmark profile shaping obtains tc/ip privileges "
|
|
4985
|
+
"(passwordless: require sudo -n; askpass: prompt locally per peer and feed sudo -S; "
|
|
4986
|
+
"container: no sudo -- run each tc/ip in a short-lived NET_ADMIN host-netns container, "
|
|
4987
|
+
"for peers with Docker but no passwordless sudo)."
|
|
4988
|
+
),
|
|
4989
|
+
)
|
|
4990
|
+
parser.add_argument(
|
|
4991
|
+
"--shaping-image",
|
|
4992
|
+
help=(
|
|
4993
|
+
"Image for --sudo-mode container (must contain tc/ip). Default: resolve on the peer -- "
|
|
4994
|
+
"the image of a running rosotacom_ container, else the newest local ros-communication* image."
|
|
4986
4995
|
),
|
|
4987
4996
|
)
|
|
4988
4997
|
parser.add_argument("--interactive", action="store_true", help="Open a tmux operator view for this benchmark run.")
|
|
@@ -5148,6 +5157,7 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
|
|
|
5148
5157
|
profile=profile,
|
|
5149
5158
|
benchmark_stepping=True,
|
|
5150
5159
|
sudo_mode=getattr(args, "sudo_mode", "passwordless"),
|
|
5160
|
+
shaping_image=getattr(args, "shaping_image", None),
|
|
5151
5161
|
)
|
|
5152
5162
|
|
|
5153
5163
|
runtime, plan, target = _resolve_ota_smoke_context(smoke_args)
|
|
@@ -5170,6 +5180,7 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
|
|
|
5170
5180
|
require_network_shaping_sudo=profile_obj is not None,
|
|
5171
5181
|
sudo_mode=smoke_args.sudo_mode,
|
|
5172
5182
|
sudo_passwords=sudo_passwords,
|
|
5183
|
+
shaping_image=smoke_args.shaping_image,
|
|
5173
5184
|
check_conflicts=not getattr(args, "skip_conflict_check", False),
|
|
5174
5185
|
)
|
|
5175
5186
|
_ota_prepare_hosts(smoke_args, runtime, plan)
|
|
@@ -5210,12 +5221,20 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
|
|
|
5210
5221
|
shaper = ProfileShaper(
|
|
5211
5222
|
ota_iface,
|
|
5212
5223
|
_peer_command_runner(
|
|
5213
|
-
peer,
|
|
5224
|
+
peer,
|
|
5225
|
+
dry_run=dry_run,
|
|
5226
|
+
sudo_password=sudo_passwords.get(peer_name),
|
|
5227
|
+
sudo_mode=smoke_args.sudo_mode,
|
|
5228
|
+
shaping_image=smoke_args.shaping_image,
|
|
5214
5229
|
),
|
|
5215
5230
|
control_interface=control_iface,
|
|
5216
5231
|
safety_max_duration_s=_PROFILE_SAFETY_MAX_S,
|
|
5217
5232
|
watchdog_launcher=_peer_watchdog_launcher(
|
|
5218
|
-
peer,
|
|
5233
|
+
peer,
|
|
5234
|
+
dry_run=dry_run,
|
|
5235
|
+
sudo_password=sudo_passwords.get(peer_name),
|
|
5236
|
+
sudo_mode=smoke_args.sudo_mode,
|
|
5237
|
+
shaping_image=smoke_args.shaping_image,
|
|
5219
5238
|
),
|
|
5220
5239
|
)
|
|
5221
5240
|
shapers.append(shaper)
|
|
@@ -5226,7 +5245,13 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
|
|
|
5226
5245
|
shaper.arm([])
|
|
5227
5246
|
else:
|
|
5228
5247
|
shapers = _ota_arm_profile(
|
|
5229
|
-
plan,
|
|
5248
|
+
plan,
|
|
5249
|
+
profile_obj,
|
|
5250
|
+
directions,
|
|
5251
|
+
dry_run=dry_run,
|
|
5252
|
+
sudo_passwords=sudo_passwords,
|
|
5253
|
+
sudo_mode=smoke_args.sudo_mode,
|
|
5254
|
+
shaping_image=smoke_args.shaping_image,
|
|
5230
5255
|
)
|
|
5231
5256
|
|
|
5232
5257
|
_ota_start_peers(target, plan, instance.instance_id, dry_run=dry_run)
|
|
@@ -232,15 +232,49 @@ parameters:
|
|
|
232
232
|
irt_1_topic: # e.g. /sensors/camera/front_wide/image_color
|
|
233
233
|
irt_1_transport: # e.g. raw
|
|
234
234
|
irt_1_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
235
|
+
irt_1_paced: # "true" -> decode reads ${irt_1_topic}/paced (playout pacer, PACE window)
|
|
235
236
|
irt_2_topic: # e.g. /sensors/front_camera/image_raw/compressed
|
|
236
237
|
irt_2_transport: # e.g. raw
|
|
237
238
|
irt_2_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
239
|
+
irt_2_paced: # "true" -> decode reads ${irt_2_topic}/paced
|
|
238
240
|
irt_3_topic: # e.g. /sensor/front_camera/image_color/compressed
|
|
239
241
|
irt_3_transport: # e.g. raw
|
|
240
242
|
irt_3_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
243
|
+
irt_3_paced: # "true" -> decode reads ${irt_3_topic}/paced
|
|
241
244
|
irt_4_topic: # e.g. /sensor/front_camera/image_color/compressed
|
|
242
245
|
irt_4_transport: # e.g. raw
|
|
243
246
|
irt_4_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
247
|
+
irt_4_paced: # "true" -> decode reads ${irt_4_topic}/paced
|
|
248
|
+
|
|
249
|
+
# Receiver-side playout pacing (transport.playout in the session definition):
|
|
250
|
+
# re-times the arrived encoded stream on '<topic>/paced' so the decode sees a
|
|
251
|
+
# steady cadence instead of the network's delay jitter. Never reorders, never
|
|
252
|
+
# drops; late packets pass through immediately.
|
|
253
|
+
pace: false
|
|
254
|
+
pace_1_topic: # the arrived encoded stream, e.g. /camera/image/ffmpeg
|
|
255
|
+
pace_1_msg_type: # e.g. ffmpeg_image_transport_msgs/msg/FFMPEGPacket
|
|
256
|
+
pace_1_adaptive: # "true" | "false"
|
|
257
|
+
pace_1_target_ms: # fixed budget when adaptive is "false"
|
|
258
|
+
pace_1_min_ms: # adaptive clamp above the observed delay floor
|
|
259
|
+
pace_1_max_ms:
|
|
260
|
+
pace_2_topic:
|
|
261
|
+
pace_2_msg_type:
|
|
262
|
+
pace_2_adaptive:
|
|
263
|
+
pace_2_target_ms:
|
|
264
|
+
pace_2_min_ms:
|
|
265
|
+
pace_2_max_ms:
|
|
266
|
+
pace_3_topic:
|
|
267
|
+
pace_3_msg_type:
|
|
268
|
+
pace_3_adaptive:
|
|
269
|
+
pace_3_target_ms:
|
|
270
|
+
pace_3_min_ms:
|
|
271
|
+
pace_3_max_ms:
|
|
272
|
+
pace_4_topic:
|
|
273
|
+
pace_4_msg_type:
|
|
274
|
+
pace_4_adaptive:
|
|
275
|
+
pace_4_target_ms:
|
|
276
|
+
pace_4_min_ms:
|
|
277
|
+
pace_4_max_ms:
|
|
244
278
|
|
|
245
279
|
common:
|
|
246
280
|
before_commands:
|
|
@@ -623,6 +657,30 @@ windows:
|
|
|
623
657
|
- args+=(--remap "${in_remap}:=${it_4_topic}" --remap "out/${it_4_transport}:=${it_4_topic}/${outgoing_suffix}")
|
|
624
658
|
- echo ${args[@]}
|
|
625
659
|
- ${args[@]}
|
|
660
|
+
- name: PACE
|
|
661
|
+
if: pace
|
|
662
|
+
layout: even-vertical
|
|
663
|
+
splits:
|
|
664
|
+
- commands:
|
|
665
|
+
- if [ '${pace_1_topic}' = "None" ]; then exit; fi
|
|
666
|
+
- args=(ros2 run com_py playout_pacer --ros-args -p topic:=${pace_1_topic} -p msg_type:=${pace_1_msg_type} -p adaptive:=${pace_1_adaptive} -p target_ms:=${pace_1_target_ms} -p min_ms:=${pace_1_min_ms} -p max_ms:=${pace_1_max_ms} -p qos_config_file:='${qos_config_file}')
|
|
667
|
+
- echo ${args[@]}
|
|
668
|
+
- ${args[@]}
|
|
669
|
+
- commands:
|
|
670
|
+
- if [ '${pace_2_topic}' = "None" ]; then exit; fi
|
|
671
|
+
- args=(ros2 run com_py playout_pacer --ros-args -p topic:=${pace_2_topic} -p msg_type:=${pace_2_msg_type} -p adaptive:=${pace_2_adaptive} -p target_ms:=${pace_2_target_ms} -p min_ms:=${pace_2_min_ms} -p max_ms:=${pace_2_max_ms} -p qos_config_file:='${qos_config_file}')
|
|
672
|
+
- echo ${args[@]}
|
|
673
|
+
- ${args[@]}
|
|
674
|
+
- commands:
|
|
675
|
+
- if [ '${pace_3_topic}' = "None" ]; then exit; fi
|
|
676
|
+
- args=(ros2 run com_py playout_pacer --ros-args -p topic:=${pace_3_topic} -p msg_type:=${pace_3_msg_type} -p adaptive:=${pace_3_adaptive} -p target_ms:=${pace_3_target_ms} -p min_ms:=${pace_3_min_ms} -p max_ms:=${pace_3_max_ms} -p qos_config_file:='${qos_config_file}')
|
|
677
|
+
- echo ${args[@]}
|
|
678
|
+
- ${args[@]}
|
|
679
|
+
- commands:
|
|
680
|
+
- if [ '${pace_4_topic}' = "None" ]; then exit; fi
|
|
681
|
+
- args=(ros2 run com_py playout_pacer --ros-args -p topic:=${pace_4_topic} -p msg_type:=${pace_4_msg_type} -p adaptive:=${pace_4_adaptive} -p target_ms:=${pace_4_target_ms} -p min_ms:=${pace_4_min_ms} -p max_ms:=${pace_4_max_ms} -p qos_config_file:='${qos_config_file}')
|
|
682
|
+
- echo ${args[@]}
|
|
683
|
+
- ${args[@]}
|
|
626
684
|
- name: IRT
|
|
627
685
|
if: irt
|
|
628
686
|
layout: even-vertical
|
|
@@ -630,28 +688,32 @@ windows:
|
|
|
630
688
|
- commands:
|
|
631
689
|
- if [ '${irt_1_topic}' = "None" ]; then exit; fi
|
|
632
690
|
- if [[ "${irt_1_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_1_out_transport}"; fi
|
|
691
|
+
- if [[ "${irt_1_paced}" != "None" ]]; then irt_in_topic="${irt_1_topic}/paced"; else irt_in_topic="${irt_1_topic}"; fi
|
|
633
692
|
- args=(ros2 run image_transport republish --ros-args -r __node:=irt_republish1 -p in_transport:=${irt_1_transport} -p out_transport:=${irt_1_out_transport}
|
|
634
|
-
--remap in/${irt_1_transport}:=${
|
|
693
|
+
--remap in/${irt_1_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_1_topic}/${irt_1_out_transport})
|
|
635
694
|
- echo ${args[@]}
|
|
636
695
|
- ${args[@]}
|
|
637
696
|
- commands:
|
|
638
697
|
- if [ '${irt_2_topic}' = "None" ]; then exit; fi
|
|
639
698
|
- if [[ "${irt_2_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_2_out_transport}"; fi
|
|
699
|
+
- if [[ "${irt_2_paced}" != "None" ]]; then irt_in_topic="${irt_2_topic}/paced"; else irt_in_topic="${irt_2_topic}"; fi
|
|
640
700
|
- args=(ros2 run image_transport republish --ros-args -r __node:=irt_republish2 -p in_transport:=${irt_2_transport} -p out_transport:=${irt_2_out_transport}
|
|
641
|
-
--remap in/${irt_2_transport}:=${
|
|
701
|
+
--remap in/${irt_2_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_2_topic}/${irt_2_out_transport})
|
|
642
702
|
- echo ${args[@]}
|
|
643
703
|
- ${args[@]}
|
|
644
704
|
- commands:
|
|
645
705
|
- if [ '${irt_3_topic}' = "None" ]; then exit; fi
|
|
646
706
|
- if [[ "${irt_3_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_3_out_transport}"; fi
|
|
707
|
+
- if [[ "${irt_3_paced}" != "None" ]]; then irt_in_topic="${irt_3_topic}/paced"; else irt_in_topic="${irt_3_topic}"; fi
|
|
647
708
|
- args=(ros2 run image_transport republish --ros-args -r __node:=irt_republish3 -p in_transport:=${irt_3_transport} -p out_transport:=${irt_3_out_transport}
|
|
648
|
-
--remap in/${irt_3_transport}:=${
|
|
709
|
+
--remap in/${irt_3_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_3_topic}/${irt_3_out_transport})
|
|
649
710
|
- echo ${args[@]}
|
|
650
711
|
- ${args[@]}
|
|
651
712
|
- commands:
|
|
652
713
|
- if [ '${irt_4_topic}' = "None" ]; then exit; fi
|
|
653
714
|
- if [[ "${irt_4_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_4_out_transport}"; fi
|
|
715
|
+
- if [[ "${irt_4_paced}" != "None" ]]; then irt_in_topic="${irt_4_topic}/paced"; else irt_in_topic="${irt_4_topic}"; fi
|
|
654
716
|
- args=(ros2 run image_transport republish --ros-args -r __node:=irt_republish4 -p in_transport:=${irt_4_transport} -p out_transport:=${irt_4_out_transport}
|
|
655
|
-
--remap in/${irt_4_transport}:=${
|
|
717
|
+
--remap in/${irt_4_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_4_topic}/${irt_4_out_transport})
|
|
656
718
|
- echo ${args[@]}
|
|
657
719
|
- ${args[@]}
|
|
@@ -92,6 +92,13 @@ class TransportSpec:
|
|
|
92
92
|
#: image_transport itself, or one that only forwards or records packets,
|
|
93
93
|
#: does not want a second copy of every frame.
|
|
94
94
|
remote_republish: Optional[str] = None
|
|
95
|
+
#: Receiver-side playout pacing (issue #284): re-time the arrived packet
|
|
96
|
+
#: stream to smooth network delay jitter before the reverse republish
|
|
97
|
+
#: decodes it. Keys: adaptive / target_ms / min_ms / max_ms (validated in
|
|
98
|
+
#: the parser; defaults live in the playout_pacer node). Requires
|
|
99
|
+
#: `remote_republish`, because the pacer publishes on `<encoded>/paced`
|
|
100
|
+
#: and only the republish remap keeps the delivered topic name unchanged.
|
|
101
|
+
playout: Optional[Dict[str, Any]] = None
|
|
95
102
|
|
|
96
103
|
|
|
97
104
|
@dataclass
|
|
@@ -1692,12 +1699,45 @@ def _compute_pipeline(
|
|
|
1692
1699
|
)
|
|
1693
1700
|
return str(value)
|
|
1694
1701
|
|
|
1695
|
-
|
|
1702
|
+
remote_republish = _republish("remote_republish")
|
|
1703
|
+
|
|
1704
|
+
playout: Optional[Dict[str, Any]] = None
|
|
1705
|
+
playout_cfg = t.get("playout")
|
|
1706
|
+
if playout_cfg is not None:
|
|
1707
|
+
if not isinstance(playout_cfg, dict):
|
|
1708
|
+
raise ValueError(f"transport.playout must be a mapping for topic '{entry.base}'")
|
|
1709
|
+
allowed = {"adaptive", "target_ms", "min_ms", "max_ms"}
|
|
1710
|
+
unknown = sorted(set(playout_cfg) - allowed)
|
|
1711
|
+
if unknown:
|
|
1712
|
+
raise ValueError(
|
|
1713
|
+
f"transport.playout for topic '{entry.base}' has unknown keys {unknown}; "
|
|
1714
|
+
f"allowed: {sorted(allowed)}"
|
|
1715
|
+
)
|
|
1716
|
+
if remote_republish is None:
|
|
1717
|
+
raise ValueError(
|
|
1718
|
+
f"transport.playout for topic '{entry.base}' requires remote_republish: "
|
|
1719
|
+
"the pacer publishes on '<encoded>/paced', and only the reverse republish's "
|
|
1720
|
+
"input remap keeps the delivered topic name unchanged. Without a republish "
|
|
1721
|
+
"the receiver would have to subscribe a renamed topic."
|
|
1722
|
+
)
|
|
1723
|
+
for key in ("target_ms", "min_ms", "max_ms"):
|
|
1724
|
+
if key in playout_cfg and (not isinstance(playout_cfg[key], (int, float)) or playout_cfg[key] <= 0):
|
|
1725
|
+
raise ValueError(f"transport.playout.{key} must be a positive number for topic '{entry.base}'")
|
|
1726
|
+
min_ms = float(playout_cfg.get("min_ms", 100.0))
|
|
1727
|
+
max_ms = float(playout_cfg.get("max_ms", 800.0))
|
|
1728
|
+
if max_ms < min_ms:
|
|
1729
|
+
raise ValueError(f"transport.playout.max_ms must be >= min_ms for topic '{entry.base}'")
|
|
1730
|
+
if "adaptive" in playout_cfg and not isinstance(playout_cfg["adaptive"], bool):
|
|
1731
|
+
raise ValueError(f"transport.playout.adaptive must be a boolean for topic '{entry.base}'")
|
|
1732
|
+
playout = dict(playout_cfg)
|
|
1733
|
+
|
|
1734
|
+
params = {k: v for k, v in t.items() if k not in ("type", "playout", *republish_keys)}
|
|
1696
1735
|
transport = TransportSpec(
|
|
1697
1736
|
type=ttype,
|
|
1698
1737
|
params=params,
|
|
1699
1738
|
local_republish=_republish("local_republish"),
|
|
1700
|
-
remote_republish=
|
|
1739
|
+
remote_republish=remote_republish,
|
|
1740
|
+
playout=playout,
|
|
1701
1741
|
)
|
|
1702
1742
|
|
|
1703
1743
|
topic = entry.base
|
|
@@ -3201,21 +3241,46 @@ def func(
|
|
|
3201
3241
|
# it encoded and (b) the decode of what arrived. Both run on *this* peer;
|
|
3202
3242
|
# what differs is which copy they decode and which transport the session
|
|
3203
3243
|
# asked each of them to publish in.
|
|
3204
|
-
|
|
3244
|
+
# The sender's own preview decode is never paced: it reads the local
|
|
3245
|
+
# copy of what it just encoded, so there is no network jitter to
|
|
3246
|
+
# absorb. Playout pacing exists on the receiving side only.
|
|
3247
|
+
irt_all: List[Tuple[str, str, str, Optional[Dict[str, Any]]]] = []
|
|
3205
3248
|
for t, tspec in irt_items_local:
|
|
3206
3249
|
assert tspec is not None
|
|
3207
|
-
irt_all.append((t, tspec.type, str(tspec.local_republish)))
|
|
3208
|
-
irt_all.extend(
|
|
3250
|
+
irt_all.append((t, tspec.type, str(tspec.local_republish), None))
|
|
3251
|
+
irt_all.extend(
|
|
3252
|
+
(topic, tspec.type, str(tspec.remote_republish), tspec.playout) for topic, tspec in irt_items_remote
|
|
3253
|
+
)
|
|
3209
3254
|
_assert_max("irt", irt_all, 4)
|
|
3210
3255
|
if irt_all:
|
|
3211
3256
|
items2 = [("irt", True)]
|
|
3212
|
-
for i, (topic, _ttype, _out_transport) in enumerate(irt_all, 1):
|
|
3257
|
+
for i, (topic, _ttype, _out_transport, _playout) in enumerate(irt_all, 1):
|
|
3213
3258
|
items2.append((f"irt_{i}_topic", topic))
|
|
3214
|
-
for i, (_topic, ttype, out_transport) in enumerate(irt_all, 1):
|
|
3259
|
+
for i, (_topic, ttype, out_transport, playout) in enumerate(irt_all, 1):
|
|
3215
3260
|
items2.append((f"irt_{i}_transport", ttype))
|
|
3216
3261
|
items2.append((f"irt_{i}_out_transport", out_transport))
|
|
3262
|
+
if playout is not None:
|
|
3263
|
+
# Points the decode's input remap at '<topic>/paced'; the
|
|
3264
|
+
# pacer itself runs in the PACE window below.
|
|
3265
|
+
items2.append((f"irt_{i}_paced", "true"))
|
|
3217
3266
|
blocks.append(PluginBlock("irt", items2))
|
|
3218
3267
|
|
|
3268
|
+
paced_entries = [
|
|
3269
|
+
(i, topic, ttype, playout)
|
|
3270
|
+
for i, (topic, ttype, _out_transport, playout) in enumerate(irt_all, 1)
|
|
3271
|
+
if playout is not None
|
|
3272
|
+
]
|
|
3273
|
+
if paced_entries:
|
|
3274
|
+
items_pace: List[Tuple[str, Any]] = [("pace", True)]
|
|
3275
|
+
for i, topic, ttype, playout in paced_entries:
|
|
3276
|
+
items_pace.append((f"pace_{i}_topic", topic))
|
|
3277
|
+
items_pace.append((f"pace_{i}_msg_type", TRANSPORT_OUTPUT_TYPES[ttype]))
|
|
3278
|
+
items_pace.append((f"pace_{i}_adaptive", "true" if playout.get("adaptive", True) else "false"))
|
|
3279
|
+
items_pace.append((f"pace_{i}_target_ms", float(playout.get("target_ms", 350.0))))
|
|
3280
|
+
items_pace.append((f"pace_{i}_min_ms", float(playout.get("min_ms", 100.0))))
|
|
3281
|
+
items_pace.append((f"pace_{i}_max_ms", float(playout.get("max_ms", 800.0))))
|
|
3282
|
+
blocks.append(PluginBlock("pace", items_pace))
|
|
3283
|
+
|
|
3219
3284
|
if nor_items:
|
|
3220
3285
|
items2 = [("nor", True)]
|
|
3221
3286
|
nor_prefix = f"/{peer_name[remote]}" if bool(inbound_cfg.get("keep_source_prefix", False)) else ""
|