rosotacom-dev 2.5.dev52__tar.gz → 2.5.dev54__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.dev52 → rosotacom_dev-2.5.dev54}/PKG-INFO +19 -1
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/README.md +18 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/deployment-configuration.md +23 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/session-configuration.md +28 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/cli.py +93 -2
- rosotacom_dev-2.5.dev54/src/rosotacom/network_preflight.py +134 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +18 -4
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +91 -16
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/PKG-INFO +19 -1
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/SOURCES.txt +2 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/scm_file_list.json +2 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_cli.py +19 -2
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_concurrency.py +25 -1
- rosotacom_dev-2.5.dev54/tests/unit/test_network_preflight.py +173 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_session_pipeline.py +88 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.devcontainer/devcontainer.json.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.devcontainer/re_generate_devcontainer_json.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/CODEOWNERS +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/actions/setup-just/action.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/dependabot.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/benchmark-calibrate.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/benchmark-gate.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/dev-release.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/image-scan.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/nightly-e2e.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/pr-lightweight.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/pr-merge-gate.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.github/workflows/release.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/.pre-commit-config.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/AGENTS.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/CONCURRENCY.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/CONTRIBUTING.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/DEVELOPMENT_PRINCIPLES.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/MANIFEST.in +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/budgets.jsonl +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/README.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/capacity-size-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/probe-loss-nominal-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/probe-loss-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/probe-loss-tight-fastdds.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/calibration/probe-loss-tight-zenoh.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/codecov.yml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/benchmark-ab.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/benchmark-bag-as-load.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/ci.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/ffmpeg-keyframes.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/README.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/delay-alone-no-loss.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/head-of-line-irregularity.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/lighter-message-loss.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/oversubscription-queues-not-losses.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/reorder-becomes-reader-loss.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/repro/decode_chain_repro.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/repro/reorder_drop_repro.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/forensics-report.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/geo-link-map.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/github-repository-settings.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/link-bandwidth.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/link-trace.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/owner-runbook.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/performance-bands.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/playbooks/README.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/quality-model.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/release-notes/TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/release-notes/v2.1.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/release-notes/v2.2.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/release-notes/v2.3.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/release-notes/v2.4.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/release.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0002-expectation-concepts.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0003-metric-backbone.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0004-network-profiles.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0006-dynamic-qos.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/0007-regression-gate.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/rfcs/README.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/testing.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/docs/work-items.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/install.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/justfile +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/media/control_center.png +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/media/echo_test.png +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/media/forensics-report-example.png +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/media/host.png +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/media/vehicle.png +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/pyproject.toml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/requirements.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/__init__.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/__main__.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/anonymize.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/benched_set.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/benchmark.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/deployment.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/forensics.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/geomap.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/image_cache.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/plots.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/py.typed +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/benched-set.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/.gitignore +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/README.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/__init__.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/profiles.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ros2docker.json.example +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/status_eval.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/transit.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom/video_quality.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/requires.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/terminology.md +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_benched_set_registry.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_findings.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_markdown_links.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_ota_source_bundle.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_package_metadata.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_packaged_resources.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_profile_covers_generated_commands.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_pytest_policy.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_readme_examples.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_security_maintenance_config.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_session_configuration_doc.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_session_template_substitution.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/contract/test_workflow_contracts.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/conftest.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_anonymize_e2e.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_benchmark_ab.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_benchmark_capacity.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_benchmark_replay.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_parallel_smoke.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_smoke.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_timeline_stepping.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/e2e/test_video_quality_e2e.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/generate_rmw_matrix.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_anonymize.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_benched_set.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_catmux_pane_logging.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_container_shaping.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_deployment.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_field_profiles.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_forensics.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_geomap.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_image_cache.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_link_trace.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_ota_link_configs.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_playout_pacer.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_plots.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_pub_sub_pair_types.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_qos_fallback_logging.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_relay_optional_parameters.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_status_eval.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_status_overview.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_status_overview_observer.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/tests/unit/test_transit.py +0 -0
- {rosotacom_dev-2.5.dev52 → rosotacom_dev-2.5.dev54}/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.
|
|
3
|
+
Version: 2.5.dev54
|
|
4
4
|
Summary: Host CLI for ROS OTA communication sessions.
|
|
5
5
|
Author: develNor
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -309,6 +309,24 @@ rosotacom start 1_heartbeat --identity b \
|
|
|
309
309
|
--peer-address a=10.0.0.10 --peer-address b=10.0.0.11
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
+
Before `start` stops a conflicting container, writes an instance, or builds an
|
|
313
|
+
image, it verifies that the selected identity's address belongs to this host and
|
|
314
|
+
that the route to the other peer selects that address as its source. A missing
|
|
315
|
+
VPN/data-plane interface or a route through the wrong network therefore fails
|
|
316
|
+
without disturbing a working session. Run the same check on its own with
|
|
317
|
+
`preflight`; deployments that require the other host to be online already can
|
|
318
|
+
also make three bounded ICMP probes mandatory:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
rosotacom preflight 1_heartbeat --identity a \
|
|
322
|
+
--peer-address a=10.0.0.10 --peer-address b=10.0.0.11 \
|
|
323
|
+
--require-peer-reachable
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Peer reachability is opt-in because some valid networks block ICMP and peers may
|
|
327
|
+
be started in either order. `rosotacom start` accepts the same
|
|
328
|
+
`--require-peer-reachable` policy when a deployment does require it.
|
|
329
|
+
|
|
312
330
|
`rosotacom` reads the static session input and creates generated files under `session-instances/<date>/<session>_<timestamp>_<id>/config/`, including per-peer plugin/session specs, topic lists, optional QoS, and optional `domain_bridge.yaml`. Catmux pane output is logged under the same instance in `logs/<peer>/catmux/`.
|
|
313
331
|
|
|
314
332
|
What else a run leaves under `logs/<peer>/`, and which file to open first when a
|
|
@@ -264,6 +264,24 @@ rosotacom start 1_heartbeat --identity b \
|
|
|
264
264
|
--peer-address a=10.0.0.10 --peer-address b=10.0.0.11
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
+
Before `start` stops a conflicting container, writes an instance, or builds an
|
|
268
|
+
image, it verifies that the selected identity's address belongs to this host and
|
|
269
|
+
that the route to the other peer selects that address as its source. A missing
|
|
270
|
+
VPN/data-plane interface or a route through the wrong network therefore fails
|
|
271
|
+
without disturbing a working session. Run the same check on its own with
|
|
272
|
+
`preflight`; deployments that require the other host to be online already can
|
|
273
|
+
also make three bounded ICMP probes mandatory:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
rosotacom preflight 1_heartbeat --identity a \
|
|
277
|
+
--peer-address a=10.0.0.10 --peer-address b=10.0.0.11 \
|
|
278
|
+
--require-peer-reachable
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Peer reachability is opt-in because some valid networks block ICMP and peers may
|
|
282
|
+
be started in either order. `rosotacom start` accepts the same
|
|
283
|
+
`--require-peer-reachable` policy when a deployment does require it.
|
|
284
|
+
|
|
267
285
|
`rosotacom` reads the static session input and creates generated files under `session-instances/<date>/<session>_<timestamp>_<id>/config/`, including per-peer plugin/session specs, topic lists, optional QoS, and optional `domain_bridge.yaml`. Catmux pane output is logged under the same instance in `logs/<peer>/catmux/`.
|
|
268
286
|
|
|
269
287
|
What else a run leaves under `logs/<peer>/`, and which file to open first when a
|
|
@@ -85,6 +85,29 @@ Precedence is explicit:
|
|
|
85
85
|
All logical peers must resolve to addresses before a session starts. Session
|
|
86
86
|
definitions do not accept physical addresses or SSH targets.
|
|
87
87
|
|
|
88
|
+
## Data-plane preflight
|
|
89
|
+
|
|
90
|
+
`rosotacom start` checks the resolved bindings against the host before it stops
|
|
91
|
+
or creates containers:
|
|
92
|
+
|
|
93
|
+
1. The selected identity's IPv4 address must be assigned locally.
|
|
94
|
+
2. `ip route get` for the other peer must select that same address as its
|
|
95
|
+
source.
|
|
96
|
+
|
|
97
|
+
This makes an absent VPN/interface, a wrong identity, and a route escaping over
|
|
98
|
+
another network startup errors instead of silent communication failures. Inspect
|
|
99
|
+
the result without starting anything:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
rosotacom preflight SESSION --identity PEER
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Add `--require-peer-reachable` to `preflight` or `start` when this deployment
|
|
106
|
+
also requires three bounded ICMP probes to succeed. Reachability is not the
|
|
107
|
+
global default: ICMP can be intentionally filtered, and valid peers can be
|
|
108
|
+
started in either order. Docker-isolated local smoke networks use their own
|
|
109
|
+
network lifecycle and bypass this host data-plane check.
|
|
110
|
+
|
|
88
111
|
## Project selection and completion
|
|
89
112
|
|
|
90
113
|
The nearest `rosotacom.yaml` is discovered upward from the current directory.
|
|
@@ -674,6 +674,7 @@ Declaratively, add a `playout` block to the transport:
|
|
|
674
674
|
target_ms: 350 # fixed budget when adaptive: false
|
|
675
675
|
min_ms: 100 # adaptive clamp above the observed delay floor
|
|
676
676
|
max_ms: 800
|
|
677
|
+
republish: paced # paced (default) | unpaced | both
|
|
677
678
|
```
|
|
678
679
|
|
|
679
680
|
The receiving peer then runs the pacer in its own `PACE` window against the
|
|
@@ -684,6 +685,33 @@ why `playout` requires `remote_republish`: without a republish the pacer's
|
|
|
684
685
|
The sender's own preview decode (`local_republish`) is never paced — it reads
|
|
685
686
|
the local copy of what was just encoded, so there is no jitter to absorb.
|
|
686
687
|
|
|
688
|
+
##### `republish`: which stream reaches the display
|
|
689
|
+
|
|
690
|
+
Pacing under an unchanged topic name is what keeps applications working, and it
|
|
691
|
+
is also what makes the improvement invisible — nothing shows paced and unpaced
|
|
692
|
+
together. `republish` names which stream the receiver decodes:
|
|
693
|
+
|
|
694
|
+
| value | decodes | publishes |
|
|
695
|
+
|---|---|---|
|
|
696
|
+
| `paced` (default) | `<encoded>/paced` | `<encoded>/<remote_republish>` |
|
|
697
|
+
| `unpaced` | `<encoded>` | `<encoded>/<remote_republish>` |
|
|
698
|
+
| `both` | both | delivered as above, comparison on `<encoded>/unpaced/<remote_republish>` |
|
|
699
|
+
|
|
700
|
+
**The delivered name is the same in all three.** Only what feeds the decode
|
|
701
|
+
moves, so switching modes never changes what an application subscribes to.
|
|
702
|
+
|
|
703
|
+
`unpaced` still runs the pacer: `/paced` and its `budget_ms` / `queue_depth`
|
|
704
|
+
debug topics are published and recordable, they just do not reach the display.
|
|
705
|
+
That is the A/B control — measure a pacer on a live link without changing what
|
|
706
|
+
an operator sees.
|
|
707
|
+
|
|
708
|
+
`both` runs the receiver's expensive stage twice (a codec decode plus, for
|
|
709
|
+
`compressed`, a JPEG encode per frame) and adds one local topic; nothing extra
|
|
710
|
+
crosses the link. It also consumes one of the four reverse-transport slots. It
|
|
711
|
+
is a rehearsal and diagnosis switch — the comparison stream is deliberately not
|
|
712
|
+
a status pipeline stage, because it is not something the link promises to
|
|
713
|
+
deliver.
|
|
714
|
+
|
|
687
715
|
The node also runs standalone against any packet stream:
|
|
688
716
|
|
|
689
717
|
```bash
|
|
@@ -64,6 +64,7 @@ from .image_cache import (
|
|
|
64
64
|
image_fingerprint,
|
|
65
65
|
reference_tag,
|
|
66
66
|
)
|
|
67
|
+
from .network_preflight import NetworkPreflightResult, run_network_preflight
|
|
67
68
|
|
|
68
69
|
anonymize_lib = importlib.import_module("rosotacom.anonymize")
|
|
69
70
|
|
|
@@ -1125,7 +1126,7 @@ def _get_local_ipv4s() -> list[str]:
|
|
|
1125
1126
|
out = subprocess.check_output(["ip", "-o", "-4", "addr", "show"], text=True)
|
|
1126
1127
|
except Exception:
|
|
1127
1128
|
return []
|
|
1128
|
-
return
|
|
1129
|
+
return re.findall(r"\binet\s+(\d+\.\d+\.\d+\.\d+)/", out)
|
|
1129
1130
|
|
|
1130
1131
|
|
|
1131
1132
|
def _default_local_ip() -> str:
|
|
@@ -1234,6 +1235,29 @@ def _auto_identity(bindings: dict[str, PeerBinding]) -> str:
|
|
|
1234
1235
|
raise RuntimeError(f"Auto identity ambiguous: matched peers={matches} for local IPv4s={sorted(local_ips)}.")
|
|
1235
1236
|
|
|
1236
1237
|
|
|
1238
|
+
def _network_preflight(
|
|
1239
|
+
bindings: dict[str, PeerBinding],
|
|
1240
|
+
identity: str,
|
|
1241
|
+
*,
|
|
1242
|
+
require_peer_reachable: bool = False,
|
|
1243
|
+
) -> NetworkPreflightResult:
|
|
1244
|
+
return run_network_preflight(
|
|
1245
|
+
bindings,
|
|
1246
|
+
identity,
|
|
1247
|
+
local_addresses=_get_local_ipv4s(),
|
|
1248
|
+
require_peer_reachable=require_peer_reachable,
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
def _print_network_preflight(result: NetworkPreflightResult) -> None:
|
|
1253
|
+
reachability = "peer reachable" if result.peer_reachable else "route verified"
|
|
1254
|
+
print(
|
|
1255
|
+
f"Network preflight OK: {result.identity} ({result.local_address}) -> "
|
|
1256
|
+
f"{result.remote_identity} ({result.remote_address}) via {result.interface} "
|
|
1257
|
+
f"src {result.source_address}; {reachability}."
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
|
|
1237
1261
|
def _peer_com_name(peers: dict[str, Any], peer_key: str) -> str:
|
|
1238
1262
|
value = (peers.get(peer_key) or {}).get("com-name")
|
|
1239
1263
|
if value is None:
|
|
@@ -5123,10 +5147,18 @@ def start_session(args: argparse.Namespace) -> str:
|
|
|
5123
5147
|
identity = args.identity or _auto_identity(bindings)
|
|
5124
5148
|
if not args.identity:
|
|
5125
5149
|
print(f"Auto-selected identity: {identity}")
|
|
5150
|
+
network_isolated = bool(getattr(args, "network_name", None))
|
|
5151
|
+
if not network_isolated:
|
|
5152
|
+
_print_network_preflight(
|
|
5153
|
+
_network_preflight(
|
|
5154
|
+
bindings,
|
|
5155
|
+
identity,
|
|
5156
|
+
require_peer_reachable=getattr(args, "require_peer_reachable", False),
|
|
5157
|
+
)
|
|
5158
|
+
)
|
|
5126
5159
|
remote_name = _remote_peer_name(cfg, identity)
|
|
5127
5160
|
instance = _resolve_session_instance(runtime, session, getattr(args, "instance_id", None))
|
|
5128
5161
|
container_name = _container_name(remote_name, runtime, instance.instance_id)
|
|
5129
|
-
network_isolated = bool(getattr(args, "network_name", None))
|
|
5130
5162
|
if not network_isolated and not getattr(args, "skip_conflict_check", False):
|
|
5131
5163
|
matching = _matching_com_containers(runtime, remote_name)
|
|
5132
5164
|
if _fixed_com_container_name(runtime, remote_name) is not None:
|
|
@@ -5235,6 +5267,30 @@ def start_session(args: argparse.Namespace) -> str:
|
|
|
5235
5267
|
return container_name
|
|
5236
5268
|
|
|
5237
5269
|
|
|
5270
|
+
def preflight_session(args: argparse.Namespace) -> NetworkPreflightResult:
|
|
5271
|
+
runtime = _load_runtime_config(args)
|
|
5272
|
+
session = _resolve_session(args.session_dir, runtime)
|
|
5273
|
+
cfg = _effective_session_config(session.host_dir, runtime)
|
|
5274
|
+
bindings = _resolve_bindings(
|
|
5275
|
+
cfg,
|
|
5276
|
+
runtime,
|
|
5277
|
+
peer=getattr(args, "peer", None),
|
|
5278
|
+
peer_address=getattr(args, "peer_address", None),
|
|
5279
|
+
)
|
|
5280
|
+
if not args.identity and not getattr(args, "auto_identity", True):
|
|
5281
|
+
raise RuntimeError("Missing --identity. Provide --identity <peer> or allow auto identity.")
|
|
5282
|
+
identity = args.identity or _auto_identity(bindings)
|
|
5283
|
+
if not args.identity:
|
|
5284
|
+
print(f"Auto-selected identity: {identity}")
|
|
5285
|
+
result = _network_preflight(
|
|
5286
|
+
bindings,
|
|
5287
|
+
identity,
|
|
5288
|
+
require_peer_reachable=getattr(args, "require_peer_reachable", False),
|
|
5289
|
+
)
|
|
5290
|
+
_print_network_preflight(result)
|
|
5291
|
+
return result
|
|
5292
|
+
|
|
5293
|
+
|
|
5238
5294
|
def stop_session(args: argparse.Namespace) -> None:
|
|
5239
5295
|
runtime = _load_runtime_config(args)
|
|
5240
5296
|
session = _resolve_session(args.session_dir, runtime)
|
|
@@ -8668,6 +8724,11 @@ def _add_start_args(parser: argparse.ArgumentParser) -> None:
|
|
|
8668
8724
|
parser.add_argument("--rewrite-formatting", action="store_true")
|
|
8669
8725
|
_add_peer_arg(parser)
|
|
8670
8726
|
_add_peer_address_arg(parser)
|
|
8727
|
+
parser.add_argument(
|
|
8728
|
+
"--require-peer-reachable",
|
|
8729
|
+
action="store_true",
|
|
8730
|
+
help="Require three bounded ICMP probes to the peer to succeed before starting.",
|
|
8731
|
+
)
|
|
8671
8732
|
parser.add_argument("--instance-id", help="Join or create a named runtime session instance.")
|
|
8672
8733
|
parser.add_argument(
|
|
8673
8734
|
"--skip-conflict-check",
|
|
@@ -8681,6 +8742,22 @@ def _add_start_args(parser: argparse.ArgumentParser) -> None:
|
|
|
8681
8742
|
parser.set_defaults(force=True, auto_identity=True)
|
|
8682
8743
|
|
|
8683
8744
|
|
|
8745
|
+
def _add_preflight_args(parser: argparse.ArgumentParser) -> None:
|
|
8746
|
+
_add_common_config_args(parser)
|
|
8747
|
+
_add_session_arg(parser, "session_dir_positional", nargs="?")
|
|
8748
|
+
_add_session_arg(parser, "-s", "--session-dir", dest="session_dir")
|
|
8749
|
+
_add_identity_arg(parser)
|
|
8750
|
+
parser.add_argument("--no-auto-identity", dest="auto_identity", action="store_false")
|
|
8751
|
+
_add_peer_arg(parser)
|
|
8752
|
+
_add_peer_address_arg(parser)
|
|
8753
|
+
parser.add_argument(
|
|
8754
|
+
"--require-peer-reachable",
|
|
8755
|
+
action="store_true",
|
|
8756
|
+
help="Require three bounded ICMP probes to the peer to succeed.",
|
|
8757
|
+
)
|
|
8758
|
+
parser.set_defaults(auto_identity=True)
|
|
8759
|
+
|
|
8760
|
+
|
|
8684
8761
|
def _normalize_session_arg(args: argparse.Namespace) -> None:
|
|
8685
8762
|
positional = getattr(args, "session_dir_positional", None)
|
|
8686
8763
|
explicit = getattr(args, "session_dir", None)
|
|
@@ -8697,6 +8774,12 @@ def start_command(args: argparse.Namespace) -> int:
|
|
|
8697
8774
|
return 0
|
|
8698
8775
|
|
|
8699
8776
|
|
|
8777
|
+
def preflight_command(args: argparse.Namespace) -> int:
|
|
8778
|
+
_normalize_session_arg(args)
|
|
8779
|
+
preflight_session(args)
|
|
8780
|
+
return 0
|
|
8781
|
+
|
|
8782
|
+
|
|
8700
8783
|
def stop_command(args: argparse.Namespace) -> int:
|
|
8701
8784
|
_normalize_session_arg(args)
|
|
8702
8785
|
stop_session(args)
|
|
@@ -9052,6 +9135,7 @@ def videoquality_command(args: argparse.Namespace) -> int:
|
|
|
9052
9135
|
# turn into `rosotacom start foo` — so a contract test keeps the two in sync.
|
|
9053
9136
|
TOP_LEVEL_COMMANDS = {
|
|
9054
9137
|
"start",
|
|
9138
|
+
"preflight",
|
|
9055
9139
|
"stop",
|
|
9056
9140
|
"doctor",
|
|
9057
9141
|
"ps",
|
|
@@ -9127,6 +9211,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
9127
9211
|
_add_start_args(start_parser)
|
|
9128
9212
|
start_parser.set_defaults(func=start_command)
|
|
9129
9213
|
|
|
9214
|
+
preflight_parser = subparsers.add_parser(
|
|
9215
|
+
"preflight",
|
|
9216
|
+
help="Validate the resolved local address, peer route, and optional peer reachability.",
|
|
9217
|
+
)
|
|
9218
|
+
_add_preflight_args(preflight_parser)
|
|
9219
|
+
preflight_parser.set_defaults(func=preflight_command)
|
|
9220
|
+
|
|
9130
9221
|
stop_parser = subparsers.add_parser("stop", help="Stop rosotacom containers for a session.")
|
|
9131
9222
|
_add_common_config_args(stop_parser)
|
|
9132
9223
|
_add_session_arg(stop_parser, "session_dir_positional", nargs="?")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Host data-plane validation for resolved communication peers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ipaddress
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from collections.abc import Iterable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from .deployment import PeerBinding
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class NetworkPreflightResult:
|
|
16
|
+
identity: str
|
|
17
|
+
local_address: str
|
|
18
|
+
remote_identity: str
|
|
19
|
+
remote_address: str
|
|
20
|
+
interface: str
|
|
21
|
+
source_address: str
|
|
22
|
+
peer_reachable: bool | None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _binding_ipv4(binding: PeerBinding, *, role: str) -> str:
|
|
26
|
+
try:
|
|
27
|
+
return str(ipaddress.IPv4Address(binding.address))
|
|
28
|
+
except ipaddress.AddressValueError as exc:
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
f"Network preflight failed: {role} peer {binding.peer!r} has non-IPv4 address "
|
|
31
|
+
f"{binding.address!r}. Communication peer addresses must be concrete IPv4 addresses."
|
|
32
|
+
) from exc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_network_preflight(
|
|
36
|
+
bindings: dict[str, PeerBinding],
|
|
37
|
+
identity: str,
|
|
38
|
+
*,
|
|
39
|
+
local_addresses: Iterable[str],
|
|
40
|
+
require_peer_reachable: bool = False,
|
|
41
|
+
) -> NetworkPreflightResult:
|
|
42
|
+
"""Validate the selected local binding and kernel path to its peer."""
|
|
43
|
+
if identity not in bindings:
|
|
44
|
+
raise RuntimeError(f"Network preflight failed: identity {identity!r} is not one of peers={sorted(bindings)}.")
|
|
45
|
+
if len(bindings) != 2:
|
|
46
|
+
raise RuntimeError(f"Network preflight failed: expected exactly 2 peers, got peers={sorted(bindings)}.")
|
|
47
|
+
|
|
48
|
+
local = bindings[identity]
|
|
49
|
+
remote_identity = next(peer for peer in bindings if peer != identity)
|
|
50
|
+
remote = bindings[remote_identity]
|
|
51
|
+
local_address = _binding_ipv4(local, role="local")
|
|
52
|
+
remote_address = _binding_ipv4(remote, role="remote")
|
|
53
|
+
|
|
54
|
+
known_local_addresses = sorted(set(local_addresses))
|
|
55
|
+
if not known_local_addresses:
|
|
56
|
+
raise RuntimeError(
|
|
57
|
+
"Network preflight failed: could not determine any local IPv4 addresses. "
|
|
58
|
+
"Verify that the `ip` command is installed and the required data-plane interface is up."
|
|
59
|
+
)
|
|
60
|
+
if local_address not in known_local_addresses:
|
|
61
|
+
raise RuntimeError(
|
|
62
|
+
f"Network preflight failed for identity {identity!r}: configured local address {local_address} "
|
|
63
|
+
f"is not assigned to this host (local IPv4s: {known_local_addresses}). "
|
|
64
|
+
"Verify --identity and the peer bindings; if the address belongs to a VPN or other data-plane "
|
|
65
|
+
"interface, bring that interface up before starting communication."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
route = subprocess.run(
|
|
70
|
+
["ip", "-4", "route", "get", remote_address],
|
|
71
|
+
text=True,
|
|
72
|
+
capture_output=True,
|
|
73
|
+
check=False,
|
|
74
|
+
)
|
|
75
|
+
except FileNotFoundError as exc:
|
|
76
|
+
raise RuntimeError("Network preflight failed: required command `ip` was not found.") from exc
|
|
77
|
+
route_output = (route.stdout or "").strip()
|
|
78
|
+
if route.returncode != 0 or not route_output:
|
|
79
|
+
detail = (route.stderr or route.stdout or "no route returned").strip()
|
|
80
|
+
raise RuntimeError(
|
|
81
|
+
f"Network preflight failed: no usable IPv4 route from {identity!r} ({local_address}) "
|
|
82
|
+
f"to {remote_identity!r} ({remote_address}): {detail}"
|
|
83
|
+
)
|
|
84
|
+
route_line = route_output.splitlines()[0]
|
|
85
|
+
source_match = re.search(r"\bsrc\s+(\d+\.\d+\.\d+\.\d+)\b", route_line)
|
|
86
|
+
interface_match = re.search(r"\bdev\s+(\S+)", route_line)
|
|
87
|
+
if source_match is None or interface_match is None:
|
|
88
|
+
raise RuntimeError(
|
|
89
|
+
f"Network preflight failed: could not determine source address and interface from route: {route_line}"
|
|
90
|
+
)
|
|
91
|
+
source_address = source_match.group(1)
|
|
92
|
+
interface = interface_match.group(1)
|
|
93
|
+
if source_address != local_address:
|
|
94
|
+
raise RuntimeError(
|
|
95
|
+
f"Network preflight failed: route to {remote_identity!r} ({remote_address}) uses source "
|
|
96
|
+
f"{source_address} on {interface}, but identity {identity!r} is bound to {local_address}. "
|
|
97
|
+
"Fix the route or peer binding before starting communication."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
peer_reachable: bool | None = None
|
|
101
|
+
if require_peer_reachable:
|
|
102
|
+
try:
|
|
103
|
+
ping = subprocess.run(
|
|
104
|
+
["ping", "-4", "-n", "-c", "3", "-W", "2", "-I", local_address, remote_address],
|
|
105
|
+
text=True,
|
|
106
|
+
capture_output=True,
|
|
107
|
+
check=False,
|
|
108
|
+
timeout=10,
|
|
109
|
+
)
|
|
110
|
+
except FileNotFoundError as exc:
|
|
111
|
+
raise RuntimeError(
|
|
112
|
+
"Network preflight failed: strict peer reachability was requested, but `ping` was not found."
|
|
113
|
+
) from exc
|
|
114
|
+
except subprocess.TimeoutExpired as exc:
|
|
115
|
+
raise RuntimeError(
|
|
116
|
+
f"Network preflight failed: ICMP check from {local_address} to {remote_address} exceeded 10 seconds."
|
|
117
|
+
) from exc
|
|
118
|
+
if ping.returncode != 0:
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"Network preflight failed: peer {remote_identity!r} ({remote_address}) did not answer "
|
|
121
|
+
f"3 ICMP echo requests sent from {local_address} via {interface}. This start requires an "
|
|
122
|
+
"already-reachable peer; verify the data plane, VPN, and firewall."
|
|
123
|
+
)
|
|
124
|
+
peer_reachable = True
|
|
125
|
+
|
|
126
|
+
return NetworkPreflightResult(
|
|
127
|
+
identity=identity,
|
|
128
|
+
local_address=local_address,
|
|
129
|
+
remote_identity=remote_identity,
|
|
130
|
+
remote_address=remote_address,
|
|
131
|
+
interface=interface,
|
|
132
|
+
source_address=source_address,
|
|
133
|
+
peer_reachable=peer_reachable,
|
|
134
|
+
)
|
|
@@ -230,22 +230,32 @@ parameters:
|
|
|
230
230
|
it_4_foxglove_qmax: -1
|
|
231
231
|
it_4_outgoing_suffix: # e.g. compressed (defaults to it_4_transport if not set)
|
|
232
232
|
|
|
233
|
+
# `irt_N_out_topic` overrides where a decode publishes. It exists so two slots
|
|
234
|
+
# can decode the *same* stream — the paced one under the name applications
|
|
235
|
+
# already subscribe to, an unpaced copy next to it for comparison
|
|
236
|
+
# (transport.playout.republish: both). Left unset, a decode publishes on
|
|
237
|
+
# ${irt_N_topic}/${irt_N_out_transport}, which is the only shape any session
|
|
238
|
+
# had before and the one every unpaced link still has.
|
|
233
239
|
irt: false
|
|
234
240
|
irt_1_topic: # e.g. /sensors/camera/front_wide/image_color
|
|
235
241
|
irt_1_transport: # e.g. raw
|
|
236
242
|
irt_1_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
243
|
+
irt_1_out_topic: # overrides the output remap; defaults to ${irt_1_topic}/${irt_1_out_transport}
|
|
237
244
|
irt_1_paced: # "true" -> decode reads ${irt_1_topic}/paced (playout pacer, PACE window)
|
|
238
245
|
irt_2_topic: # e.g. /sensors/front_camera/image_raw/compressed
|
|
239
246
|
irt_2_transport: # e.g. raw
|
|
240
247
|
irt_2_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
248
|
+
irt_2_out_topic: # overrides the output remap; defaults to ${irt_2_topic}/${irt_2_out_transport}
|
|
241
249
|
irt_2_paced: # "true" -> decode reads ${irt_2_topic}/paced
|
|
242
250
|
irt_3_topic: # e.g. /sensor/front_camera/image_color/compressed
|
|
243
251
|
irt_3_transport: # e.g. raw
|
|
244
252
|
irt_3_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
253
|
+
irt_3_out_topic: # overrides the output remap; defaults to ${irt_3_topic}/${irt_3_out_transport}
|
|
245
254
|
irt_3_paced: # "true" -> decode reads ${irt_3_topic}/paced
|
|
246
255
|
irt_4_topic: # e.g. /sensor/front_camera/image_color/compressed
|
|
247
256
|
irt_4_transport: # e.g. raw
|
|
248
257
|
irt_4_out_transport: compressed # e.g. raw, compressed – determines the output remap format
|
|
258
|
+
irt_4_out_topic: # overrides the output remap; defaults to ${irt_4_topic}/${irt_4_out_transport}
|
|
249
259
|
irt_4_paced: # "true" -> decode reads ${irt_4_topic}/paced
|
|
250
260
|
|
|
251
261
|
# Receiver-side playout pacing (transport.playout in the session definition):
|
|
@@ -707,31 +717,35 @@ windows:
|
|
|
707
717
|
- if [ '${irt_1_topic}' = "None" ]; then exit; fi
|
|
708
718
|
- if [[ "${irt_1_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_1_out_transport}"; fi
|
|
709
719
|
- if [[ "${irt_1_paced}" != "None" ]]; then irt_in_topic="${irt_1_topic}/paced"; else irt_in_topic="${irt_1_topic}"; fi
|
|
720
|
+
- if [[ "${irt_1_out_topic}" != "None" ]]; then irt_out_topic="${irt_1_out_topic}"; else irt_out_topic="${irt_1_topic}/${irt_1_out_transport}"; fi
|
|
710
721
|
- 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}
|
|
711
|
-
--remap in/${irt_1_transport}:=${irt_in_topic} --remap ${out_remap}:=${
|
|
722
|
+
--remap in/${irt_1_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_out_topic})
|
|
712
723
|
- echo ${args[@]}
|
|
713
724
|
- ${args[@]}
|
|
714
725
|
- commands:
|
|
715
726
|
- if [ '${irt_2_topic}' = "None" ]; then exit; fi
|
|
716
727
|
- if [[ "${irt_2_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_2_out_transport}"; fi
|
|
717
728
|
- if [[ "${irt_2_paced}" != "None" ]]; then irt_in_topic="${irt_2_topic}/paced"; else irt_in_topic="${irt_2_topic}"; fi
|
|
729
|
+
- if [[ "${irt_2_out_topic}" != "None" ]]; then irt_out_topic="${irt_2_out_topic}"; else irt_out_topic="${irt_2_topic}/${irt_2_out_transport}"; fi
|
|
718
730
|
- 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}
|
|
719
|
-
--remap in/${irt_2_transport}:=${irt_in_topic} --remap ${out_remap}:=${
|
|
731
|
+
--remap in/${irt_2_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_out_topic})
|
|
720
732
|
- echo ${args[@]}
|
|
721
733
|
- ${args[@]}
|
|
722
734
|
- commands:
|
|
723
735
|
- if [ '${irt_3_topic}' = "None" ]; then exit; fi
|
|
724
736
|
- if [[ "${irt_3_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_3_out_transport}"; fi
|
|
725
737
|
- if [[ "${irt_3_paced}" != "None" ]]; then irt_in_topic="${irt_3_topic}/paced"; else irt_in_topic="${irt_3_topic}"; fi
|
|
738
|
+
- if [[ "${irt_3_out_topic}" != "None" ]]; then irt_out_topic="${irt_3_out_topic}"; else irt_out_topic="${irt_3_topic}/${irt_3_out_transport}"; fi
|
|
726
739
|
- 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}
|
|
727
|
-
--remap in/${irt_3_transport}:=${irt_in_topic} --remap ${out_remap}:=${
|
|
740
|
+
--remap in/${irt_3_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_out_topic})
|
|
728
741
|
- echo ${args[@]}
|
|
729
742
|
- ${args[@]}
|
|
730
743
|
- commands:
|
|
731
744
|
- if [ '${irt_4_topic}' = "None" ]; then exit; fi
|
|
732
745
|
- if [[ "${irt_4_out_transport}" == "raw" ]]; then out_remap="out"; else out_remap="out/${irt_4_out_transport}"; fi
|
|
733
746
|
- if [[ "${irt_4_paced}" != "None" ]]; then irt_in_topic="${irt_4_topic}/paced"; else irt_in_topic="${irt_4_topic}"; fi
|
|
747
|
+
- if [[ "${irt_4_out_topic}" != "None" ]]; then irt_out_topic="${irt_4_out_topic}"; else irt_out_topic="${irt_4_topic}/${irt_4_out_transport}"; fi
|
|
734
748
|
- 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}
|
|
735
|
-
--remap in/${irt_4_transport}:=${irt_in_topic} --remap ${out_remap}:=${
|
|
749
|
+
--remap in/${irt_4_transport}:=${irt_in_topic} --remap ${out_remap}:=${irt_out_topic})
|
|
736
750
|
- echo ${args[@]}
|
|
737
751
|
- ${args[@]}
|
|
@@ -101,6 +101,44 @@ class TransportSpec:
|
|
|
101
101
|
playout: Optional[Dict[str, Any]] = None
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
#: What `transport.playout.republish` may say, and what each answer decodes.
|
|
105
|
+
#: The delivered topic name is the same in all three -- only what feeds the
|
|
106
|
+
#: decode moves, and `both` adds a second decode under a name of its own.
|
|
107
|
+
PLAYOUT_REPUBLISH_MODES = ("paced", "unpaced", "both")
|
|
108
|
+
PLAYOUT_REPUBLISH_DEFAULT = "paced"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def playout_republish(playout: Dict[str, Any]) -> str:
|
|
112
|
+
"""Which stream the receiver decodes, for a link that declares `playout`.
|
|
113
|
+
|
|
114
|
+
Only meaningful where a pacer runs. A link without `playout` has no paced
|
|
115
|
+
copy to read, so callers decide that case before asking -- passing None here
|
|
116
|
+
would answer 'paced' for a stream nothing paces.
|
|
117
|
+
"""
|
|
118
|
+
return str((playout or {}).get("republish", PLAYOUT_REPUBLISH_DEFAULT))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class IrtSlot:
|
|
123
|
+
"""One reverse-transport decode: what it reads, and where it publishes.
|
|
124
|
+
|
|
125
|
+
Four of these fit in the base plugin. Most links use one per direction, but
|
|
126
|
+
a comparison decode (`playout.republish: both`) is a second slot on the same
|
|
127
|
+
topic -- which is the only reason `out_topic` exists, since two decodes of
|
|
128
|
+
one stream would otherwise remap onto the same output name.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
topic: str
|
|
132
|
+
ttype: str
|
|
133
|
+
out_transport: str
|
|
134
|
+
#: Set only for the slot that a pacer feeds; the pacer is configured from it.
|
|
135
|
+
playout: Optional[Dict[str, Any]] = None
|
|
136
|
+
#: Read '<topic>/paced' rather than '<topic>'.
|
|
137
|
+
reads_paced: bool = False
|
|
138
|
+
#: Override the output remap; None means '<topic>/<out_transport>'.
|
|
139
|
+
out_topic: Optional[str] = None
|
|
140
|
+
|
|
141
|
+
|
|
104
142
|
@dataclass
|
|
105
143
|
class TopicEntry:
|
|
106
144
|
base: str
|
|
@@ -1746,7 +1784,7 @@ def _compute_pipeline(
|
|
|
1746
1784
|
if playout_cfg is not None:
|
|
1747
1785
|
if not isinstance(playout_cfg, dict):
|
|
1748
1786
|
raise ValueError(f"transport.playout must be a mapping for topic '{entry.base}'")
|
|
1749
|
-
allowed = {"adaptive", "target_ms", "min_ms", "max_ms"}
|
|
1787
|
+
allowed = {"adaptive", "target_ms", "min_ms", "max_ms", "republish"}
|
|
1750
1788
|
unknown = sorted(set(playout_cfg) - allowed)
|
|
1751
1789
|
if unknown:
|
|
1752
1790
|
raise ValueError(
|
|
@@ -1769,6 +1807,15 @@ def _compute_pipeline(
|
|
|
1769
1807
|
raise ValueError(f"transport.playout.max_ms must be >= min_ms for topic '{entry.base}'")
|
|
1770
1808
|
if "adaptive" in playout_cfg and not isinstance(playout_cfg["adaptive"], bool):
|
|
1771
1809
|
raise ValueError(f"transport.playout.adaptive must be a boolean for topic '{entry.base}'")
|
|
1810
|
+
if "republish" in playout_cfg and playout_cfg["republish"] not in PLAYOUT_REPUBLISH_MODES:
|
|
1811
|
+
raise ValueError(
|
|
1812
|
+
f"transport.playout.republish for topic '{entry.base}' is "
|
|
1813
|
+
f"{playout_cfg['republish']!r}; allowed: {list(PLAYOUT_REPUBLISH_MODES)}. "
|
|
1814
|
+
"'paced' decodes the paced copy (the default), 'unpaced' runs the pacer "
|
|
1815
|
+
"without putting it on the display path, and 'both' adds a second decode "
|
|
1816
|
+
"on '<encoded>/unpaced/<transport>' for comparison. The delivered topic "
|
|
1817
|
+
"name is the same in all three."
|
|
1818
|
+
)
|
|
1772
1819
|
playout = dict(playout_cfg)
|
|
1773
1820
|
|
|
1774
1821
|
params = {k: v for k, v in t.items() if k not in ("type", "playout", *republish_keys)}
|
|
@@ -2781,11 +2828,12 @@ def func(
|
|
|
2781
2828
|
nor_items.append((base, suffix))
|
|
2782
2829
|
|
|
2783
2830
|
# Hard limits from session_plugin_base.yaml.
|
|
2784
|
-
def _assert_max(label: str, items: List[Any], limit: int) -> None:
|
|
2831
|
+
def _assert_max(label: str, items: List[Any], limit: int, note: str = "") -> None:
|
|
2785
2832
|
if len(items) > limit:
|
|
2786
2833
|
raise RuntimeError(
|
|
2787
2834
|
f"peer '{local}': too many '{label}' entries ({len(items)}). "
|
|
2788
2835
|
f"The base session plugin supports at most {limit}."
|
|
2836
|
+
+ (f" Note: {note}." if note else "")
|
|
2789
2837
|
)
|
|
2790
2838
|
|
|
2791
2839
|
_assert_max("drp", drp_items, 8)
|
|
@@ -3319,31 +3367,58 @@ def func(
|
|
|
3319
3367
|
# The sender's own preview decode is never paced: it reads the local
|
|
3320
3368
|
# copy of what it just encoded, so there is no network jitter to
|
|
3321
3369
|
# absorb. Playout pacing exists on the receiving side only.
|
|
3322
|
-
irt_all: List[
|
|
3370
|
+
irt_all: List[IrtSlot] = []
|
|
3323
3371
|
for t, tspec in irt_items_local:
|
|
3324
3372
|
assert tspec is not None
|
|
3325
|
-
irt_all.append((t, tspec.type, str(tspec.local_republish)
|
|
3326
|
-
|
|
3327
|
-
|
|
3373
|
+
irt_all.append(IrtSlot(t, tspec.type, str(tspec.local_republish)))
|
|
3374
|
+
for topic, tspec in irt_items_remote:
|
|
3375
|
+
out_transport = str(tspec.remote_republish)
|
|
3376
|
+
if tspec.playout is None:
|
|
3377
|
+
# No pacer on this link: the decode reads what arrived. Asking
|
|
3378
|
+
# it for '<topic>/paced' would give it a name nothing publishes,
|
|
3379
|
+
# which advertises an output and then never fills it.
|
|
3380
|
+
irt_all.append(IrtSlot(topic, tspec.type, out_transport))
|
|
3381
|
+
continue
|
|
3382
|
+
mode = playout_republish(tspec.playout)
|
|
3383
|
+
# The decode an application reads keeps its name in every mode; only
|
|
3384
|
+
# what feeds it moves. `unpaced` still runs the pacer -- it publishes
|
|
3385
|
+
# '<topic>/paced' and its debug topics for measurement -- and simply
|
|
3386
|
+
# does not put it on the display path.
|
|
3387
|
+
irt_all.append(
|
|
3388
|
+
IrtSlot(topic, tspec.type, out_transport, playout=tspec.playout, reads_paced=mode != "unpaced")
|
|
3389
|
+
)
|
|
3390
|
+
if mode == "both":
|
|
3391
|
+
# The comparison branch: same stream, undelayed, under a name of
|
|
3392
|
+
# its own. `<topic>/unpaced` is a valid image_transport base, so
|
|
3393
|
+
# a viewer subscribes to it exactly like the delivered one.
|
|
3394
|
+
irt_all.append(
|
|
3395
|
+
IrtSlot(topic, tspec.type, out_transport, out_topic=f"{topic}/unpaced/{out_transport}")
|
|
3396
|
+
)
|
|
3397
|
+
_assert_max(
|
|
3398
|
+
"irt",
|
|
3399
|
+
irt_all,
|
|
3400
|
+
4,
|
|
3401
|
+
note="a `playout.republish: both` comparison decode consumes a slot of its own",
|
|
3328
3402
|
)
|
|
3329
|
-
_assert_max("irt", irt_all, 4)
|
|
3330
3403
|
if irt_all:
|
|
3331
3404
|
items2 = [("irt", True)]
|
|
3332
|
-
for i,
|
|
3333
|
-
items2.append((f"irt_{i}_topic", topic))
|
|
3334
|
-
for i,
|
|
3335
|
-
items2.append((f"irt_{i}_transport", ttype))
|
|
3336
|
-
items2.append((f"irt_{i}_out_transport", out_transport))
|
|
3337
|
-
if
|
|
3405
|
+
for i, slot in enumerate(irt_all, 1):
|
|
3406
|
+
items2.append((f"irt_{i}_topic", slot.topic))
|
|
3407
|
+
for i, slot in enumerate(irt_all, 1):
|
|
3408
|
+
items2.append((f"irt_{i}_transport", slot.ttype))
|
|
3409
|
+
items2.append((f"irt_{i}_out_transport", slot.out_transport))
|
|
3410
|
+
if slot.out_topic is not None:
|
|
3411
|
+
items2.append((f"irt_{i}_out_topic", slot.out_topic))
|
|
3412
|
+
if slot.reads_paced:
|
|
3338
3413
|
# Points the decode's input remap at '<topic>/paced'; the
|
|
3339
3414
|
# pacer itself runs in the PACE window below.
|
|
3340
3415
|
items2.append((f"irt_{i}_paced", "true"))
|
|
3341
3416
|
blocks.append(PluginBlock("irt", items2))
|
|
3342
3417
|
|
|
3343
3418
|
paced_entries = [
|
|
3344
|
-
(i, topic, ttype, playout)
|
|
3345
|
-
for i,
|
|
3346
|
-
if playout is not None
|
|
3419
|
+
(i, slot.topic, slot.ttype, slot.playout)
|
|
3420
|
+
for i, slot in enumerate(irt_all, 1)
|
|
3421
|
+
if slot.playout is not None
|
|
3347
3422
|
]
|
|
3348
3423
|
if paced_entries:
|
|
3349
3424
|
items_pace: List[Tuple[str, Any]] = [("pace", True)]
|