rosotacom-dev 2.5.dev56__tar.gz → 2.5.dev58__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.dev56 → rosotacom_dev-2.5.dev58}/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/session-configuration.md +29 -2
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +17 -3
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +16 -3
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +6 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +68 -4
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +16 -10
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/status_eval.py +11 -2
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_generated_topics_have_a_producer.py +30 -4
- rosotacom_dev-2.5.dev58/tests/unit/test_playout_pacer.py +167 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_session_pipeline.py +51 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_status_eval.py +41 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_status_overview.py +124 -0
- rosotacom_dev-2.5.dev56/tests/unit/test_playout_pacer.py +0 -81
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.devcontainer/devcontainer.json.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.devcontainer/re_generate_devcontainer_json.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/CODEOWNERS +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/actions/setup-just/action.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/dependabot.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/benchmark-calibrate.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/benchmark-gate.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/dev-release.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/image-scan.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/nightly-e2e.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/pr-lightweight.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/pr-merge-gate.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.github/workflows/release.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/.pre-commit-config.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/AGENTS.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/CONCURRENCY.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/CONTRIBUTING.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/DEVELOPMENT_PRINCIPLES.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/MANIFEST.in +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/README.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/budgets.jsonl +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/README.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/capacity-size-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/probe-loss-nominal-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/probe-loss-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/probe-loss-tight-fastdds.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/calibration/probe-loss-tight-zenoh.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/codecov.yml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/deployment-configuration.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/benchmark-ab.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/benchmark-bag-as-load.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/ci.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/ffmpeg-keyframes.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/README.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/delay-alone-no-loss.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/head-of-line-irregularity.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/lighter-message-loss.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/oversubscription-queues-not-losses.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/reorder-becomes-reader-loss.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/repro/decode_chain_repro.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/repro/reorder_drop_repro.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/forensics-report.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/geo-link-map.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/github-repository-settings.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/link-bandwidth.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/link-trace.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/owner-runbook.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/performance-bands.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/playbooks/README.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/quality-model.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/release-notes/TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/release-notes/v2.1.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/release-notes/v2.2.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/release-notes/v2.3.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/release-notes/v2.4.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/release.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0002-expectation-concepts.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0003-metric-backbone.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0004-network-profiles.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0006-dynamic-qos.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/0007-regression-gate.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/rfcs/README.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/testing.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/docs/work-items.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/install.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/justfile +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/media/control_center.png +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/media/echo_test.png +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/media/forensics-report-example.png +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/media/host.png +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/media/vehicle.png +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/pyproject.toml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/requirements.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/__init__.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/__main__.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/anonymize.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/benched_set.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/benchmark.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/cli.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/deployment.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/forensics.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/geomap.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/image_cache.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/network_preflight.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/plots.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/py.typed +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/benched-set.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/.gitignore +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/README.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/__init__.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/profiles.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ros2docker.json.example +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/transit.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom/video_quality.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/SOURCES.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/requires.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/scm_file_list.json +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/terminology.md +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_benched_set_registry.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_findings.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_markdown_links.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_ota_source_bundle.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_package_metadata.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_packaged_resources.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_profile_covers_generated_commands.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_pytest_policy.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_readme_examples.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_security_maintenance_config.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_session_configuration_doc.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_session_template_substitution.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/contract/test_workflow_contracts.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/conftest.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_anonymize_e2e.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_benchmark_ab.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_benchmark_capacity.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_benchmark_replay.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_parallel_smoke.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_smoke.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_timeline_stepping.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/e2e/test_video_quality_e2e.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/generate_rmw_matrix.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_anonymize.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_benched_set.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_catmux_pane_logging.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_cli.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_concurrency.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_container_shaping.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_deployment.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_field_profiles.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_forensics.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_geomap.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_image_cache.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_link_trace.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_network_preflight.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_ota_link_configs.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_plots.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_pub_sub_pair_types.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_qos_fallback_logging.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_relay_optional_parameters.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_status_overview_observer.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_transit.py +0 -0
- {rosotacom_dev-2.5.dev56 → rosotacom_dev-2.5.dev58}/tests/unit/test_video_quality.py +0 -0
|
@@ -720,8 +720,35 @@ ros2 run com_py playout_pacer --ros-args \
|
|
|
720
720
|
-p target_ms:=350.0 -p adaptive:=true
|
|
721
721
|
```
|
|
722
722
|
|
|
723
|
-
It republishes on `<topic>/paced` plus
|
|
724
|
-
`.../paced/queue_depth`
|
|
723
|
+
It republishes on `<topic>/paced` plus three topics next to it:
|
|
724
|
+
`.../paced/budget_ms` and `.../paced/queue_depth` at 1 Hz, and
|
|
725
|
+
`.../paced/hold_ms` with every release, carrying how long *that* message was
|
|
726
|
+
actually held.
|
|
727
|
+
|
|
728
|
+
##### What the status overview does with it
|
|
729
|
+
|
|
730
|
+
A paced stage's age is the link plus a delay this session asked for, and one
|
|
731
|
+
number cannot be judged against a threshold meant for the link. So the
|
|
732
|
+
generator names the pacer's report on the stage it explains
|
|
733
|
+
(`paced_hold_topic` in `pipeline_spec.yaml`), and `status_overview` takes the
|
|
734
|
+
hold back out before classifying:
|
|
735
|
+
|
|
736
|
+
```
|
|
737
|
+
native_in FLOWING .../ffmpeg/compressed 10.0Hz 286ms(link 46+paced 240) ...
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
`latency_ms` stays the age on screen; `link_latency_ms` and `pacing_hold_ms`
|
|
741
|
+
are the split, and the quality verdict uses the former. Without this the
|
|
742
|
+
default 200 ms bad-threshold — set before anything on a link was held on
|
|
743
|
+
purpose — marks every healthy paced stream BAD, which is how an operator learns
|
|
744
|
+
to stop reading the panel.
|
|
745
|
+
|
|
746
|
+
It is the *applied* hold that is subtracted, never the configured budget: a
|
|
747
|
+
message that arrived later than its release time passes straight through, is
|
|
748
|
+
reported as held for 0 ms, and still flags. A budget-based correction would
|
|
749
|
+
credit that message a buffer it never received and quietly stop reporting a slow
|
|
750
|
+
link. Before the pacer has reported at all, nothing is subtracted — neither zero
|
|
751
|
+
nor the budget is a safe guess.
|
|
725
752
|
|
|
726
753
|
`type: foxglove` supports (CompressedVideo):
|
|
727
754
|
- `gop_size` (int)
|
|
@@ -8,7 +8,16 @@ Inserted between the OTA unwrapper and a decoder chain it turns network jitter
|
|
|
8
8
|
into a constant, bounded age instead of visible stutter.
|
|
9
9
|
|
|
10
10
|
Debug topics next to the output: ``.../paced/budget_ms`` (current delay
|
|
11
|
-
budget) and ``.../paced/queue_depth
|
|
11
|
+
budget) and ``.../paced/queue_depth``, both at 1 Hz, and ``.../paced/hold_ms``
|
|
12
|
+
— published with *every* release, carrying how long that message was actually
|
|
13
|
+
held.
|
|
14
|
+
|
|
15
|
+
``hold_ms`` exists so the status overview can tell the link's contribution from
|
|
16
|
+
this node's. Deliberately the applied hold rather than the budget: a message
|
|
17
|
+
that arrived later than its release time passes straight through and is held
|
|
18
|
+
for 0 ms, so a genuinely slow link cannot be credited with a buffer it never
|
|
19
|
+
used. Subtracting the *budget* would do exactly that, and would quietly stop
|
|
20
|
+
reporting the problem it exists to make visible.
|
|
12
21
|
"""
|
|
13
22
|
|
|
14
23
|
from __future__ import annotations
|
|
@@ -62,6 +71,7 @@ class PlayoutPacerNode(Node):
|
|
|
62
71
|
self._pub = self.create_publisher(msg_cls, out_topic, qos)
|
|
63
72
|
self._budget_pub = self.create_publisher(Float64, out_topic + "/budget_ms", 10)
|
|
64
73
|
self._depth_pub = self.create_publisher(Int32, out_topic + "/queue_depth", 10)
|
|
74
|
+
self._hold_pub = self.create_publisher(Float64, out_topic + "/hold_ms", 10)
|
|
65
75
|
self.create_subscription(msg_cls, topic, self._on_message, qos)
|
|
66
76
|
# 2 ms release tick: bounds added jitter to well below frame cadence.
|
|
67
77
|
self.create_timer(0.002, self._release_due)
|
|
@@ -72,12 +82,16 @@ class PlayoutPacerNode(Node):
|
|
|
72
82
|
now = time.time()
|
|
73
83
|
stamp = msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9
|
|
74
84
|
release = self.schedule.on_message(stamp, now)
|
|
75
|
-
self._queue.append((release, msg))
|
|
85
|
+
self._queue.append((release, msg, now))
|
|
76
86
|
|
|
77
87
|
def _release_due(self) -> None:
|
|
78
88
|
now = time.time()
|
|
79
89
|
while self._queue and self._queue[0][0] <= now:
|
|
80
|
-
self.
|
|
90
|
+
release, msg, arrival = self._queue.popleft()
|
|
91
|
+
self._pub.publish(msg)
|
|
92
|
+
# What this node added, not what it was allowed to add. Clamped
|
|
93
|
+
# because a late message's release time is already in the past.
|
|
94
|
+
self._hold_pub.publish(Float64(data=max(0.0, (release - arrival) * 1000.0)))
|
|
81
95
|
|
|
82
96
|
def _publish_debug(self) -> None:
|
|
83
97
|
self._budget_pub.publish(Float64(data=float(self.schedule.budget_ms)))
|
|
@@ -32,7 +32,10 @@ class PacerConfig:
|
|
|
32
32
|
adaptive: bool = True
|
|
33
33
|
percentile: float = 0.95 # of (arrival - stamp), window-local
|
|
34
34
|
margin_ms: float = 40.0 # headroom above the tracked percentile
|
|
35
|
-
|
|
35
|
+
#: Samples (~40 s at 10 Hz). Bounds both the tracked percentile and the
|
|
36
|
+
#: delay floor, so how fast the schedule re-anchors after the path changes
|
|
37
|
+
#: is this number: a longer window is steadier and slower to recover.
|
|
38
|
+
window: int = 400
|
|
36
39
|
|
|
37
40
|
def __post_init__(self) -> None:
|
|
38
41
|
if not 0.0 < self.percentile < 1.0:
|
|
@@ -72,8 +75,18 @@ class PlayoutSchedule:
|
|
|
72
75
|
"""
|
|
73
76
|
d = arrival_s - stamp_s
|
|
74
77
|
self._d.append(d)
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
# The minimum over the *window*, not over all time. An all-time floor
|
|
79
|
+
# can only fall, so any upward shift in `arrival - stamp` -- a clock
|
|
80
|
+
# step, an NTP correction, a route change, a looping replay -- leaves
|
|
81
|
+
# every later message past `stamp + floor + budget`, and the pacer
|
|
82
|
+
# degrades to a pass-through it can never come back from. Found on the
|
|
83
|
+
# fleet e2e, where a bag loop moved the delay by the bag's length and
|
|
84
|
+
# the centre's camera ran unpaced for the rest of the run with every
|
|
85
|
+
# panel healthy. Windowed, the same shift re-anchors within one window.
|
|
86
|
+
#
|
|
87
|
+
# O(window) per message: 400 floats at 10 Hz. A monotonic deque would
|
|
88
|
+
# make it O(1) and is not worth the machinery at these rates.
|
|
89
|
+
self._d_floor = min(self._d)
|
|
77
90
|
release = stamp_s + self._d_floor + self.budget_ms / 1000.0
|
|
78
91
|
if release < arrival_s: # already late: pass through immediately
|
|
79
92
|
release = arrival_s
|
|
@@ -71,6 +71,7 @@ from com_py.ffmpeg_flags import FFMPEG_PACKET_TYPE, parse_keyframe_flag
|
|
|
71
71
|
from com_py.link_bytes import LinkByteSampler, resolve_link_interface
|
|
72
72
|
from com_py.link_trace import LinkTraceRecorder
|
|
73
73
|
from com_py.status_overview_core import (
|
|
74
|
+
SCALAR_MSG_TYPE,
|
|
74
75
|
STARTUP_GRACE_S,
|
|
75
76
|
ClockOffsetEstimator,
|
|
76
77
|
StageObservation,
|
|
@@ -309,6 +310,11 @@ class StageObserver(Node):
|
|
|
309
310
|
"t_com_out": now_ros_s,
|
|
310
311
|
"keyframe": keyframe,
|
|
311
312
|
}
|
|
313
|
+
elif obs.type_str == SCALAR_MSG_TYPE:
|
|
314
|
+
# Not a stage: a number this node reads to interpret one. The
|
|
315
|
+
# pacing hold arrives here (`.../paced/hold_ms`); it carries no
|
|
316
|
+
# header, so there is no latency to record, only the value.
|
|
317
|
+
obs.last_value = float(msg.data)
|
|
312
318
|
else:
|
|
313
319
|
header = getattr(msg, "header", None)
|
|
314
320
|
stamp = getattr(header, "stamp", None) if header is not None else None
|
|
@@ -61,6 +61,12 @@ GOOD = "GOOD"
|
|
|
61
61
|
DEGRADED = "DEGRADED"
|
|
62
62
|
BAD = "BAD"
|
|
63
63
|
|
|
64
|
+
#: A stage whose age includes a delay this session asked for names the topic on
|
|
65
|
+
#: which the node responsible reports what it actually held. Set by the
|
|
66
|
+
#: generator for a decode that reads a playout pacer's output.
|
|
67
|
+
PACED_HOLD_KEY = "paced_hold_topic"
|
|
68
|
+
SCALAR_MSG_TYPE = "std_msgs/msg/Float64"
|
|
69
|
+
|
|
64
70
|
# Topic-level rollup
|
|
65
71
|
OK = "OK"
|
|
66
72
|
PARTIAL = "PARTIAL"
|
|
@@ -371,6 +377,9 @@ class StageObservation:
|
|
|
371
377
|
last_recv_wall: Optional[float] = None
|
|
372
378
|
last_delay_s: Optional[float] = None
|
|
373
379
|
last_raw_delay_s: Optional[float] = None
|
|
380
|
+
#: Payload of the last message, for the scalar topics this node reads as
|
|
381
|
+
#: numbers rather than observes as stages (the pacing hold).
|
|
382
|
+
last_value: Optional[float] = None
|
|
374
383
|
last_rtt_s: Optional[float] = None
|
|
375
384
|
last_clock_offset_s: Optional[float] = None
|
|
376
385
|
# (t_mono, size_bytes, delay_s_or_None)
|
|
@@ -673,6 +682,21 @@ class StatusAggregator:
|
|
|
673
682
|
self._startup_pending: Optional[Tuple[float, set]] = None
|
|
674
683
|
|
|
675
684
|
# -- observation lookup --
|
|
685
|
+
def _pacing_hold_ms(self, stage: Dict[str, Any]) -> Optional[float]:
|
|
686
|
+
"""How long this session deliberately held the last message of a stage.
|
|
687
|
+
|
|
688
|
+
None whenever the answer is not actually known -- no pacer named, or the
|
|
689
|
+
pacer has not reported yet. Guessing zero would read a paced picture as
|
|
690
|
+
a slow link; guessing the configured budget would do the opposite and
|
|
691
|
+
credit a hold that a late message never received.
|
|
692
|
+
"""
|
|
693
|
+
hold_topic = stage.get(PACED_HOLD_KEY)
|
|
694
|
+
if not hold_topic:
|
|
695
|
+
return None
|
|
696
|
+
observer = self._observers.get("local")
|
|
697
|
+
observation = observer.observations.get(str(hold_topic)) if observer else None
|
|
698
|
+
return None if observation is None else observation.last_value
|
|
699
|
+
|
|
676
700
|
def _obs_for(self, stage: Dict[str, Any]) -> Optional[StageObservation]:
|
|
677
701
|
domain = stage.get("domain", "local")
|
|
678
702
|
observer = self._observers.get(domain) or self._observers.get("local")
|
|
@@ -714,6 +738,9 @@ class StatusAggregator:
|
|
|
714
738
|
"observation": "graph" if obs is not None and obs.graph_only else "payload",
|
|
715
739
|
"inferred_from": None,
|
|
716
740
|
"latched": str((expect or {}).get("mode", "")).strip().lower() == "latched",
|
|
741
|
+
# Split of `latency_ms` for a stage this session delays on purpose.
|
|
742
|
+
"pacing_hold_ms": None,
|
|
743
|
+
"link_latency_ms": None,
|
|
717
744
|
}
|
|
718
745
|
if obs is None:
|
|
719
746
|
return result
|
|
@@ -727,6 +754,10 @@ class StatusAggregator:
|
|
|
727
754
|
result["age_s"] = round(m["age_s"], 3) if m["age_s"] is not None else None
|
|
728
755
|
if m["last_delay_s"] is not None:
|
|
729
756
|
result["latency_ms"] = round(m["last_delay_s"] * 1000.0, 1)
|
|
757
|
+
hold_ms = self._pacing_hold_ms(stage)
|
|
758
|
+
if hold_ms is not None:
|
|
759
|
+
result["pacing_hold_ms"] = round(hold_ms, 1)
|
|
760
|
+
result["link_latency_ms"] = round(max(0.0, result["latency_ms"] - hold_ms), 1)
|
|
730
761
|
if m["last_raw_delay_s"] is not None:
|
|
731
762
|
result["latency_uncorrected_ms"] = round(m["last_raw_delay_s"] * 1000.0, 1)
|
|
732
763
|
if m["last_rtt_s"] is not None:
|
|
@@ -759,7 +790,9 @@ class StatusAggregator:
|
|
|
759
790
|
return result
|
|
760
791
|
|
|
761
792
|
result["state"] = FLOWING
|
|
762
|
-
quality, reason = self._classify_quality(
|
|
793
|
+
quality, reason = self._classify_quality(
|
|
794
|
+
m, expected_hz, expect, pacing_hold_ms=result["pacing_hold_ms"]
|
|
795
|
+
)
|
|
763
796
|
result["quality"] = quality
|
|
764
797
|
result["quality_reason"] = reason
|
|
765
798
|
return result
|
|
@@ -823,7 +856,12 @@ class StatusAggregator:
|
|
|
823
856
|
self._copy_inferred_activity(stage_result, source)
|
|
824
857
|
|
|
825
858
|
def _classify_quality(
|
|
826
|
-
self,
|
|
859
|
+
self,
|
|
860
|
+
m: Dict[str, Any],
|
|
861
|
+
expected_hz: Optional[float],
|
|
862
|
+
expect: Optional[Dict[str, Any]] = None,
|
|
863
|
+
*,
|
|
864
|
+
pacing_hold_ms: Optional[float] = None,
|
|
827
865
|
) -> Tuple[str, Optional[str]]:
|
|
828
866
|
expect = expect or {}
|
|
829
867
|
hz_exp = expect.get("hz") or {}
|
|
@@ -833,8 +871,17 @@ class StatusAggregator:
|
|
|
833
871
|
# Latency: a declared `expect.latency_ms.max` overrides the global bad
|
|
834
872
|
# threshold; the good band stays the default so a tight contract still
|
|
835
873
|
# surfaces a DEGRADED before BAD where it makes sense.
|
|
874
|
+
#
|
|
875
|
+
# A stage the session delays on purpose is judged on what is left after
|
|
876
|
+
# that delay is taken back out. Otherwise a threshold calibrated for a
|
|
877
|
+
# link measures a buffer, which reads as a fault on a healthy stream and
|
|
878
|
+
# teaches an operator to stop believing the panel. The subtracted value
|
|
879
|
+
# is the hold actually applied, so a late message -- which the pacer
|
|
880
|
+
# passes straight through -- is credited nothing and still flags.
|
|
836
881
|
bad_lat = float(lat_exp["max"]) if "max" in lat_exp else self._delay_bad_ms
|
|
837
882
|
delay_ms = (m["last_delay_s"] * 1000.0) if m["last_delay_s"] is not None else None
|
|
883
|
+
if delay_ms is not None and pacing_hold_ms is not None:
|
|
884
|
+
delay_ms = max(0.0, delay_ms - pacing_hold_ms)
|
|
838
885
|
if delay_ms is not None:
|
|
839
886
|
if delay_ms >= bad_lat:
|
|
840
887
|
return BAD, "latency"
|
|
@@ -1142,7 +1189,16 @@ class StatusAggregator:
|
|
|
1142
1189
|
if st["state"] in (FLOWING, STALE):
|
|
1143
1190
|
parts = [f"{st['hz']:.1f}Hz"]
|
|
1144
1191
|
if st["latency_ms"] is not None:
|
|
1145
|
-
|
|
1192
|
+
# A paced stage reads as one big number unless the split
|
|
1193
|
+
# is on the same line: what the link cost, and what this
|
|
1194
|
+
# session chose to add on top of it.
|
|
1195
|
+
if st.get("pacing_hold_ms") is not None:
|
|
1196
|
+
parts.append(
|
|
1197
|
+
f"{st['latency_ms']:.0f}ms"
|
|
1198
|
+
f"(link {st['link_latency_ms']:.0f}+paced {st['pacing_hold_ms']:.0f})"
|
|
1199
|
+
)
|
|
1200
|
+
else:
|
|
1201
|
+
parts.append(f"{st['latency_ms']:.0f}ms")
|
|
1146
1202
|
elif st["latency_uncorrected_ms"] is not None:
|
|
1147
1203
|
parts.append(f"{st['latency_uncorrected_ms']:.0f}ms(raw)")
|
|
1148
1204
|
if st["loss_pct"] is not None:
|
|
@@ -1298,7 +1354,12 @@ class StatusAggregator:
|
|
|
1298
1354
|
|
|
1299
1355
|
|
|
1300
1356
|
def collect_stage_topics(spec: Dict[str, Any]) -> Dict[str, Dict[str, Optional[str]]]:
|
|
1301
|
-
"""Return {domain: {topic: type_hint}} from the pipeline spec.
|
|
1357
|
+
"""Return {domain: {topic: type_hint}} from the pipeline spec.
|
|
1358
|
+
|
|
1359
|
+
Includes the pacing-hold topics a stage names: they are not stages of the
|
|
1360
|
+
link -- nothing is delivered on them -- but the aggregator has to read them
|
|
1361
|
+
to tell this session's deliberate delay from the network's.
|
|
1362
|
+
"""
|
|
1302
1363
|
by_domain: Dict[str, Dict[str, Optional[str]]] = {"local": {}, "ota": {}}
|
|
1303
1364
|
for topic_spec in spec.get("topics", []):
|
|
1304
1365
|
type_hint = topic_spec.get("type")
|
|
@@ -1307,6 +1368,9 @@ def collect_stage_topics(spec: Dict[str, Any]) -> Dict[str, Dict[str, Optional[s
|
|
|
1307
1368
|
by_domain.setdefault(domain, {})
|
|
1308
1369
|
existing = by_domain[domain].get(stage["topic"])
|
|
1309
1370
|
by_domain[domain][stage["topic"]] = existing or stage.get("type") or type_hint
|
|
1371
|
+
hold_topic = stage.get(PACED_HOLD_KEY)
|
|
1372
|
+
if hold_topic:
|
|
1373
|
+
by_domain["local"].setdefault(str(hold_topic), SCALAR_MSG_TYPE)
|
|
1310
1374
|
return by_domain
|
|
1311
1375
|
|
|
1312
1376
|
|
|
@@ -1666,17 +1666,23 @@ def _build_status_pipeline_spec(
|
|
|
1666
1666
|
if postprocessed != final:
|
|
1667
1667
|
native_in_type = base_type
|
|
1668
1668
|
transport = p.get("transport")
|
|
1669
|
+
native_in: Dict[str, Any] = {
|
|
1670
|
+
"stage": "native_in",
|
|
1671
|
+
"topic": _relay_in_local_topic(postprocessed),
|
|
1672
|
+
"type": native_in_type,
|
|
1673
|
+
"domain": "local",
|
|
1674
|
+
"produced_by": "postprocessing",
|
|
1675
|
+
}
|
|
1669
1676
|
if isinstance(transport, TransportSpec) and transport.remote_republish:
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
"
|
|
1677
|
-
"
|
|
1678
|
-
|
|
1679
|
-
)
|
|
1677
|
+
native_in["type"] = REPUBLISH_OUTPUT_TYPES[transport.remote_republish]
|
|
1678
|
+
if transport.playout is not None and playout_republish(transport.playout) != "unpaced":
|
|
1679
|
+
# This stage's age is the link plus a hold this session
|
|
1680
|
+
# asked for. The pacer reports the hold it applied; naming
|
|
1681
|
+
# the topic here is what lets the status separate the two
|
|
1682
|
+
# instead of reading a deliberate buffer as a fault.
|
|
1683
|
+
paced = _relay_in_local_topic(str(p.get("irt_in"))) + "/paced"
|
|
1684
|
+
native_in["paced_hold_topic"] = f"{paced}/hold_ms"
|
|
1685
|
+
stages.append(native_in)
|
|
1680
1686
|
|
|
1681
1687
|
topics.append(
|
|
1682
1688
|
{
|
|
@@ -265,10 +265,19 @@ def _check_expect(
|
|
|
265
265
|
failures.append(f"[{peer}] {base}: latency_ms.stage '{stage_name}' not in pipeline")
|
|
266
266
|
return failures
|
|
267
267
|
lat_stage = named
|
|
268
|
-
|
|
268
|
+
# A stage the session delays on purpose reports the split, and `expect` is
|
|
269
|
+
# about what the *link* delivers -- the buffer is already bounded by the
|
|
270
|
+
# `playout` block that asked for it. The live status classifies on the same
|
|
271
|
+
# number, and the two must not disagree about one contract: that is how a
|
|
272
|
+
# session ends up passing `rosotacom test` while its own panel reads BAD.
|
|
273
|
+
lat = lat_stage.get("link_latency_ms")
|
|
274
|
+
measured = "link latency"
|
|
275
|
+
if lat is None:
|
|
276
|
+
lat = lat_stage.get("latency_ms")
|
|
277
|
+
measured = "latency"
|
|
269
278
|
if "max" in lat_exp and (lat is None or lat > lat_exp["max"]):
|
|
270
279
|
where = f" at '{stage_name}'" if stage_name else ""
|
|
271
|
-
failures.append(f"[{peer}] {base}:
|
|
280
|
+
failures.append(f"[{peer}] {base}: {measured} {lat}ms{where} > expected max {lat_exp['max']}ms")
|
|
272
281
|
|
|
273
282
|
loss_exp = expect.get("loss_pct") or {}
|
|
274
283
|
if "max" in loss_exp:
|
|
@@ -218,7 +218,7 @@ def touches(params: dict, peer_dir: Path) -> Touches:
|
|
|
218
218
|
for index in _slots(params, "pace_{}_topic", limit=4):
|
|
219
219
|
topic = _one(params, f"pace_{index}_topic")
|
|
220
220
|
result.through(str(topic), f"{topic}/paced")
|
|
221
|
-
result.writes.update({f"{topic}/paced/budget_ms", f"{topic}/paced/queue_depth"})
|
|
221
|
+
result.writes.update({f"{topic}/paced/budget_ms", f"{topic}/paced/queue_depth", f"{topic}/paced/hold_ms"})
|
|
222
222
|
|
|
223
223
|
# NOR: relays a prefixed, suffixed name back onto the bare base, so a local
|
|
224
224
|
# consumer (a TF tree) finds the name it expects.
|
|
@@ -245,14 +245,35 @@ def touches(params: dict, peer_dir: Path) -> Touches:
|
|
|
245
245
|
|
|
246
246
|
# Observability: reads only, never republishes.
|
|
247
247
|
result.reads.update(_many(params, "metric_stage_topics"))
|
|
248
|
+
# STAT: the status overview reads the pacing hold a stage names, to tell
|
|
249
|
+
# this session's deliberate delay from the network's. It is configured out
|
|
250
|
+
# of the pipeline spec rather than out of these parameters, but it is a read
|
|
251
|
+
# by a node this peer starts, so it belongs here -- a hold topic no pacer
|
|
252
|
+
# publishes would leave the status silently subtracting nothing.
|
|
253
|
+
result.reads.update(paced_hold_topics(peer_dir))
|
|
248
254
|
|
|
249
255
|
return result
|
|
250
256
|
|
|
251
257
|
|
|
258
|
+
def _spec(peer_dir: Path) -> dict:
|
|
259
|
+
path = peer_dir / "pipeline_spec.yaml"
|
|
260
|
+
if not path.is_file():
|
|
261
|
+
return {}
|
|
262
|
+
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _stages(peer_dir: Path) -> Iterator[dict]:
|
|
266
|
+
for topic in _spec(peer_dir).get("topics", []):
|
|
267
|
+
yield from topic.get("stages", [])
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def paced_hold_topics(peer_dir: Path) -> set[str]:
|
|
271
|
+
return {stage["paced_hold_topic"] for stage in _stages(peer_dir) if stage.get("paced_hold_topic")}
|
|
272
|
+
|
|
273
|
+
|
|
252
274
|
def spec_topics(peer_dir: Path) -> set[str]:
|
|
253
275
|
"""Every topic this peer's pipeline spec models, at any stage."""
|
|
254
|
-
|
|
255
|
-
return {stage["topic"] for topic in spec.get("topics", []) for stage in topic.get("stages", [])}
|
|
276
|
+
return {stage["topic"] for stage in _stages(peer_dir)}
|
|
256
277
|
|
|
257
278
|
|
|
258
279
|
# ---------------------------------------------------------------------------
|
|
@@ -352,7 +373,12 @@ def test_the_composed_names_are_the_ones_the_template_composes() -> None:
|
|
|
352
373
|
# reads that name, and `touches` claims those two writes, so all three have
|
|
353
374
|
# to agree -- they are three files apart and nothing else compares them.
|
|
354
375
|
pacer = PACER_PY.read_text(encoding="utf-8")
|
|
355
|
-
for composed in (
|
|
376
|
+
for composed in (
|
|
377
|
+
'declare_parameter("topic_suffix", "/paced")',
|
|
378
|
+
'+ "/budget_ms"',
|
|
379
|
+
'+ "/queue_depth"',
|
|
380
|
+
'+ "/hold_ms"',
|
|
381
|
+
):
|
|
356
382
|
assert composed in pacer, f"the pacer no longer composes {composed}"
|
|
357
383
|
|
|
358
384
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Playout pacer core — schedule properties on synthetic arrival traces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
12
|
+
WS_PY = REPO_ROOT / "src" / "rosotacom" / "resources" / "ws" / "ros2src" / "com_py"
|
|
13
|
+
sys.path.insert(0, str(WS_PY))
|
|
14
|
+
|
|
15
|
+
from com_py.playout_pacer_core import PacerConfig, PlayoutSchedule # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _run(schedule: PlayoutSchedule, stamps, delays):
|
|
19
|
+
releases = []
|
|
20
|
+
for stamp, d in zip(stamps, delays, strict=True):
|
|
21
|
+
releases.append(schedule.on_message(stamp, stamp + d))
|
|
22
|
+
return releases
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_jittered_arrivals_release_on_a_regular_grid() -> None:
|
|
26
|
+
# 10 Hz stream, delay jitter far above the frame period: released cadence
|
|
27
|
+
# must be the sender cadence, not the arrival cadence.
|
|
28
|
+
rng = random.Random(1)
|
|
29
|
+
stamps = [i / 10 for i in range(300)]
|
|
30
|
+
delays = [0.030 + rng.random() * 0.120 for _ in stamps] # 30-150 ms
|
|
31
|
+
schedule = PlayoutSchedule(PacerConfig(adaptive=True, min_ms=100.0, max_ms=800.0))
|
|
32
|
+
releases = _run(schedule, stamps, delays)
|
|
33
|
+
gaps = [b - a for a, b in zip(releases[50:], releases[51:], strict=False)]
|
|
34
|
+
assert max(gaps) < 0.150 # no visible stutter (>150 ms) left
|
|
35
|
+
assert sum(1 for g in gaps if abs(g - 0.1) < 0.01) / len(gaps) > 0.95
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_late_message_passes_through_immediately_and_order_is_kept() -> None:
|
|
39
|
+
schedule = PlayoutSchedule(PacerConfig(adaptive=False, target_ms=200.0))
|
|
40
|
+
stamps = [i / 10 for i in range(50)]
|
|
41
|
+
delays = [0.030] * 50
|
|
42
|
+
delays[25] = 0.700 # one message far beyond any budget
|
|
43
|
+
releases = _run(schedule, stamps, delays)
|
|
44
|
+
# Late message is released at its arrival, not held further...
|
|
45
|
+
assert releases[25] == pytest.approx(stamps[25] + 0.700)
|
|
46
|
+
# ...and ordering is never violated.
|
|
47
|
+
assert all(b >= a for a, b in zip(releases, releases[1:], strict=False))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_budget_is_clock_offset_proof() -> None:
|
|
51
|
+
# Same jitter, sender clock shifted by -3 s: the budget must not explode.
|
|
52
|
+
rng = random.Random(2)
|
|
53
|
+
delays = [0.030 + rng.random() * 0.060 for _ in range(200)]
|
|
54
|
+
plain = PlayoutSchedule()
|
|
55
|
+
shifted = PlayoutSchedule()
|
|
56
|
+
for i, d in enumerate(delays):
|
|
57
|
+
plain.on_message(i / 10, i / 10 + d)
|
|
58
|
+
shifted.on_message(i / 10 - 3.0, i / 10 + d) # stamp 3 s in the "past"
|
|
59
|
+
assert shifted.budget_ms == pytest.approx(plain.budget_ms, rel=0.05)
|
|
60
|
+
assert shifted.budget_ms < 800.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_adaptive_budget_tracks_jitter_and_respects_clamps() -> None:
|
|
64
|
+
calm = PlayoutSchedule(PacerConfig(min_ms=100.0, max_ms=800.0, margin_ms=40.0))
|
|
65
|
+
rough = PlayoutSchedule(PacerConfig(min_ms=100.0, max_ms=800.0, margin_ms=40.0))
|
|
66
|
+
rng = random.Random(3)
|
|
67
|
+
for i in range(300):
|
|
68
|
+
calm.on_message(i / 10, i / 10 + 0.030 + rng.random() * 0.010)
|
|
69
|
+
rough.on_message(i / 10, i / 10 + 0.030 + rng.random() * 0.400)
|
|
70
|
+
assert calm.budget_ms == pytest.approx(100.0) # clamped at min
|
|
71
|
+
assert rough.budget_ms > 300.0
|
|
72
|
+
assert rough.budget_ms <= 800.0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_config_validation() -> None:
|
|
76
|
+
with pytest.raises(ValueError):
|
|
77
|
+
PacerConfig(percentile=1.5)
|
|
78
|
+
with pytest.raises(ValueError):
|
|
79
|
+
PacerConfig(min_ms=500.0, max_ms=100.0)
|
|
80
|
+
with pytest.raises(ValueError):
|
|
81
|
+
PacerConfig(window=3)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# The hold the node reports (#301).
|
|
85
|
+
#
|
|
86
|
+
# `.../paced/hold_ms` carries what this node actually added, so the status
|
|
87
|
+
# overview can take it back out of a decoded stage's age and judge what is left
|
|
88
|
+
# -- the link. The node computes `max(0, release - arrival)`; both halves of
|
|
89
|
+
# that clamp are properties of the schedule, so they are asserted here rather
|
|
90
|
+
# than in three lines of publishing glue.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _hold_ms(schedule: PlayoutSchedule, stamp: float, arrival: float) -> float:
|
|
94
|
+
"""What the node publishes for a message: the delay it applied, never negative."""
|
|
95
|
+
return max(0.0, (schedule.on_message(stamp, arrival) - arrival) * 1000.0)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_the_reported_hold_is_what_was_added_not_what_was_allowed() -> None:
|
|
99
|
+
schedule = PlayoutSchedule(PacerConfig(adaptive=False, target_ms=200.0))
|
|
100
|
+
# First message sets the floor at its own delay, so it is released at
|
|
101
|
+
# floor + 200 ms and waits exactly the budget.
|
|
102
|
+
assert _hold_ms(schedule, 0.0, 0.030) == pytest.approx(200.0, abs=1.0)
|
|
103
|
+
# One that arrives 150 ms later than the floor has already spent 150 of the
|
|
104
|
+
# budget on the wire and waits only the remainder.
|
|
105
|
+
assert _hold_ms(schedule, 1.0, 1.180) == pytest.approx(50.0, abs=1.0)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_a_late_message_is_reported_as_held_for_nothing() -> None:
|
|
109
|
+
"""The clamp: a pass-through must not be credited with a buffer it skipped."""
|
|
110
|
+
schedule = PlayoutSchedule(PacerConfig(adaptive=False, target_ms=200.0))
|
|
111
|
+
_hold_ms(schedule, 0.0, 0.030)
|
|
112
|
+
|
|
113
|
+
assert _hold_ms(schedule, 1.0, 1.400) == 0.0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# The floor has to be able to rise again (#312).
|
|
117
|
+
#
|
|
118
|
+
# Every budget is anchored to `d_floor`, the fastest path observed. Taking that
|
|
119
|
+
# over all time rather than over the window means it can only fall, so one
|
|
120
|
+
# upward shift in `arrival - stamp` puts every later message past its release
|
|
121
|
+
# time and the pacer becomes a pass-through it never recovers from. A clock step
|
|
122
|
+
# on either peer is enough; the fleet e2e's looping bag replay does it every
|
|
123
|
+
# lap, and until #301 the symptom was invisible -- node up, topic flowing,
|
|
124
|
+
# nothing re-timed.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _hold_p50(schedule: PlayoutSchedule, count: int, stamp0: float, offset: float) -> float:
|
|
128
|
+
holds = []
|
|
129
|
+
for index in range(count):
|
|
130
|
+
stamp = stamp0 + index * 0.1
|
|
131
|
+
# 10 ms most of the time, 50 ms every third message: enough jitter for
|
|
132
|
+
# an adaptive budget to have something to track.
|
|
133
|
+
arrival = stamp + offset + (0.05 if index % 3 == 0 else 0.01)
|
|
134
|
+
holds.append(max(0.0, (schedule.on_message(stamp, arrival) - arrival) * 1000.0))
|
|
135
|
+
return sorted(holds)[len(holds) // 2]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_a_delay_step_up_is_re_anchored_within_one_window() -> None:
|
|
139
|
+
schedule = PlayoutSchedule(PacerConfig(adaptive=True, min_ms=100.0, max_ms=300.0))
|
|
140
|
+
window = schedule.config.window
|
|
141
|
+
|
|
142
|
+
assert _hold_p50(schedule, 500, 0.0, 0.20) == pytest.approx(100.0, abs=5.0)
|
|
143
|
+
# The step itself is absorbed, not smoothed: those messages are genuinely
|
|
144
|
+
# late relative to what was known, and pass through.
|
|
145
|
+
_hold_p50(schedule, window, 1000.0, 161.20)
|
|
146
|
+
# One window later the floor describes the new path again.
|
|
147
|
+
assert _hold_p50(schedule, 500, 2000.0, 161.20) == pytest.approx(100.0, abs=5.0)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_a_constant_offset_is_still_absorbed_entirely() -> None:
|
|
151
|
+
"""The property the floor exists for: no clock synchronization needed."""
|
|
152
|
+
near = PlayoutSchedule(PacerConfig(adaptive=True, min_ms=100.0, max_ms=300.0))
|
|
153
|
+
far = PlayoutSchedule(PacerConfig(adaptive=True, min_ms=100.0, max_ms=300.0))
|
|
154
|
+
|
|
155
|
+
assert _hold_p50(near, 500, 0.0, 0.20) == pytest.approx(_hold_p50(far, 500, 0.0, 3600.20), abs=1.0)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_the_step_neither_drops_nor_reorders() -> None:
|
|
159
|
+
schedule = PlayoutSchedule(PacerConfig(adaptive=True, min_ms=100.0, max_ms=300.0))
|
|
160
|
+
releases = []
|
|
161
|
+
for index in range(600):
|
|
162
|
+
stamp = index * 0.1
|
|
163
|
+
offset = 0.20 if index < 300 else 161.20
|
|
164
|
+
releases.append(schedule.on_message(stamp, stamp + offset + 0.01))
|
|
165
|
+
|
|
166
|
+
assert len(releases) == 600
|
|
167
|
+
assert releases == sorted(releases)
|
|
@@ -394,3 +394,54 @@ def test_every_mode_delivers_the_same_topic_name(tmp_path: Path) -> None:
|
|
|
394
394
|
delivered.add(generator._delivered_topic(unpaced_link, unpaced_link["final"]))
|
|
395
395
|
|
|
396
396
|
assert len(delivered) == 1, delivered
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# The status has to be told which pacer belongs to which stage (#301).
|
|
400
|
+
#
|
|
401
|
+
# A decoded stage's age is the link plus a hold this session asked for, and the
|
|
402
|
+
# status overview cannot tell them apart by looking at one number. The generator
|
|
403
|
+
# knows both nodes, so it names the pacer's report on the stage it explains --
|
|
404
|
+
# rather than the status guessing from a topic-name convention.
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _native_in(receiver: str) -> dict:
|
|
408
|
+
spec = yaml.safe_load(receiver)
|
|
409
|
+
for topic in spec["topics"]:
|
|
410
|
+
if topic["direction"] == "inbound":
|
|
411
|
+
for stage in topic["stages"]:
|
|
412
|
+
if stage["stage"] == "native_in":
|
|
413
|
+
return stage
|
|
414
|
+
raise AssertionError("no inbound native_in stage")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _receiver_spec(tmp_path: Path, playout: dict | None) -> str:
|
|
418
|
+
transport: dict = {"type": "ffmpeg", "remote_republish": "compressed"}
|
|
419
|
+
if playout is not None:
|
|
420
|
+
transport["playout"] = playout
|
|
421
|
+
generator.func(
|
|
422
|
+
session_config_obj=_camera_cfg({"transport": transport, "use_ota_wrapper": True}),
|
|
423
|
+
output_dir=str(tmp_path),
|
|
424
|
+
force=True,
|
|
425
|
+
peer_addresses={"a": "127.0.0.1", "b": "127.0.0.2"},
|
|
426
|
+
)
|
|
427
|
+
return (tmp_path / "a" / "pipeline_spec.yaml").read_text(encoding="utf-8")
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def test_a_paced_decode_names_the_pacer_that_explains_its_age(tmp_path: Path) -> None:
|
|
431
|
+
stage = _native_in(_receiver_spec(tmp_path, {"adaptive": True, "max_ms": 300}))
|
|
432
|
+
|
|
433
|
+
assert stage["paced_hold_topic"] == "/camera/image/ffmpeg/paced/hold_ms"
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def test_an_unpaced_link_names_no_pacer(tmp_path: Path) -> None:
|
|
437
|
+
"""Nothing holds this stream, so nothing may be subtracted from its age."""
|
|
438
|
+
stage = _native_in(_receiver_spec(tmp_path, None))
|
|
439
|
+
|
|
440
|
+
assert "paced_hold_topic" not in stage
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_a_pacer_kept_off_the_display_path_explains_nothing(tmp_path: Path) -> None:
|
|
444
|
+
"""`republish: unpaced` decodes the arrived stream; its age is all link."""
|
|
445
|
+
stage = _native_in(_receiver_spec(tmp_path, {"adaptive": True, "republish": "unpaced"}))
|
|
446
|
+
|
|
447
|
+
assert "paced_hold_topic" not in stage
|