From 19c4adc4e289686716c58533e1d208bc30ad1ab2 Mon Sep 17 00:00:00 2001 From: Andrew Jong Date: Thu, 20 Aug 2026 18:08:09 -0400 Subject: [PATCH 1/4] P5-E1(rfc-379): reference stack folders (wrap form), --stack dispatch, single-locus lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stacks/{full_default,full_droan_cpu,full_macvo}: self-contained stack folders (modules.repos w/ airstack_compat, flat wrap-form stack.launch.xml, compose stub, README). full_macvo FIXES the broken local_macvo_obstacle_avoidance.launch.xml wiring in the stack file (correct local_-prefixed args, real perception/macvo/disparity topic, launch_macvo actually enabled) — collapses the local_* variant explosion into named stacks differing by include lines - robot.launch.xml: stack_dir/stack_entry env-default args; stack group replaces role groups when AIRSTACK_STACK_DIR set; legacy role path byte-preserved. Compose: stacks/ volume + env passthrough in robot-base - airstack up --stack [:]: host-side validation, container-path exports, effective-config lines; AUTONOMY_ROLE deprecation warning - single-locus launch lint (unit mark): no remaps outside stacks/*/launch/, frozen shrink-only allowlist (19 grandfathered files), description= required on stack args. write-launch-file skill rewritten same commit (canonical-default args, never remap in module files) - wiring mark is stack-aware: --stack makes the golden stacks//wiring.md - docs/development/stacks.md + nav; 8 new launch-intent contract tests Unit suite: 294 passed. Co-Authored-By: Claude Fable 5 --- .agents/skills/write-launch-file/SKILL.md | 394 +++++------------- airstack.sh | 61 ++- docs/development/stacks.md | 134 ++++++ mkdocs.yml | 1 + robot/docker/robot-base-docker-compose.yaml | 7 + .../autonomy_bringup/launch/robot.launch.xml | 25 +- stacks/full_default/README.md | 51 +++ stacks/full_default/docker-compose.yaml | 11 + stacks/full_default/launch/stack.launch.xml | 67 +++ stacks/full_default/modules.repos | 15 + stacks/full_droan_cpu/README.md | 44 ++ stacks/full_droan_cpu/docker-compose.yaml | 11 + stacks/full_droan_cpu/launch/stack.launch.xml | 65 +++ stacks/full_droan_cpu/modules.repos | 15 + stacks/full_macvo/README.md | 60 +++ stacks/full_macvo/docker-compose.yaml | 13 + stacks/full_macvo/launch/stack.launch.xml | 78 ++++ stacks/full_macvo/modules.repos | 16 + tests/conftest.py | 17 + tests/meta/launch_lint_allowlist.txt | 29 ++ tests/meta/test_launch_intent_contract.py | 71 ++++ tests/meta/test_launch_single_locus.py | 138 ++++++ tests/meta/test_stack_layout_contract.py | 117 ++++++ tests/system/test_wiring_snapshot.py | 29 +- 24 files changed, 1170 insertions(+), 299 deletions(-) create mode 100644 docs/development/stacks.md create mode 100644 stacks/full_default/README.md create mode 100644 stacks/full_default/docker-compose.yaml create mode 100644 stacks/full_default/launch/stack.launch.xml create mode 100644 stacks/full_default/modules.repos create mode 100644 stacks/full_droan_cpu/README.md create mode 100644 stacks/full_droan_cpu/docker-compose.yaml create mode 100644 stacks/full_droan_cpu/launch/stack.launch.xml create mode 100644 stacks/full_droan_cpu/modules.repos create mode 100644 stacks/full_macvo/README.md create mode 100644 stacks/full_macvo/docker-compose.yaml create mode 100644 stacks/full_macvo/launch/stack.launch.xml create mode 100644 stacks/full_macvo/modules.repos create mode 100644 tests/meta/launch_lint_allowlist.txt create mode 100644 tests/meta/test_launch_single_locus.py create mode 100644 tests/meta/test_stack_layout_contract.py diff --git a/.agents/skills/write-launch-file/SKILL.md b/.agents/skills/write-launch-file/SKILL.md index 5c80bc001..0850e73a6 100644 --- a/.agents/skills/write-launch-file/SKILL.md +++ b/.agents/skills/write-launch-file/SKILL.md @@ -1,6 +1,6 @@ --- name: write-launch-file -description: Author a ROS 2 launch file for AirStack with the correct conventions. Use when creating or editing any .launch.xml/.launch.py for the robot autonomy stack — covers ROBOT_NAME namespacing, topic remapping, allow_substs parameter loading, conditional launch, and layer bringup composition. +description: Author a ROS 2 launch file for AirStack with the correct conventions. Use when creating or editing any .launch.xml/.launch.py — covers the single-locus wiring rule (remaps live ONLY in stack launch files), canonical-default topic args with descriptions, ROBOT_NAME namespacing, allow_substs parameter loading, and stack-folder composition. license: Apache-2.0 metadata: author: AirLab CMU @@ -12,181 +12,108 @@ metadata: ## When to Use - Creating a launch file for a brand-new module package (paired with [add-ros2-package](../add-ros2-package)) -- Editing a layer's bringup launch file to wire in a new module (paired with [integrate-module-into-layer](../integrate-module-into-layer)) -- Adding optional/conditional behavior (e.g. `enable_rviz`, `use_alt_planner`) to an existing launch file -- Composing multiple module launch files into a higher-level launch -- Any time a module's topic names, parameters, or remappings need to change +- Wiring a module into a stack's entry launch file (`stacks//launch/*.launch.xml`) +- Adding optional/conditional behavior (e.g. `enable_rviz`) to an existing launch file +- Any time a module's topic names, parameters, or connections need to change -If you only need to tweak runtime parameters and the launch file already exists with sensible launch arguments, override via `` rather than editing the file. +If you only need different runtime wiring and the module already declares topic args, override the args from the stack launch file — never edit the module. -## Core Conventions (Read First) +## The Two Kinds of Launch File (Read First) -These are non-negotiable in AirStack: +AirStack separates launch files into exactly two roles (RFC #379 §4 — the **single-locus wiring rule**): -1. **Every robot-side topic is under `/$(env ROBOT_NAME)/...`.** Never hardcode `/drone1`, `/robot`, etc. Multi-robot scenarios depend on this. -2. **Module nodes use *relative* topic names internally** (e.g. `odometry`, `global_plan`). The launch file **remaps** them to the correct global topic. -3. **YAML config files load with `allow_substs="true"`.** Without it, `$(env ...)` and `$(var ...)` inside the YAML are not expanded. -4. **XML is the AirStack default.** Use `*.launch.xml`. Use Python (`*.launch.py`) only when you need real control flow (loops, conditional graph construction, computed values). -5. **Launch files are installed at build time.** After editing, you must rebuild the package (`bws --packages-select `) before the change takes effect. -6. **ROS 2 does NOT scope launch arguments.** A `` declared in a child launch file leaks to the parent. Prefix args with the layer/module name (e.g. `local_odometry_in_topic`) — see the warning at the top of `local.launch.xml`. +| | Module launch file | Stack launch file | +|---|---|---| +| Lives in | `/launch/` | `stacks//launch/` | +| Declares topic ``s | YES — with **canonical defaults** and a `description=` on every arg | May declare args (each needs `description=`) | +| `` / `remappings=` | **NEVER** | YES — this is the only place cross-module wiring lives | +| Hardcodes cross-module topics | NEVER | N/A — the stack file IS the wiring document | +| Format | XML default; Python only for real control flow | **XML only** — declarative, greppable, statically parseable | -## XML vs. Python Launch Files +Consequences: -| Use XML when… | Use Python when… | -|---------------|------------------| -| Launching a fixed set of nodes | You need to loop (e.g. spawn N robots from a list) | -| Static remappings, parameter files, conditional groups | You need to compute values at launch time | -| Including other launch files | You need conditional logic that XML's `if`/`unless` cannot express | -| You want a config that is easy to diff and read | You need to read JSON/YAML and generate nodes from it | +- **All cross-module wiring lives in the stack's entry launch file(s).** One flat file where every `` block reads "this module, these connections." `grep -r global_plan stacks/my_stack/` answers "who touches this." +- **A CI lint enforces it** ([`tests/meta/test_launch_single_locus.py`](../../../tests/meta/test_launch_single_locus.py)): any ``/`remappings=` outside `stacks/*/launch/` fails unless the file is in the frozen [`launch_lint_allowlist.txt`](../../../tests/meta/launch_lint_allowlist.txt). The allowlist only shrinks — never add to it. Legacy layer-bringup files still carry remaps (wrap form); they flatten into the stack files over time, deleting their allowlist lines as they go. +- **Module topic args default to the canonical names** from [docs/robot/autonomy/integration_checklist.md](../../../docs/robot/autonomy/integration_checklist.md). A module launched with zero overrides connects to a default stack correctly. -Almost every module launch file and every layer bringup launch file in `robot/ros_ws/src/` is XML. Defaulting to XML keeps the codebase consistent and reviewable. If you find yourself reaching for Python, first check whether environment variables + `` + `` can express the same thing. +Current status: **wrap form**. Reference stacks (`stacks/full_default/` etc.) include the existing layer bringups; flattening the nodes and remaps into the stack files arrives in a later phase. New wiring you write goes in the stack file, not in a module or bringup file. See [docs/development/stacks.md](../../../docs/development/stacks.md). -## Steps (Module Launch File) - -This is the typical "I just made a new package" workflow. The result is a single `/launch/.launch.xml`. - -### 1. Locate the Launch Directory - -``` -robot/ros_ws/src////launch/.launch.xml -``` +## Core Conventions -Make sure your `CMakeLists.txt` (C++) or `setup.py` (Python) installs the `launch/` directory — see [add-ros2-package](../add-ros2-package). A launch file that is not installed does not exist as far as `ros2 launch` is concerned. +1. **Every robot-side topic is under `/$(env ROBOT_NAME)/...`.** Never hardcode `/drone1`, `/robot`, etc. — multi-robot depends on this. +2. **Module nodes use *relative* topic names internally** (e.g. `odometry`, `global_plan`); the module launch file exposes them as prefixed ``s; the **stack** launch file overrides those args (or remaps) to wire modules together. +3. **Every `` gets a `description=`.** The lint requires it for stack launch files; treat it as mandatory everywhere — an undescribed arg is unwireable by the next person. +4. **YAML config files load with `allow_substs="true"`.** Without it, `$(env ...)`/`$(var ...)` inside the YAML are not expanded. +5. **ROS 2 does NOT scope launch arguments.** A child's `` collides with a sibling's. Prefix args with the module name: `my_planner_odometry_in_topic`. +6. **Launch files are installed at build time.** Rebuild (`bws --packages-select `) after editing — except stack launch files, which are read from the bind-mounted `stacks/` folder and need no build. -### 2. Declare Launch Arguments +## Steps (Module Launch File) -Put every topic name, every config path, and every toggleable feature behind an `` with a sensible default. This is what lets the layer bringup file remap your module without editing your launch file. +Result: a single `/launch/.launch.xml` that declares its interface and starts its nodes — **no remaps, no cross-module topic knowledge beyond canonical defaults**. ```xml - + + default="/$(env ROBOT_NAME)/odometry_conversion/odometry" + description="Input odometry (nav_msgs/Odometry)" /> + default="/$(env ROBOT_NAME)/global_plan" + description="Input global waypoint path (nav_msgs/Path)" /> + default="/$(env ROBOT_NAME)/trajectory_controller/trajectory_segment_to_add" + description="Output planned trajectory segment (airstack_msgs/TrajectorySegment)" /> - - - - -``` - -### 3. Launch the Node with Param + Remap + default="$(find-pkg-share my_planner)/config/my_planner.yaml" + description="Node parameter YAML" /> -```xml - - - - - - + + + + ``` -The `` direction always reads as: "the topic the node calls **X** in its source code should resolve to **Y** at runtime." - -### 4. (Optional) Add Conditional Sub-Components - -Anything that should only sometimes run goes in a ``: - -```xml - - - -``` - -Use `` for the inverse. For mutually exclusive alternatives, pair an `if` group with an `unless` group on the same arg. +If the node subscribes by relative name in code (the common ROS pattern), keep the relative names and let the **stack** launch file remap them — the prohibition is on remaps *in module files*, not on nodes using relative names. -### 5. (Optional) Push a Sub-Namespace - -If your module spawns several supporting nodes, group them under a namespace so all of their topics get a clean prefix: +Sub-namespace grouping is still fine inside a module: ```xml - - - ... - ... - -``` - -This is exactly the pattern `local.launch.xml` uses for the `droan` group and `px4_interface.launch.xml` uses for the `fmu` group. - -## Steps (Layer Bringup Launch File) - -A layer bringup file (e.g. `local_bringup/launch/local.launch.xml`) is a *composition* — it does not start a single node, it starts every node the layer needs and wires them together with shared topic args. - -### 1. Define Shared Topic Args at the Top - -Use prefixed argument names so they cannot collide with sibling layers' args: - -```xml - - - - - + + + + + ``` -### 2. For Each Module: Add a `` (or ``) Block +## Steps (Stack Launch File) -Two valid styles: +A stack folder (`stacks//`) is the unit of topology — see [docs/development/stacks.md](../../../docs/development/stacks.md) for the full anatomy. Its `launch/stack.launch.xml` is **THE wiring document**: a flat list of module ``s, each block stating that module's connections. -**Style A — direct ``** (when the module's launch file is small or you need to override most of its params): +1. **Start from a reference stack.** Copy `stacks/full_default/` and rename (`airstack stack new` arrives in a later phase). +2. **One `` per module.** Pass the module's declared topic args to wire it: ```xml - - - - - - -``` - -**Style B — `` the module's own launch file** (preferred when the module already exposes good launch args): - -```xml - - - + + + + ``` -Style B keeps wiring concerns in the bringup file and parameter concerns in the module — that's the goal. - -### 3. Wire Cross-Module Topics Using AirStack Standard Names - -Use the canonical names from AGENTS.md → "Standard Topic Patterns": - -| Topic | Where it comes from | Where it goes to | -|-------|---------------------|------------------| -| `/$(env ROBOT_NAME)/odometry_conversion/odometry` | `px4_interface` (or other interface) | every consumer (planners, controllers) | -| `/$(env ROBOT_NAME)/global_plan` | global planner | local planner | -| `/$(env ROBOT_NAME)/trajectory_controller/trajectory_segment_to_add` | local planner | trajectory controller | -| `/$(env ROBOT_NAME)/trajectory_controller/look_ahead` | trajectory controller | local planner | -| `/$(env ROBOT_NAME)/trajectory_controller/tracking_point` | trajectory controller | controllers, action servers | -| `/$(env ROBOT_NAME)/trajectory_controller/trajectory_override` | takeoff/land/fixed-traj action servers | trajectory controller | -| `/$(env ROBOT_NAME)/tasks/` | behavior tree / GCS | task executor action server | - -### 4. Add the Module to the Bringup `package.xml` - -`ros2 launch` does not need this, but `colcon build --packages-up-to ` and the install dependency tracking do. Add `my_planner` to `/package.xml`. See [integrate-module-into-layer](../integrate-module-into-layer) step 5. +3. **Remaps (when a module exposes relative names) go here and only here.** +4. **Swap a module = edit one include.** Point the include at a different package/launch file and adjust the args — nothing else changes. +5. **XML only** in stack launch files. Python launch stays available *inside* modules where genuinely needed. +6. **No rebuild needed** — `stacks/` is bind-mounted into the robot container at `/root/AirStack/stacks`; re-launch to pick up edits. +7. Run it: `airstack up --stack --sim isaac`. -### 5. Rebuild - -```bash -docker exec airstack-robot-desktop-1 bash -c "bws --packages-select my_planner" -``` +The shared per-robot preamble (ROBOT_NAME namespace push, `use_sim_time`, `robot_state_publisher`, world→map static TF) runs in `autonomy_bringup/launch/robot.launch.xml` before your stack entry file is included — do not repeat it. ## Loading YAML Config Files @@ -194,190 +121,77 @@ docker exec airstack-robot-desktop-1 bash -c "bws --packages-select ``` -`allow_substs="true"` enables `$(env VAR)` and `$(var arg)` substitution **inside the YAML file itself**. Without it, a YAML line like: - -```yaml -/**: - ros__parameters: - frame_id: $(env ROBOT_NAME)/base_link -``` - -…will load the literal string `"$(env ROBOT_NAME)/base_link"` and downstream code will fail in confusing ways. **When in doubt, set `allow_substs="true"` — there is no downside.** - -YAML structure for ROS 2 parameters (this is what your `config/.yaml` should look like): +`allow_substs="true"` enables `$(env VAR)` / `$(var arg)` substitution **inside the YAML**. Without it a line like `frame_id: $(env ROBOT_NAME)/base_link` loads as a literal string. When in doubt, set it — there is no downside. ```yaml /**: ros__parameters: update_rate: 10.0 target_frame: map - enable_visualization: true ``` -The `/**:` wildcard matches any node name, which is the most portable form when the launch file may rename the node. - -## Including Child Launch Files - -```xml - - - - -``` - -Notes: -- Use `$(find-pkg-share )` to locate launch files — never hardcode absolute paths. -- `` runs the child launch file in the parent's argument scope (remember: ROS 2 does NOT scope args). Set every argument you depend on explicitly. -- You can include `.launch.xml` from a `.launch.xml`, and either format from a `.launch.py`. +The `/**:` wildcard matches any node name — the most portable form. ## Conditional Launch Patterns -### Toggle a node on/off - ```xml - - + ``` -### Pick one of two implementations - -```xml - - - - ... - - - ... - -``` - -### Read a default from an env var - -```xml - -``` - -The second positional value to `$(env VAR default)` is the fallback when the env var is unset. +- `` for the inverse; pair `if`/`unless` on one arg for two-way alternatives. +- Env-var default: `` — the second token is the fallback when unset (`''` for an empty-string fallback). +- String comparison in conditions uses the escaped-quote eval idiom: + ``. ## Common Pitfalls -This list also appears in AGENTS.md → "Critical Pitfalls #5"; this is the expanded version. - -- **Hardcoded topic names in node code.** - - Bad: `create_subscription("/drone1/odometry", ...)` - - Good: `create_subscription("odometry", ...)` and remap in the launch file. -- **Hardcoded topic prefixes in launch files.** - - Bad: `` - - Good: `` -- **Forgetting `allow_substs="true"` on a ``.** YAML substitutions silently fail to expand. Symptom: parameters look like literal `$(env ROBOT_NAME)` strings at runtime. -- **`ROBOT_NAME` env var not set.** All `$(env ROBOT_NAME)` substitutions become an empty string, producing topic names like `//odometry`. `ROBOT_NAME` is normally resolved at container shell startup by `robot/docker/.bashrc` via `robot/docker/robot_name_map/resolve_robot_name.py` — see [configure-multi-robot](../configure-multi-robot). For ad-hoc overrides, pass `-e ROBOT_NAME=robot_X` to `docker exec`. -- **`` name collisions across layers.** Two child launch files that both define `` will silently fight. Always prefix: `local_odometry_in_topic`, `global_odometry_in_topic`, etc. -- **Missing `install(DIRECTORY launch …)` in CMakeLists.txt.** The launch file builds fine but `ros2 launch` cannot find it. Run `ls install//share//launch/` to verify after build. -- **Editing a launch file but not rebuilding.** Launch files are *installed* by `colcon build`. The source `launch/` directory is NOT what `ros2 launch` reads. Always rebuild after editing. -- **Forgetting `output="screen"`.** The node runs but its logs go to a file you have to hunt for. Use `output="screen"` on every node during development. -- **Wrong `` direction.** `from` is what the *node's source code* calls the topic; `to` is what it should resolve to at runtime. Swapping these is one of the most common silent failures. -- **Using `~/topic` without understanding it.** `~/foo` resolves to `/foo`. Useful for action server private namespaces (see `random_walk` and `takeoff_landing_planner` examples) but confusing if you don't expect it. -- **Pushing a namespace and then absolute-path remapping anyway.** `` only affects *relative* topic names. `` ignores the pushed namespace entirely. That's usually what you want, but be aware of the interaction. +- **Adding a `` to a module or layer launch file.** The single-locus lint fails CI. Wiring belongs in `stacks//launch/`. +- **Hardcoded topic names in node code.** Bad: `create_subscription("/drone1/odometry", ...)`. Good: relative `"odometry"` (stack remaps it) or a declared parameter fed by a launch arg. +- **Hardcoded topic prefixes.** Always `/$(env ROBOT_NAME)/...`, never a literal robot name. +- **Missing `description=` on an ``.** Mandatory in stack files (lint rule 3); expected everywhere. +- **Forgetting `allow_substs="true"`.** Symptom: parameters contain literal `$(env ROBOT_NAME)` at runtime. +- **`ROBOT_NAME` unset** → topics like `//odometry`. It is resolved by `robot/docker/.bashrc` via `resolve_robot_name.py`; for ad-hoc `docker exec`, pass `-e ROBOT_NAME=robot_1`. +- **`` name collisions across included files.** ROS 2 does not scope args — prefix them (`local_odometry_in_topic`, not `odometry_in_topic`). Passing a wrongly-named arg to an include fails *silently* (the legacy `local_macvo_obstacle_avoidance.launch.xml` bug — superseded by `stacks/full_macvo/`). +- **Editing a module launch file but not rebuilding.** `ros2 launch` reads the *installed* copy. (Stack launch files are exempt — bind-mounted, no build.) +- **Missing `install(DIRECTORY launch …)` in CMakeLists.txt.** Builds fine, `ros2 launch` can't find it. +- **Wrong `` direction** (stack files only): `from` = the name in the node's code, `to` = what it should resolve to. +- **`~/topic`** resolves to `/topic` — used for action-server private namespaces. ## Verification -After writing or editing the launch file, run through this checklist: - ```bash -# 1. Rebuild +# Module files: rebuild + confirm installed docker exec airstack-robot-desktop-1 bash -c "bws --packages-select " - -# 2. Confirm the file was installed docker exec airstack-robot-desktop-1 bash -c "ls install//share//launch/" -# 3. Dry-run launch to catch XML/syntax errors -docker exec airstack-robot-desktop-1 bash -c "sws && ros2 launch .launch.xml --print" - -# 4. Actually launch and watch logs -docker exec airstack-robot-desktop-1 bash -c "sws && ros2 launch .launch.xml" - -# 5. Verify the node and its topics resolved correctly -docker exec airstack-robot-desktop-1 bash -c "ros2 node list" -docker exec airstack-robot-desktop-1 bash -c "ros2 node info ///" -docker exec airstack-robot-desktop-1 bash -c "ros2 topic info //" -``` +# Static parse (host): stack files are plain XML +xmllint --noout stacks//launch/stack.launch.xml -If `ros2 node info` shows a node subscribing to `/odometry` instead of `//odometry_conversion/odometry`, your remap is wrong or `ROBOT_NAME` is unset. +# Dry-run launch to catch errors +docker exec airstack-robot-desktop-1 bash -c "sws && ros2 launch .launch.xml --print" -## Skeleton Template +# Verify actual connections +docker exec airstack-robot-desktop-1 bash -c "ros2 node info //" +docker exec airstack-robot-desktop-1 bash -c "ros2 topic info //" -Copy-paste this into `robot/ros_ws/src////launch/.launch.xml` and replace the `MY_*` / `my_*` tokens. +# The observed-truth check: snapshot the running graph and diff against the +# stack's committed wiring.md +airstack test -m wiring --stack --sim isaacsim --num-robots 1 -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# The static lint (runs in CI as a unit test) +airstack test -m unit -v ``` +If `ros2 node info` shows a node subscribing to `/odometry` instead of `//...`, the stack wiring or `ROBOT_NAME` is wrong. + ## References -- **Real launch files to study:** - - Module: `robot/ros_ws/src/interface/px4_interface/launch/px4_interface.launch.xml` - - Module: `robot/ros_ws/src/local/planners/droan_local_planner/launch/droan_local_planner.launch.xml` - - Layer bringup (global): `robot/ros_ws/src/global/global_bringup/launch/global.launch.xml` - - Layer bringup (local): `robot/ros_ws/src/local/local_bringup/launch/local.launch.xml` - - Template: `.agents/skills/add-ros2-package/assets/package_template/launch/template.launch.xml` - -- **ROS 2 docs:** - - [ROS 2 Launch Tutorials](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Launch-Main.html) - - [Using ROS 2 Launch For Large Projects](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Using-ROS2-Launch-For-Large-Projects.html) - - [Launch XML format reference](https://design.ros2.org/articles/roslaunch_xml.html) - -- **Related skills:** - - [add-ros2-package](../add-ros2-package) — full package creation flow that includes a launch file - - [integrate-module-into-layer](../integrate-module-into-layer) — wiring your launch file into a layer bringup - - [debug-module](../debug-module) — diagnosing launch and topic issues - - [test-in-simulation](../test-in-simulation) — verifying the launched stack end-to-end +- **Stacks:** [docs/development/stacks.md](../../../docs/development/stacks.md) — anatomy, wrap-vs-flatten status, wiring.md generation, AUTONOMY_ROLE deprecation +- **Canonical topic names:** [docs/robot/autonomy/integration_checklist.md](../../../docs/robot/autonomy/integration_checklist.md) +- **Reference stack launch files:** `stacks/full_default/launch/stack.launch.xml`, `stacks/full_droan_cpu/`, `stacks/full_macvo/` +- **Lint:** `tests/meta/test_launch_single_locus.py` + `tests/meta/launch_lint_allowlist.txt` +- **ROS 2 docs:** [Launch tutorials](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Launch-Main.html) · [Launch XML format](https://design.ros2.org/articles/roslaunch_xml.html) +- **Related skills:** [add-ros2-package](../add-ros2-package) · [integrate-module-into-layer](../integrate-module-into-layer) (legacy wrap-form path) · [debug-module](../debug-module) · [test-in-simulation](../test-in-simulation) diff --git a/airstack.sh b/airstack.sh index 23d1bf08b..7a0afe8a0 100755 --- a/airstack.sh +++ b/airstack.sh @@ -161,6 +161,9 @@ function print_command_help { echo "Options:" echo " --build Build images before starting containers" echo " --recreate Recreate containers even if their configuration and image haven't changed" + echo " --stack NAME Launch a stack folder (stacks/NAME/launch/stack.launch.xml) instead of" + echo " the legacy AUTONOMY_ROLE dispatch. NAME:ENTRY selects an alternate entry" + echo " file (launch/ENTRY.launch.xml). See docs/development/stacks.md." ;; images) echo "Usage: airstack images" @@ -959,6 +962,7 @@ function parse_launch_intent { AIRSTACK_INTENT_HEADLESS="" AIRSTACK_INTENT_PLAY="" AIRSTACK_INTENT_AUTOLAUNCH="" + AIRSTACK_INTENT_STACK="" AIRSTACK_DRY_RUN="" AIRSTACK_UP_WAIT="" @@ -974,6 +978,8 @@ function parse_launch_intent { --play) AIRSTACK_INTENT_PLAY="true";; --no-play) AIRSTACK_INTENT_PLAY="false";; --no-autolaunch) AIRSTACK_INTENT_AUTOLAUNCH="false";; + --stack) i=$((i+1)); AIRSTACK_INTENT_STACK="${args[$i]:-}";; + --stack=*) AIRSTACK_INTENT_STACK="${a#--stack=}";; --wait) AIRSTACK_UP_WAIT="1";; # NOTE: shadows compose's own `up --dry-run`; ours validates the # derived launch config and exits without starting services. @@ -994,9 +1000,44 @@ function parse_launch_intent { return 0 } +# Validate a --stack selection against the host stacks/ tree and export the +# CONTAINER paths the launch dispatch reads (stacks/ is bind-mounted at +# /root/AirStack/stacks by robot-base-docker-compose.yaml). Accepts the +# split-entry form `:` (entry names launch/.launch.xml; +# reserved for split stacks — RFC #380 §2). Default entry: stack. +function apply_stack_intent { + local stack_name="$AIRSTACK_INTENT_STACK" stack_entry="stack" + if [[ "$stack_name" == *:* ]]; then + stack_entry="${stack_name#*:}" + stack_name="${stack_name%%:*}" + fi + if [[ -z "$stack_name" || -z "$stack_entry" ]]; then + log_error "--stack requires a stack name (got '$AIRSTACK_INTENT_STACK'; expected or :)" + return 1 + fi + local stack_host_dir="$PROJECT_ROOT/stacks/$stack_name" + if [[ ! -d "$stack_host_dir" ]]; then + local available + available=$(ls -1 "$PROJECT_ROOT/stacks" 2>/dev/null | grep -v '^\.' | tr '\n' ' ') + log_error "Unknown stack '$stack_name' — $stack_host_dir does not exist. Available stacks: ${available:-}" + return 1 + fi + if [[ ! -f "$stack_host_dir/launch/$stack_entry.launch.xml" ]]; then + log_error "Stack '$stack_name' has no entry point launch/$stack_entry.launch.xml (expected $stack_host_dir/launch/$stack_entry.launch.xml)" + return 1 + fi + export AIRSTACK_STACK_DIR="/root/AirStack/stacks/$stack_name" + export AIRSTACK_STACK_ENTRY="$stack_entry" + return 0 +} + # Derive + export env vars from the parsed intent. Args: remaining CLI args # (scanned for --env-file when resolving current values). function apply_launch_intent { + if [[ -n "$AIRSTACK_INTENT_STACK" ]]; then + apply_stack_intent || return 1 + fi + if [[ -n "$AIRSTACK_INTENT_SIM" ]]; then local sim_profile urdf case "$AIRSTACK_INTENT_SIM" in @@ -1063,7 +1104,8 @@ function apply_launch_intent { function print_launch_config { local keys=(COMPOSE_PROFILES NUM_ROBOTS URDF_FILE AUTOLAUNCH PLAY_SIM_ON_START ISAAC_SIM_SCRIPT_NAME ISAAC_SIM_USE_STANDALONE ISAAC_SIM_HEADLESS - MS_AIRSIM_HEADLESS VERSION DOCKER_IMAGE_BUILD_MODE) + MS_AIRSIM_HEADLESS AIRSTACK_STACK_DIR AIRSTACK_STACK_ENTRY + VERSION DOCKER_IMAGE_BUILD_MODE) local k v lines=() for k in "${keys[@]}"; do v=$(resolve_launch_var "$k" "$@") @@ -1077,6 +1119,11 @@ function print_launch_config { log_info " isaac: script=$(resolve_launch_var ISAAC_SIM_SCRIPT_NAME "$@") headless=$(resolve_launch_var ISAAC_SIM_HEADLESS "$@")" fi log_info " urdf=$(resolve_launch_var URDF_FILE "$@")" + local _stack_dir + _stack_dir=$(resolve_launch_var AIRSTACK_STACK_DIR "$@") + if [[ -n "$_stack_dir" ]]; then + log_info " stack: dir=$_stack_dir entry=$(resolve_launch_var AIRSTACK_STACK_ENTRY "$@")" + fi echo "--- effective launch config ---" printf '%s\n' "${lines[@]}" @@ -1175,6 +1222,18 @@ function preflight_up { log_warn "Docker $docker_major < 29: container-name DNS resolution fails, robots will resolve as 'unknown_robot' on domain 0 (MAVROS will not connect). Upgrade Docker or set ROBOT_NAME_SOURCE=hostname." fi + # 7. Stack vs legacy AUTONOMY_ROLE dispatch. AUTONOMY_ROLE counts as + # "explicitly set" only via env / --env-file / .env — the compose files' + # own `${AUTONOMY_ROLE:-full}` default is invisible here, by design. + local _pf_stack_dir _pf_role + _pf_stack_dir=$(resolve_launch_var AIRSTACK_STACK_DIR "${_pf_global[@]}") + _pf_role=$(resolve_launch_var AUTONOMY_ROLE "${_pf_global[@]}") + if [[ -n "$_pf_role" && -z "$_pf_stack_dir" ]]; then + log_warn "AUTONOMY_ROLE is the legacy dispatch; stacks replace it in 0.21 — try: airstack up --stack full_default" + elif [[ -n "$_pf_role" && -n "$_pf_stack_dir" ]]; then + log_warn "Both a stack ($_pf_stack_dir) and AUTONOMY_ROLE=$_pf_role are set — the stack wins: robot.launch.xml ignores the role when a stack dir is set." + fi + unset -f _pf_error return $errors } diff --git a/docs/development/stacks.md b/docs/development/stacks.md new file mode 100644 index 000000000..0c0935fdf --- /dev/null +++ b/docs/development/stacks.md @@ -0,0 +1,134 @@ +# AirStack Stacks + +A **stack** is a self-contained folder that defines a complete robot topology: +which modules run and how they are wired together ([RFC #379 §3](https://github.com/castacks/AirStack/discussions/379)). +Trunk ships a small set of **reference stacks** under `stacks/`; custom stacks +live with their owners (a lab keeps a private stack repo wiring together +public modules — no fork of AirStack needed). + +## Stack folder anatomy + +Plain files, no schema beyond a required layout (from [RFC #385 §1](https://github.com/castacks/AirStack/discussions/385)): + +| File | Purpose | +|------|---------| +| `modules.repos` | vcstool format, **pinned** to tags/commits — never branches. A stack with a pinned `.repos` *is* a localized release set. Carries a top-level `airstack_compat:` key (sibling of `repositories:`; vcstool ignores it) declaring the trunk semver range the stack was tested against. | +| `launch/stack.launch.xml` | **THE wiring document**: a flat list of module ``s. All cross-module remaps and topic-arg overrides live here — nowhere else (the single-locus rule). Unsplit stacks have exactly this one entry point; split stacks (RFC #380 §2) carry one entry file per host role plus `bridge.yaml`. | +| `docker-compose.yaml` | Composes this stack's images from module layers (RFC #379 §6). A documented stub until the stack pins modules. | +| `wiring.md` | **Generated** from the *running* graph by the wiring-snapshot test — never hand-edited. Drift-checked in CI. | +| `README.md` | What this stack is for, how to run it, its known limits. | + +Anatomy is enforced by a unit test: `tests/meta/test_stack_layout_contract.py` +(`wiring.md` is optional until the first snapshot run commits it). + +## Reference stacks in trunk + +| Stack | Topology | +|-------|----------| +| [`full_default`](https://github.com/castacks/AirStack/tree/develop/stacks/full_default) | The current full-autonomy topology (GPU `droan_gl` planner) — baseline, graph-identical to legacy `AUTONOMY_ROLE=full`. | +| [`full_droan_cpu`](https://github.com/castacks/AirStack/tree/develop/stacks/full_droan_cpu) | CPU DROAN planner + live `disparity_expansion` — absorbs `local_droan_cpu.launch.xml`. | +| [`full_macvo`](https://github.com/castacks/AirStack/tree/develop/stacks/full_macvo) | MAC-VO as the planner's disparity source — supersedes (and fixes) the broken `local_macvo_obstacle_avoidance.launch.xml` variant. | + +## Wrap vs. flatten — current status + +Stack adoption is a two-step migration: + +- **Wrap form (now):** each reference stack's `stack.launch.xml` *includes* + the existing layer bringup files (`interface_bringup`, `local_bringup`, …), + capturing today's topology without moving any wiring. The remaps still live + inside those layer bringups. +- **Flatten (next phases):** the layer bringups' nodes and remaps move into + the stack entry files, the legacy files shrink, and their lines disappear + from the lint allowlist (below). `autonomy_bringup` thins until the + AUTONOMY_ROLE dispatch is gone. + +In both forms, the stack's `wiring.md` — snapshotted from the running system — +is the observed truth of the graph. + +## Running a stack + +```bash +airstack up --stack full_default --sim isaac --robots 1 +airstack ready +``` + +Mechanics: `--stack ` validates `stacks//launch/stack.launch.xml` +exists, then exports `AIRSTACK_STACK_DIR=/root/AirStack/stacks/` (the +*container* path — `stacks/` is bind-mounted into every robot container) and +`AIRSTACK_STACK_ENTRY=stack`. Inside the container, +`autonomy_bringup/launch/robot.launch.xml` still runs the shared preamble +(ROBOT_NAME namespace, `use_sim_time`, `robot_state_publisher`, world→map TF), +then includes the stack entry file *instead of* the legacy role groups. + +`--stack :` selects an alternate entry file +(`launch/.launch.xml`) — reserved for split stacks (RFC #380 §2). + +Stack launch files need no `colcon build` — they are read from the bind mount; +edit and re-launch. + +## wiring.md: generation and drift-checking + +The wiring-snapshot system test brings the stack up in sim, waits for the node +graph to settle, captures it (`ros2 node list` + `ros2 topic info --verbose`), +and renders a mermaid dataflow document: + +```bash +airstack test -m wiring --stack full_default --sim isaacsim --num-robots 1 +``` + +- **No `stacks//wiring.md` committed yet (bootstrap):** the test PASSES + and logs an INSTRUCTION pointing at the observed snapshot + (`tests/results//wiring/observed_.md`). Validate it, copy it to + `stacks//wiring.md`, commit. +- **Committed:** the test fails on any drift between the committed diagram and + the observed graph — a PR that changes wiring must regenerate `wiring.md`, + so the review diff shows the topology change. + +Legacy runs without `--stack` keep using the golden at +`tests/goldens/wiring/full_default..robot.md`. + +## The single-locus rule (and its lint) + +All cross-module remaps and topic-arg overrides live in the stack's entry +launch file(s). Module launch files declare topic args (canonical defaults per +[the integration checklist](../robot/autonomy/integration_checklist.md), a +`description=` on every arg) but **never** `` and never hardcode +cross-module topics. The stack file *is* the wiring diagram; `grep -r +global_plan stacks/my_stack/` answers "who touches this". + +Enforced by `tests/meta/test_launch_single_locus.py` (`unit` mark, runs in CI): + +1. No ``/`remappings=` outside `stacks/*/launch/` — except files frozen + in `tests/meta/launch_lint_allowlist.txt` (the wrap-form legacy set). +2. The allowlist only shrinks: an entry whose file no longer carries a remap + fails the lint until its line is deleted. +3. Stack launch files must describe every `` they declare. + +See the [write-launch-file skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/write-launch-file/SKILL.md) +for the authoring workflow. + +## Making a new stack today + +1. Copy a reference stack: `cp -r stacks/full_default stacks/my_stack` + (`airstack stack new` arrives in a later phase). +2. Edit `launch/stack.launch.xml` — swap/add/remove module ``s and + their topic args. Update `README.md`. +3. Run it: `airstack up --stack my_stack --sim isaac`. +4. Snapshot the wiring: `airstack test -m wiring --stack my_stack`, validate + the observed file, commit it as `stacks/my_stack/wiring.md`. +5. `airstack test -m unit` — the layout contract and the launch lint must pass. + +## AUTONOMY_ROLE deprecation + +`AUTONOMY_ROLE` remains fully functional while stacks land, but it is the +legacy dispatch — stacks replace it in 0.21. `airstack up` warns when it sees +an explicitly set `AUTONOMY_ROLE`: + +| You have | What happens | Migration | +|----------|--------------|-----------| +| No stack, no explicit `AUTONOMY_ROLE` | Legacy dispatch, compose default role (`full`) — unchanged, no warning | `airstack up --stack full_default` when ready | +| Explicit `AUTONOMY_ROLE=full` (env / `.env` / `--env-file`) | Legacy dispatch + deprecation warning | `--stack full_default` | +| `AUTONOMY_ROLE=full` + `local_launch_file:=local_droan_cpu.launch.xml` | Legacy dispatch + warning | `--stack full_droan_cpu` | +| `local_macvo_obstacle_avoidance.launch.xml` | Broken (wrong arg names, stale topic) | `--stack full_macvo` (fixed) | +| `AUTONOMY_ROLE=onboard` / `offboard` (split) | Legacy dispatch + warning | split stacks arrive with RFC #380 §2 (`lite_offload_global`) | +| `--stack X` **and** explicit `AUTONOMY_ROLE` | Stack wins — the role is ignored by the launch dispatch; warning says so | Drop `AUTONOMY_ROLE` | diff --git a/mkdocs.yml b/mkdocs.yml index 3a4bc5d86..51f67c1be 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,6 +82,7 @@ nav: - Advanced Tutorials: - AI Agent Guide: docs/development/advanced/ai_agent_guide.md - AirStack Modules: docs/development/modules.md + - AirStack Stacks: docs/development/stacks.md - AirStack CLI Tool: - Extending: docs/development/advanced/airstack-cli/extending.md - Architecture: docs/development/advanced/airstack-cli/architecture.md diff --git a/robot/docker/robot-base-docker-compose.yaml b/robot/docker/robot-base-docker-compose.yaml index 793cf6cab..2d1ffb207 100644 --- a/robot/docker/robot-base-docker-compose.yaml +++ b/robot/docker/robot-base-docker-compose.yaml @@ -23,6 +23,11 @@ services: - DEBUG_RVIZ=${DEBUG_RVIZ:-false} # OptiTrack / NatNet - NATNET_SERVER_IP=${NATNET_SERVER_IP:-172.31.0.200} + # Stack dispatch (RFC #379): set by `airstack up --stack ` to the + # CONTAINER path of the stack folder (/root/AirStack/stacks/). + # Empty = legacy AUTONOMY_ROLE dispatch in robot.launch.xml. + - AIRSTACK_STACK_DIR=${AIRSTACK_STACK_DIR:-} + - AIRSTACK_STACK_ENTRY=${AIRSTACK_STACK_ENTRY:-stack} volumes: # display stuff - $HOME/.Xauthority:/.Xauthority @@ -40,6 +45,8 @@ services: - ../../common/ros_packages:/root/AirStack/robot/ros_ws/src/common:rw - ../../common/fastdds.xml:/root/AirStack/robot/ros_ws/src/fastdds.xml - ../ros_ws:/root/AirStack/robot/ros_ws:rw + # reference stack folders (launch entry points read via AIRSTACK_STACK_DIR) + - ../../stacks:/root/AirStack/stacks:rw # bags - ../bags:/bags:rw diff --git a/robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml b/robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml index 0a30ea3f0..e93244e73 100644 --- a/robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml +++ b/robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml @@ -1,9 +1,13 @@ @@ -13,6 +17,15 @@ lite, desktop_split sim) + + + + @@ -30,7 +43,13 @@ lite, desktop_split sim) name="world_to_map_broadcaster" args="0 0 0 0 0 0 world map" /> - + + + + + + + @@ -77,4 +96,6 @@ lite, desktop_split sim) file="$(find-pkg-share autonomy_bringup)/onboard_local_offboard_global/launch/offboard_autonomy_global.launch.xml" /> + + \ No newline at end of file diff --git a/stacks/full_default/README.md b/stacks/full_default/README.md new file mode 100644 index 000000000..a371b4dc3 --- /dev/null +++ b/stacks/full_default/README.md @@ -0,0 +1,51 @@ +# `full_default` — trunk reference stack + +The current full-autonomy topology, as a self-contained stack folder (RFC #379 §3). +This is the stack most users start from and the baseline other stacks are copied from. + +## What it launches + +Wrap form (E1): the entry point `launch/stack.launch.xml` includes the existing +layer bringups — interface, sensors, perception, local, global, behavior, +logging — exactly as `onboard_autonomy_all.launch.xml` does with default args, +plus the two role-`full` extras (DDS-router domain bridge to the GCS, gossip +coordination layer). Flattening the layer bringups' nodes and remaps into the +stack file (the single-locus wiring rule) arrives in a later phase (E2/E3). + +## Equivalence claim + +`airstack up --stack full_default` produces a ROS graph identical to the legacy +`AUTONOMY_ROLE=full` dispatch. Verify with the wiring snapshot test: + +```bash +airstack test -m wiring --stack full_default --sim isaacsim --num-robots 1 +``` + +## How to run + +```bash +airstack up --stack full_default --sim isaac --robots 1 +airstack ready +``` + +The shared per-robot preamble (ROBOT_NAME namespace, `use_sim_time`, +`robot_state_publisher`, world→map static TF) still runs in +`autonomy_bringup/launch/robot.launch.xml`, which dispatches to this stack when +`AIRSTACK_STACK_DIR` is set. + +## Known limits + +- Wrap form: cross-module remaps still live inside the layer bringup launch + files, not in `stack.launch.xml`. Grep the layer bringups (or read + `wiring.md` once committed) for the actual topic wiring. +- `modules.repos` pins no external modules yet; every package is trunk-resident. +- `docker-compose.yaml` is a stub — per-stack image composition arrives with + the first module pins; trunk compose profiles provide all services. + +## wiring.md + +Generated — the commit arrives with the first snapshot run of +`airstack test -m wiring --stack full_default` (the harness writes +`observed_full_default.md` under the run directory; validate it and copy it +here as `wiring.md`). Once committed, CI drift-checks the running graph +against it. diff --git a/stacks/full_default/docker-compose.yaml b/stacks/full_default/docker-compose.yaml new file mode 100644 index 000000000..45e512d3b --- /dev/null +++ b/stacks/full_default/docker-compose.yaml @@ -0,0 +1,11 @@ +# Per-stack image composition (RFC #379 S6) arrives with this stack's first +# module pins: the P4 machinery (tools/compose_module_layers.py) composes +# per-module dependency layers on top of the trunk base image and emits a +# compose override for `airstack up`. +# +# full_default pins no modules (see modules.repos), so there is nothing to +# compose yet -- the trunk compose profiles (root docker-compose.yaml, +# robot/docker/docker-compose.yaml) provide every service meanwhile. The empty +# services map keeps this file valid YAML for the stack-anatomy contract +# (tests/meta/test_stack_layout_contract.py). +services: {} diff --git a/stacks/full_default/launch/stack.launch.xml b/stacks/full_default/launch/stack.launch.xml new file mode 100644 index 000000000..4d76929c1 --- /dev/null +++ b/stacks/full_default/launch/stack.launch.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stacks/full_default/modules.repos b/stacks/full_default/modules.repos new file mode 100644 index 000000000..b9fa8271f --- /dev/null +++ b/stacks/full_default/modules.repos @@ -0,0 +1,15 @@ +# modules.repos : module pins for the full_default reference stack (RFC #379 S3). +# +# vcstool format, PINNED to tags/commits (never branches). `airstack module sync` +# reads this file into the gitignored modules/ dir; a stack with a pinned .repos +# IS a localized release set. +# +# airstack_compat is a top-level sibling of repositories: (vcstool ignores it, +# AirStack tooling reads it) declaring the trunk semver range this stack was +# tested against. sync warns on mismatch; it never gates. +# +# This reference stack pulls no external modules yet: every package it launches +# is trunk-resident (robot/ros_ws/src + common/ros_packages). +airstack_compat: ">=0.19.0-alpha.18 <0.21.0" +repositories: {} +x-local-modules: [] diff --git a/stacks/full_droan_cpu/README.md b/stacks/full_droan_cpu/README.md new file mode 100644 index 000000000..07c04ccde --- /dev/null +++ b/stacks/full_droan_cpu/README.md @@ -0,0 +1,44 @@ +# `full_droan_cpu` — trunk reference stack + +Full autonomy with the **CPU DROAN local planner** (`droan_local_planner` + +a live `disparity_expansion` world model) instead of the GPU `droan_gl` node. +One of the presets absorbing the `local_*.launch.xml` variant explosion into +named stacks a few include lines apart (RFC #379 §3). + +## What it launches + +Wrap form (E1): identical to [`full_default`](../full_default/README.md) +except the Local layer include is +`local_bringup/launch/local_droan_cpu.launch.xml`. Everything else — interface, +sensors, perception, global, behavior, logging, DDS router, gossip — matches +`full_default` exactly. + +## Equivalence claim + +`airstack up --stack full_droan_cpu` produces a ROS graph identical to the +legacy `AUTONOMY_ROLE=full` dispatch with +`local_launch_file:=local_droan_cpu.launch.xml` passed to +`onboard_autonomy_all.launch.xml`. + +## How to run + +```bash +airstack up --stack full_droan_cpu --sim isaac --robots 1 +airstack ready +``` + +## Known limits + +- Wrap form: cross-module remaps still live inside the layer bringup launch + files, not in `stack.launch.xml`. +- CPU DROAN is the slower planner path — use `full_default` (GPU `droan_gl`) + unless you need to run without the GPU planner or are debugging + `disparity_expansion`. +- `modules.repos` pins no external modules yet; `docker-compose.yaml` is a + stub (trunk compose profiles provide all services). + +## wiring.md + +Generated — the commit arrives with the first snapshot run of +`airstack test -m wiring --stack full_droan_cpu`. Once committed, CI +drift-checks the running graph against it. diff --git a/stacks/full_droan_cpu/docker-compose.yaml b/stacks/full_droan_cpu/docker-compose.yaml new file mode 100644 index 000000000..ce27cbd0f --- /dev/null +++ b/stacks/full_droan_cpu/docker-compose.yaml @@ -0,0 +1,11 @@ +# Per-stack image composition (RFC #379 S6) arrives with this stack's first +# module pins: the P4 machinery (tools/compose_module_layers.py) composes +# per-module dependency layers on top of the trunk base image and emits a +# compose override for `airstack up`. +# +# full_droan_cpu pins no modules (see modules.repos), so there is nothing to +# compose yet -- the trunk compose profiles (root docker-compose.yaml, +# robot/docker/docker-compose.yaml) provide every service meanwhile. The empty +# services map keeps this file valid YAML for the stack-anatomy contract +# (tests/meta/test_stack_layout_contract.py). +services: {} diff --git a/stacks/full_droan_cpu/launch/stack.launch.xml b/stacks/full_droan_cpu/launch/stack.launch.xml new file mode 100644 index 000000000..57378c4dc --- /dev/null +++ b/stacks/full_droan_cpu/launch/stack.launch.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stacks/full_droan_cpu/modules.repos b/stacks/full_droan_cpu/modules.repos new file mode 100644 index 000000000..a2bce54e2 --- /dev/null +++ b/stacks/full_droan_cpu/modules.repos @@ -0,0 +1,15 @@ +# modules.repos : module pins for the full_droan_cpu reference stack (RFC #379 S3). +# +# vcstool format, PINNED to tags/commits (never branches). `airstack module sync` +# reads this file into the gitignored modules/ dir; a stack with a pinned .repos +# IS a localized release set. +# +# airstack_compat is a top-level sibling of repositories: (vcstool ignores it, +# AirStack tooling reads it) declaring the trunk semver range this stack was +# tested against. sync warns on mismatch; it never gates. +# +# This reference stack pulls no external modules yet: every package it launches +# is trunk-resident (robot/ros_ws/src + common/ros_packages). +airstack_compat: ">=0.19.0-alpha.18 <0.21.0" +repositories: {} +x-local-modules: [] diff --git a/stacks/full_macvo/README.md b/stacks/full_macvo/README.md new file mode 100644 index 000000000..f003c8f3f --- /dev/null +++ b/stacks/full_macvo/README.md @@ -0,0 +1,60 @@ +# `full_macvo` — trunk reference stack + +Full autonomy with **MAC-VO** as the disparity source for the local planner. +One of the presets absorbing the `local_*.launch.xml` variant explosion into +named stacks a few include lines apart (RFC #379 §3). + +## What it launches + +Wrap form (E1): identical to [`full_default`](../full_default/README.md) +except: + +1. Perception is included with `launch_macvo:=true`, so the `macvo_ros2` node + runs and publishes `/$ROBOT_NAME/perception/macvo/disparity` (plus macvo + odometry/pose/point-cloud topics). +2. The Local layer include passes + `local_disparity_in_topic:=/$ROBOT_NAME/perception/macvo/disparity` and + `local_depth_in_topic:=none`, wiring the planner's disparity input to + MAC-VO's real output topic. + +## Supersedes the broken legacy variant + +This stack **supersedes** +`robot/ros_ws/src/local/local_bringup/launch/local_macvo_obstacle_avoidance.launch.xml`, +which is broken two ways and is left untouched: + +- It passes `odometry_in_topic` / `disparity_in_topic` / `depth_in_topic` — + arg names missing the `local_` prefix, so `local.launch.xml` never sees them + and silently uses its stereo defaults. +- Its disparity topic `/$ROBOT_NAME/macvo/disparity` is stale: macvo launches + inside the `perception` namespace and actually publishes + `/$ROBOT_NAME/perception/macvo/disparity`. +- It also never enabled `launch_macvo` in perception, so the macvo node it + pointed at was not even running. + +## How to run + +```bash +airstack up --stack full_macvo --sim isaac --robots 1 +airstack ready +``` + +## Known limits + +- Wrap form: cross-module remaps still live inside the layer bringup launch + files, not in `stack.launch.xml`. +- `stereo_image_proc` still runs alongside MAC-VO (perception's + `launch_stereo_image_proc` default is kept `true`): the stereo point cloud + feeds other consumers, so this stack runs both estimators. A leaner + macvo-only preset can flip that arg once downstream consumers are audited. +- MAC-VO is GPU-heavy; expect reduced sim real-time factor on a shared GPU. +- No committed equivalence baseline exists for this topology (the legacy + variant never worked), so the first `wiring.md` snapshot IS the baseline. +- `modules.repos` pins no external modules yet; `docker-compose.yaml` is a + stub (trunk compose profiles provide all services). + +## wiring.md + +Generated — the commit arrives with the first snapshot run of +`airstack test -m wiring --stack full_macvo`. Once committed, CI drift-checks +the running graph against it. diff --git a/stacks/full_macvo/docker-compose.yaml b/stacks/full_macvo/docker-compose.yaml new file mode 100644 index 000000000..85c1ef99f --- /dev/null +++ b/stacks/full_macvo/docker-compose.yaml @@ -0,0 +1,13 @@ +# Per-stack image composition (RFC #379 S6) arrives with this stack's first +# module pins: the P4 machinery (tools/compose_module_layers.py) composes +# per-module dependency layers on top of the trunk base image and emits a +# compose override for `airstack up`. +# +# full_macvo pins no modules (see modules.repos), so there is nothing to +# compose yet -- the trunk compose profiles (root docker-compose.yaml, +# robot/docker/docker-compose.yaml) provide every service meanwhile (MAC-VO's +# networks are baked into the trunk robot image today; they migrate into a +# module layer when macvo_ros2 moves out of trunk). The empty services map +# keeps this file valid YAML for the stack-anatomy contract +# (tests/meta/test_stack_layout_contract.py). +services: {} diff --git a/stacks/full_macvo/launch/stack.launch.xml b/stacks/full_macvo/launch/stack.launch.xml new file mode 100644 index 000000000..ced9c15bb --- /dev/null +++ b/stacks/full_macvo/launch/stack.launch.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stacks/full_macvo/modules.repos b/stacks/full_macvo/modules.repos new file mode 100644 index 000000000..1abe4c500 --- /dev/null +++ b/stacks/full_macvo/modules.repos @@ -0,0 +1,16 @@ +# modules.repos : module pins for the full_macvo reference stack (RFC #379 S3). +# +# vcstool format, PINNED to tags/commits (never branches). `airstack module sync` +# reads this file into the gitignored modules/ dir; a stack with a pinned .repos +# IS a localized release set. +# +# airstack_compat is a top-level sibling of repositories: (vcstool ignores it, +# AirStack tooling reads it) declaring the trunk semver range this stack was +# tested against. sync warns on mismatch; it never gates. +# +# This reference stack pulls no external modules yet: every package it launches +# is trunk-resident (macvo_ros2 lives in robot/ros_ws/src/perception; it is a +# prime candidate to migrate out as a module later). +airstack_compat: ">=0.19.0-alpha.18 <0.21.0" +repositories: {} +x-local-modules: [] diff --git a/tests/conftest.py b/tests/conftest.py index 38aec5c5f..b721e0c02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,6 +27,13 @@ def pytest_addoption(parser): "Default isaacsim; pass --sim msairsim to opt in.") parser.addoption("--num-robots", default="1,3", help="Comma-separated robot counts, e.g. 1,3") + parser.addoption("--stack", default=None, + help="Stack folder under stacks/ to launch instead of " + "the legacy AUTONOMY_ROLE dispatch (sets " + "AIRSTACK_STACK_DIR for airstack up). Default: " + "None (legacy role dispatch). The wiring test " + "drift-checks against stacks//wiring.md " + "when set.") parser.addoption("--stress-iterations", type=int, default=1, help="Number of up/down iterations per (sim, num_robots) config") parser.addoption("--stable-duration", type=int, default=120, @@ -220,6 +227,14 @@ def airstack_env(request): env_overrides["QT_QPA_PLATFORM"] = "offscreen" env_overrides.update(cfg.get("extra_env", {})) + # Stack dispatch (RFC #379 §3): route robot.launch.xml to the stack's + # entry launch file instead of the legacy AUTONOMY_ROLE role groups. + # Container path — stacks/ is bind-mounted at /root/AirStack/stacks. + stack = request.config.getoption("--stack") + if stack: + env_overrides["AIRSTACK_STACK_DIR"] = f"/root/AirStack/stacks/{stack}" + env_overrides["AIRSTACK_STACK_ENTRY"] = "stack" + with logger_to(log): missing = missing_images(env=env_overrides) if missing: @@ -250,6 +265,8 @@ def airstack_env(request): "robot_pattern": "robot.*desktop", "up_started_at": t0, "cfg": cfg, + # None = legacy AUTONOMY_ROLE dispatch; else the stacks/ launched. + "stack": stack, } tid = current_test_id() diff --git a/tests/meta/launch_lint_allowlist.txt b/tests/meta/launch_lint_allowlist.txt new file mode 100644 index 000000000..6bb81ca10 --- /dev/null +++ b/tests/meta/launch_lint_allowlist.txt @@ -0,0 +1,29 @@ +# Launch files allowed to carry /remappings= OUTSIDE stacks/*/launch/ +# (tests/meta/test_launch_single_locus.py — RFC #379 §4 single-locus rule). +# +# This list is FROZEN at the wrap-form baseline (P5-E1): it names every launch +# file that carried remaps when the rule landed. It only shrinks — as E2/E3 +# flatten wiring into the stack entry files, delete the corresponding line. +# A listed file that no longer contains a remap FAILS the lint until its line +# is removed; adding a NEW line needs the same scrutiny as an RFC change. +# +# Paths are relative to the repo root, one per line; '#' starts a comment. +common/ros_packages/airstack_common/launch/playback.launch.xml +robot/docker/zed/ws/src/zed_wrapper/launch/zed_camera.launch.py +robot/ros_ws/src/behavior/behavior_bringup/launch/behavior.launch.xml +robot/ros_ws/src/global/global_bringup/launch/global.launch.xml +robot/ros_ws/src/global/planners/exploration/launch/exploration_launch.xml +robot/ros_ws/src/global/planners/exploration/launch/robot_launch_gazebo/gz_behavior_launch.xml +robot/ros_ws/src/global/planners/exploration/launch/robot_launch_gazebo/gz_global_launch.xml +robot/ros_ws/src/global/planners/exploration/launch/robot_launch_gazebo/gz_interface_launch.xml +robot/ros_ws/src/global/planners/exploration/launch/robot_launch_gazebo/gz_local_launch.xml +robot/ros_ws/src/global/planners/random_walk/launch/random_walk_launch.xml +robot/ros_ws/src/interface/interface_bringup/launch/interface.launch.py +robot/ros_ws/src/interface/px4_interface/launch/px4_interface.launch.xml +robot/ros_ws/src/interface/robot_interface/launch/odometry_conversion.xml +robot/ros_ws/src/local/local_bringup/launch/local.launch.xml +robot/ros_ws/src/local/local_bringup/launch/local_droan_cpu.launch.xml +robot/ros_ws/src/local/world_models/disparity_expansion/launch/disparity_pcd.launch.xml +robot/ros_ws/src/perception/natnet_ros2/launch/mavros_gp_origin.launch.xml +robot/ros_ws/src/perception/natnet_ros2/launch/px4_param_setter.launch.xml +robot/ros_ws/src/perception/perception_bringup/launch/perception.launch.xml diff --git a/tests/meta/test_launch_intent_contract.py b/tests/meta/test_launch_intent_contract.py index 2d541e138..856c877d4 100644 --- a/tests/meta/test_launch_intent_contract.py +++ b/tests/meta/test_launch_intent_contract.py @@ -178,3 +178,74 @@ def test_invalid_sim_is_fatal(): code, out, _ = run_up_dry("--sim", "gazebo", check=False) assert code != 0 assert "gazebo" in out + + +# ── --stack dispatch (RFC #379 §3, P5-E1) ────────────────────────────────── + +def test_stack_flag_exports_container_paths(): + """--stack validates host-side but exports the CONTAINER path (stacks/ is + bind-mounted at /root/AirStack/stacks).""" + _, _, cfg = run_up_dry("--sim", "isaac", "--stack", "full_default") + assert cfg["AIRSTACK_STACK_DIR"] == "/root/AirStack/stacks/full_default" + assert cfg["AIRSTACK_STACK_ENTRY"] == "stack" + + +def test_stack_split_entry_form(): + """--stack : selects launch/.launch.xml (reserved for + split stacks; the default entry file also resolves through it).""" + _, _, cfg = run_up_dry("--sim", "isaac", "--stack", "full_default:stack") + assert cfg["AIRSTACK_STACK_DIR"] == "/root/AirStack/stacks/full_default" + assert cfg["AIRSTACK_STACK_ENTRY"] == "stack" + + +def test_unknown_stack_is_fatal(): + code, out, _ = run_up_dry("--stack", "no_such_stack", check=False) + assert code != 0 + assert "no_such_stack" in out + assert "full_default" in out # error lists the available stacks + + +def test_stack_missing_entry_is_fatal(): + code, out, _ = run_up_dry("--stack", "full_default:onboard", check=False) + assert code != 0 + assert "onboard.launch.xml" in out + + +def test_no_stack_leaves_stack_vars_empty(): + """Legacy path: no --stack → both stack vars empty in effective config.""" + _, _, cfg = run_up_dry( + "--sim", "isaac", + # Scrub any stack vars inherited from the invoking shell. + env={"AIRSTACK_STACK_DIR": "", "AIRSTACK_STACK_ENTRY": ""}, + ) + assert cfg.get("AIRSTACK_STACK_DIR", "") == "" + + +def test_autonomy_role_without_stack_warns_deprecation(): + """AUTONOMY_ROLE explicitly set (env/--env-file) + no stack → one + deprecation warning pointing at --stack.""" + _, out, _ = run_up_dry( + "--sim", "isaac", + env={"AUTONOMY_ROLE": "full", "AIRSTACK_STACK_DIR": ""}, + ) + assert "legacy dispatch" in out + assert "--stack full_default" in out + + +def test_no_deprecation_warning_without_explicit_role(): + """The compose files' own ${AUTONOMY_ROLE:-full} default must NOT trip the + warning — only an explicit env / --env-file / .env value does.""" + _, out, _ = run_up_dry( + "--sim", "isaac", + env={"AUTONOMY_ROLE": "", "AIRSTACK_STACK_DIR": ""}, + ) + assert "legacy dispatch" not in out + + +def test_stack_and_role_both_set_warns_stack_wins(): + _, out, _ = run_up_dry( + "--sim", "isaac", "--stack", "full_default", + env={"AUTONOMY_ROLE": "full"}, + ) + assert "stack wins" in out + assert "legacy dispatch" not in out diff --git a/tests/meta/test_launch_single_locus.py b/tests/meta/test_launch_single_locus.py new file mode 100644 index 000000000..57f19f38d --- /dev/null +++ b/tests/meta/test_launch_single_locus.py @@ -0,0 +1,138 @@ +# Copyright (c) 2026 Carnegie Mellon University +# MIT License - see LICENSE in the repository root for full text. +"""Single-locus wiring lint (RFC #379 §4.1). + +All cross-module remaps belong in a stack's entry launch files +(``stacks/*/launch/``). Module and layer launch files declare topic args with +canonical defaults but never ```` (XML) or ``remappings=`` (Python +launch). + +The rule lands mid-migration (wrap form, P5-E1), so the launch files that +carried remaps at freeze time are grandfathered in +``launch_lint_allowlist.txt``. The allowlist only shrinks: + +* rule 1 — no remap outside ``stacks/*/launch/`` except allowlisted files; +* rule 2 — every allowlist entry must still exist AND still contain a remap + (once E2/E3 flatten a file's wiring away, its line must be deleted); +* rule 3 — stack launch files must carry ``description=`` on every ```` + they declare (they are the wiring document — args must be self-describing). + +Scan scope: ``*.xml`` / ``*.py`` under any ``launch/`` directory inside +``robot/``, ``gcs/``, ``common/``, and ``simulation/``. +""" +import re +import xml.etree.ElementTree as ET + +import pytest + +from harness.discovery import TESTS_DIR + +pytestmark = pytest.mark.unit + +REPO = TESTS_DIR.parent +ALLOWLIST_PATH = TESTS_DIR / "meta" / "launch_lint_allowlist.txt" +SCAN_ROOTS = ("robot", "gcs", "common", "simulation") + +# in XML launch; remappings=[...] in Python launch. +_REMAP_RE = re.compile(r"/remappings= " + "but are not in the frozen allowlist " + f"({ALLOWLIST_PATH.relative_to(REPO)}):\n " + + "\n ".join(offenders) + + "\nCross-module wiring belongs in the stack entry launch file " + "(RFC #379 §4 single-locus rule; see " + ".agents/skills/write-launch-file/SKILL.md). The allowlist only " + "shrinks — do not add new entries." + ) + + +def test_allowlist_entries_exist_and_still_have_remaps(): + """Rule 2 (shrink-only): stale allowlist lines must be deleted.""" + problems = [] + for rel in _allowlist(): + path = REPO / rel + if not path.is_file(): + problems.append(f"{rel}: file no longer exists — delete its " + "allowlist line") + elif not _has_remap(path): + problems.append(f"{rel}: no longer contains a remap — its wiring " + "moved (good!); delete its allowlist line so the " + "file stays remap-free") + assert not problems, ( + f"Stale entries in {ALLOWLIST_PATH.relative_to(REPO)}:\n " + + "\n ".join(problems) + ) + + +def _stack_launch_files(): + stacks = REPO / "stacks" + if not stacks.is_dir(): + return [] + return sorted( + p for p in stacks.glob("*/launch/*.launch.xml") + if not p.parts[len(stacks.parts)].startswith(".") + ) + + +def test_stack_launch_args_have_descriptions(): + """Rule 3: stack entry files are the wiring document — every they + declare needs a description=. Vacuously true while stack files declare no + args (wrap form passes include args instead).""" + stack_files = _stack_launch_files() + assert stack_files, "no stack launch files found under stacks/*/launch/" + offenders = [] + for path in stack_files: + tree = ET.parse(path) # also asserts well-formed XML + # Only top-level children of are declarations; + # inside passes a value and needs no description. + for arg in tree.getroot().findall("arg"): + if not arg.get("description"): + offenders.append( + f"{path.relative_to(REPO).as_posix()}: " + f" missing description=" + ) + assert not offenders, ( + "Stack launch files must describe every declared :\n " + + "\n ".join(offenders) + ) diff --git a/tests/meta/test_stack_layout_contract.py b/tests/meta/test_stack_layout_contract.py new file mode 100644 index 000000000..a1208bf0b --- /dev/null +++ b/tests/meta/test_stack_layout_contract.py @@ -0,0 +1,117 @@ +# Copyright (c) 2026 Carnegie Mellon University +# MIT License - see LICENSE in the repository root for full text. +"""Stack folder anatomy contract (RFC #379 §3, RFC #385 §1). + +Every stack directory under ``stacks/`` (ignoring ``.external``, the gitignored +home of fetched third-party stack repos) must ship the four committed anatomy +files: + +* ``modules.repos`` — YAML with a top-level ``airstack_compat`` semver range + (sibling of ``repositories:``; vcstool ignores it, our tooling reads it); +* ``launch/`` — at least one ``*.launch.xml`` entry point; +* ``docker-compose.yaml`` — valid YAML (a documented stub until module pins + arrive); +* ``README.md`` — non-trivial. + +``wiring.md`` is NOT required yet (bootstrap: it is generated from the first +validated wiring-snapshot run), but when present it must carry the +machine-readable ``wiring-graph-v1`` trailer that the drift check reads. +""" +import pytest +import yaml + +from harness.discovery import TESTS_DIR + +pytestmark = pytest.mark.unit + +REPO = TESTS_DIR.parent +STACKS_DIR = REPO / "stacks" + + +def _stack_dirs(): + if not STACKS_DIR.is_dir(): + return [] + return sorted( + d for d in STACKS_DIR.iterdir() + if d.is_dir() and not d.name.startswith(".") + ) + + +def _stack_ids(): + return [d.name for d in _stack_dirs()] + + +def test_stacks_dir_has_reference_stacks(): + assert _stack_dirs(), ( + f"{STACKS_DIR} has no stack folders — trunk ships reference stacks " + "(full_default, ...)" + ) + + +@pytest.mark.parametrize("stack", _stack_dirs(), ids=_stack_ids()) +class TestStackAnatomy: + + def test_modules_repos(self, stack): + path = stack / "modules.repos" + assert path.is_file(), f"{stack.name}: missing modules.repos" + data = yaml.safe_load(path.read_text()) + assert isinstance(data, dict), ( + f"{stack.name}: modules.repos did not parse to a YAML mapping" + ) + compat = data.get("airstack_compat") + assert isinstance(compat, str) and compat.strip(), ( + f"{stack.name}: modules.repos needs a top-level airstack_compat " + "semver-range string (the trunk range the stack was tested " + "against — RFC #379 §3)" + ) + assert "repositories" in data, ( + f"{stack.name}: modules.repos needs a repositories: key " + "(vcstool format; {} when the stack pins no modules)" + ) + + def test_launch_entry_points(self, stack): + launch_dir = stack / "launch" + assert launch_dir.is_dir(), f"{stack.name}: missing launch/ dir" + entries = list(launch_dir.glob("*.launch.xml")) + assert entries, ( + f"{stack.name}: launch/ has no *.launch.xml entry point " + "(unsplit stacks have exactly stack.launch.xml)" + ) + + def test_docker_compose_is_valid_yaml(self, stack): + path = stack / "docker-compose.yaml" + assert path.is_file(), f"{stack.name}: missing docker-compose.yaml" + data = yaml.safe_load(path.read_text()) + assert data is not None, ( + f"{stack.name}: docker-compose.yaml is empty — keep at least " + "'services: {}' plus the explanatory comments" + ) + + def test_readme_non_trivial(self, stack): + path = stack / "README.md" + assert path.is_file(), f"{stack.name}: missing README.md" + text = path.read_text().strip() + assert len(text) >= 200, ( + f"{stack.name}: README.md is trivial ({len(text)} chars) — state " + "what the stack is for, how to run it, and its known limits" + ) + + def test_wiring_md_trailer_when_present(self, stack): + """wiring.md is optional (bootstrap) but must be machine-readable.""" + path = stack / "wiring.md" + if not path.exists(): + pytest.skip(f"{stack.name}: wiring.md not committed yet " + "(generated by the first wiring-snapshot run)") + import wiring_snapshot as ws + try: + graph = ws.extract_graph_from_md(path.read_text()) + except ValueError as exc: + pytest.fail( + f"{stack.name}: wiring.md lacks a parseable wiring-graph-v1 " + f"trailer ({exc}) — regenerate it from a wiring-snapshot " + "run, never hand-edit" + ) + assert graph.get("nodes") is not None, ( + f"{stack.name}: wiring.md trailer has no nodes key — regenerate " + "it from a wiring-snapshot run" + ) diff --git a/tests/system/test_wiring_snapshot.py b/tests/system/test_wiring_snapshot.py index 6be788964..1a78391e2 100644 --- a/tests/system/test_wiring_snapshot.py +++ b/tests/system/test_wiring_snapshot.py @@ -12,6 +12,14 @@ committed from a validated local run — see ``tests/goldens/wiring/README.md``); if present, ``diff_graphs`` must report identical or the test fails with the JSON drift verdict. + +Stack mode (RFC #379 §3/§4): with ``--stack `` the fixture launches the +stack's entry launch file and the golden becomes ``stacks//wiring.md`` +(one observed-wiring document per stack, committed in the stack folder). The +observed snapshot is written as ``observed_.md`` and the same +bootstrap-pass + INSTRUCTION behavior applies when the stack has no wiring.md +yet. Legacy runs (no ``--stack``) keep the tests/goldens/wiring path +unchanged. """ import json import os @@ -236,6 +244,12 @@ def test_wiring_matches_golden(self, airstack_env): m = get_metrics() tid = current_test_id() + # Legacy (no --stack) keeps the full_default naming and the + # tests/goldens/wiring golden; a selected stack owns its golden at + # stacks//wiring.md. + stack = airstack_env.get("stack") + stack_name = stack or "full_default" + _wait_for_settled_node_sets(airstack_env) t0 = time.time() graph = _capture_merged_graph(airstack_env) @@ -243,7 +257,7 @@ def test_wiring_matches_golden(self, airstack_env): round(time.time() - t0, 2), unit="s") meta = { - "stack": "full_default", + "stack": stack_name, "generated-by": "tests/system/test_wiring_snapshot.py", "date": time.strftime("%Y-%m-%d %H:%M:%S"), "sim": sim, @@ -252,7 +266,7 @@ def test_wiring_matches_golden(self, airstack_env): } out_dir = run_dir() / "wiring" out_dir.mkdir(parents=True, exist_ok=True) - observed_path = out_dir / "observed_full_default.md" + observed_path = out_dir / f"observed_{stack_name}.md" observed_path.write_text(ws.render_wiring_md(graph, meta)) logger.info("Wrote observed wiring snapshot to %s", observed_path) @@ -265,10 +279,13 @@ def test_wiring_matches_golden(self, airstack_env): m.record(tid, "wiring_topic_count", len(graph["topics"]), unit="count", direction="higher_is_better") - golden_path = repo_path( - "tests", "goldens", "wiring", - f"full_default.{sim}.{num_robots}robot.md", - ) + if stack: + golden_path = repo_path("stacks", stack, "wiring.md") + else: + golden_path = repo_path( + "tests", "goldens", "wiring", + f"full_default.{sim}.{num_robots}robot.md", + ) if not golden_path.exists(): logger.info( "INSTRUCTION: no golden at %s — bootstrap by validating the " From 5efffa3d1b9c867212835d4d509e6782ded3c9dd Mon Sep 17 00:00:00 2001 From: Andrew Jong Date: Thu, 20 Aug 2026 18:25:04 -0400 Subject: [PATCH 2/4] P5-E1(rfc-379): observed wiring.md for all three reference stacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captured from live isaacsim bring-ups @ d02a3467: - full_default: 83 nodes / 405 edges — diff vs the legacy AUTONOMY_ROLE=full golden is IDENTICAL (machine-proven wrap-form equivalence) - full_droan_cpu: 84 nodes / 419 edges (CPU droan + live disparity_expansion) - full_macvo: 84 nodes / 407 edges — first working MACVO topology: droan's disparity_expander subscribes /perception/macvo/disparity (the wiring the broken legacy variant never achieved) These are the E2 flatten gates: the refactor must reproduce these graphs. Co-Authored-By: Claude Fable 5 --- stacks/full_default/wiring.md | 7633 ++++++++++++++++++++++++++++++ stacks/full_droan_cpu/wiring.md | 7865 +++++++++++++++++++++++++++++++ stacks/full_macvo/wiring.md | 7662 ++++++++++++++++++++++++++++++ 3 files changed, 23160 insertions(+) create mode 100644 stacks/full_default/wiring.md create mode 100644 stacks/full_droan_cpu/wiring.md create mode 100644 stacks/full_macvo/wiring.md diff --git a/stacks/full_default/wiring.md b/stacks/full_default/wiring.md new file mode 100644 index 000000000..ca8e779e8 --- /dev/null +++ b/stacks/full_default/wiring.md @@ -0,0 +1,7633 @@ +# Wiring snapshot: full_default + +- **generated-by**: tests/system/test_wiring_snapshot.py +- **date**: 2026-08-20 22:14:34 +- **sim**: isaacsim +- **num_robots**: 1 +- **source-sha**: d02a3467b72d + +```mermaid +graph LR + subgraph g0["behavior"] + n2["/robot_1/behavior/drone_safety_monitor/drone_safety_monitor"] + end + subgraph g1["control"] + n3["/robot_1/control/pid_controller"] + end + subgraph g2["droan"] + n4["/robot_1/droan/disparity_expander_node"] + end + subgraph g3["interface"] + n6["/robot_1/interface/mavros/actuator_control"] + n7["/robot_1/interface/mavros/adsb"] + n8["/robot_1/interface/mavros/altitude"] + n9["/robot_1/interface/mavros/cam_imu_sync"] + n10["/robot_1/interface/mavros/camera"] + n11["/robot_1/interface/mavros/cellular_status"] + n12["/robot_1/interface/mavros/cmd"] + n13["/robot_1/interface/mavros/companion_process"] + n14["/robot_1/interface/mavros/debug_value"] + n15["/robot_1/interface/mavros/esc_status"] + n16["/robot_1/interface/mavros/esc_telemetry"] + n17["/robot_1/interface/mavros/fake_gps"] + n18["/robot_1/interface/mavros/ftp"] + n19["/robot_1/interface/mavros/geofence"] + n20["/robot_1/interface/mavros/gimbal_control"] + n21["/robot_1/interface/mavros/global_position"] + n22["/robot_1/interface/mavros/gps_input"] + n23["/robot_1/interface/mavros/gps_rtk"] + n24["/robot_1/interface/mavros/gpsstatus"] + n25["/robot_1/interface/mavros/guided_target"] + n26["/robot_1/interface/mavros/hil"] + n27["/robot_1/interface/mavros/home_position"] + n28["/robot_1/interface/mavros/imu"] + n29["/robot_1/interface/mavros/landing_target"] + n30["/robot_1/interface/mavros/local_position"] + n31["/robot_1/interface/mavros/log_transfer"] + n32["/robot_1/interface/mavros/mag_calibration"] + n33["/robot_1/interface/mavros/manual_control"] + n34["/robot_1/interface/mavros/mavros"] + n35["/robot_1/interface/mavros/mavros_node"] + n36["/robot_1/interface/mavros/mavros_router"] + n37["/robot_1/interface/mavros/mission"] + n38["/robot_1/interface/mavros/mocap"] + n39["/robot_1/interface/mavros/mount_control"] + n40["/robot_1/interface/mavros/nav_controller_output"] + n41["/robot_1/interface/mavros/obstacle"] + n42["/robot_1/interface/mavros/obstacle_distance_3d"] + n43["/robot_1/interface/mavros/odometry"] + n44["/robot_1/interface/mavros/onboard_computer"] + n45["/robot_1/interface/mavros/open_drone_id"] + n46["/robot_1/interface/mavros/optical_flow"] + n47["/robot_1/interface/mavros/param"] + n48["/robot_1/interface/mavros/play_tune"] + n49["/robot_1/interface/mavros/px4flow"] + n50["/robot_1/interface/mavros/rallypoint"] + n51["/robot_1/interface/mavros/rc"] + n52["/robot_1/interface/mavros/setpoint_accel"] + n53["/robot_1/interface/mavros/setpoint_attitude"] + n54["/robot_1/interface/mavros/setpoint_position"] + n55["/robot_1/interface/mavros/setpoint_raw"] + n56["/robot_1/interface/mavros/setpoint_trajectory"] + n57["/robot_1/interface/mavros/setpoint_velocity"] + n58["/robot_1/interface/mavros/sim_state"] + n59["/robot_1/interface/mavros/sys"] + n60["/robot_1/interface/mavros/tdr_radio"] + n61["/robot_1/interface/mavros/terrain"] + n62["/robot_1/interface/mavros/time"] + n63["/robot_1/interface/mavros/trajectory"] + n64["/robot_1/interface/mavros/tunnel"] + n65["/robot_1/interface/mavros/vfr_hud"] + n66["/robot_1/interface/mavros/vision_pose"] + n67["/robot_1/interface/mavros/vision_speed"] + n68["/robot_1/interface/mavros/wind"] + n69["/robot_1/interface/odom_modifier"] + n70["/robot_1/interface/robot_interface"] + end + subgraph g4["odometry_conversion"] + n71["/robot_1/odometry_conversion/odometry_conversion"] + end + subgraph g5["perception"] + n72["/robot_1/perception/stereo_image_proc/disparity_node"] + n73["/robot_1/perception/stereo_pointcloud"] + end + subgraph g6["robot_1"] + n1["/robot_1/Container"] + n5["/robot_1/gossip_node"] + n74["/robot_1/random_walk_node"] + n75["/robot_1/robot_state_publisher"] + n78["/robot_1/topic_keepalive"] + n81["/robot_1/vdb_mapping"] + n82["/robot_1/world_to_map_broadcaster"] + end + subgraph g7["root"] + n0["/action_relay_client"] + end + subgraph g8["sensors"] + n76["/robot_1/sensors/lidar_point_cloud_filter"] + end + subgraph g9["takeoff_landing_planner"] + n77["/robot_1/takeoff_landing_planner/takeoff_landing_task"] + end + subgraph g10["trajectory_controller"] + n79["/robot_1/trajectory_controller/fixed_trajectory_task"] + n80["/robot_1/trajectory_controller/trajectory_control_node"] + end + d0(["/clock (no publishers)"]) -->|"/clock
Clock"| n1 + d0 -->|"/clock
Clock"| n2 + d0 -->|"/clock
Clock"| n3 + d0 -->|"/clock
Clock"| n4 + d0 -->|"/clock
Clock"| n5 + d0 -->|"/clock
Clock"| n6 + d0 -->|"/clock
Clock"| n7 + d0 -->|"/clock
Clock"| n8 + d0 -->|"/clock
Clock"| n9 + d0 -->|"/clock
Clock"| n10 + d0 -->|"/clock
Clock"| n11 + d0 -->|"/clock
Clock"| n12 + d0 -->|"/clock
Clock"| n13 + d0 -->|"/clock
Clock"| n14 + d0 -->|"/clock
Clock"| n15 + d0 -->|"/clock
Clock"| n16 + d0 -->|"/clock
Clock"| n17 + d0 -->|"/clock
Clock"| n18 + d0 -->|"/clock
Clock"| n19 + d0 -->|"/clock
Clock"| n20 + d0 -->|"/clock
Clock"| n21 + d0 -->|"/clock
Clock"| n22 + d0 -->|"/clock
Clock"| n23 + d0 -->|"/clock
Clock"| n24 + d0 -->|"/clock
Clock"| n25 + d0 -->|"/clock
Clock"| n26 + d0 -->|"/clock
Clock"| n27 + d0 -->|"/clock
Clock"| n28 + d0 -->|"/clock
Clock"| n29 + d0 -->|"/clock
Clock"| n30 + d0 -->|"/clock
Clock"| n31 + d0 -->|"/clock
Clock"| n32 + d0 -->|"/clock
Clock"| n33 + d0 -->|"/clock
Clock"| n34 + d0 -->|"/clock
Clock"| n35 + d0 -->|"/clock
Clock"| n36 + d0 -->|"/clock
Clock"| n37 + d0 -->|"/clock
Clock"| n38 + d0 -->|"/clock
Clock"| n39 + d0 -->|"/clock
Clock"| n40 + d0 -->|"/clock
Clock"| n41 + d0 -->|"/clock
Clock"| n42 + d0 -->|"/clock
Clock"| n43 + d0 -->|"/clock
Clock"| n44 + d0 -->|"/clock
Clock"| n45 + d0 -->|"/clock
Clock"| n46 + d0 -->|"/clock
Clock"| n47 + d0 -->|"/clock
Clock"| n48 + d0 -->|"/clock
Clock"| n49 + d0 -->|"/clock
Clock"| n50 + d0 -->|"/clock
Clock"| n51 + d0 -->|"/clock
Clock"| n52 + d0 -->|"/clock
Clock"| n53 + d0 -->|"/clock
Clock"| n54 + d0 -->|"/clock
Clock"| n55 + d0 -->|"/clock
Clock"| n56 + d0 -->|"/clock
Clock"| n57 + d0 -->|"/clock
Clock"| n58 + d0 -->|"/clock
Clock"| n59 + d0 -->|"/clock
Clock"| n60 + d0 -->|"/clock
Clock"| n61 + d0 -->|"/clock
Clock"| n62 + d0 -->|"/clock
Clock"| n63 + d0 -->|"/clock
Clock"| n64 + d0 -->|"/clock
Clock"| n65 + d0 -->|"/clock
Clock"| n66 + d0 -->|"/clock
Clock"| n67 + d0 -->|"/clock
Clock"| n68 + d0 -->|"/clock
Clock"| n69 + d0 -->|"/clock
Clock"| n70 + d0 -->|"/clock
Clock"| n71 + d0 -->|"/clock
Clock"| n72 + d0 -->|"/clock
Clock"| n73 + d0 -->|"/clock
Clock"| n74 + d0 -->|"/clock
Clock"| n75 + d0 -->|"/clock
Clock"| n76 + d0 -->|"/clock
Clock"| n77 + d0 -->|"/clock
Clock"| n78 + d0 -->|"/clock
Clock"| n79 + d0 -->|"/clock
Clock"| n80 + d0 -->|"/clock
Clock"| n81 + d0 -->|"/clock
Clock"| n82 + n34 -->|"/diagnostics
DiagnosticArray"| d1(["/diagnostics (no subscribers)"]) + n36 -->|"/diagnostics
DiagnosticArray"| d1 + n5 -->|"/gossip/peers
PeerProfile"| n5 + n25 -->|"/move_base_simple/goal
PoseStamped"| d2(["/move_base_simple/goal (no subscribers)"]) + d3(["/robot_1/behavior/drone_safety_monitor/command (no publishers)"]) -->|"/robot_1/behavior/drone_safety_monitor/command
String"| n2 + n2 -->|"/robot_1/behavior/drone_safety_monitor/state_estimate_timed_out
Bool"| n77 + n70 -->|"/robot_1/control/reset_integrators
Empty"| n3 + n3 -->|"/robot_1/control/vx_pid_info
PIDInfo"| d4(["/robot_1/control/vx_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/vy_pid_info
PIDInfo"| d5(["/robot_1/control/vy_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/vz_pid_info
PIDInfo"| d6(["/robot_1/control/vz_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/x_pid_info
PIDInfo"| d7(["/robot_1/control/x_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/y_pid_info
PIDInfo"| d8(["/robot_1/control/y_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/z_pid_info
PIDInfo"| d9(["/robot_1/control/z_pid_info (no subscribers)"]) + n5 -->|"/robot_1/coordination/peer_registry
PeerProfile"| d10(["/robot_1/coordination/peer_registry (no subscribers)"]) + n69 -->|"/robot_1/cross_track_error
PoseStamped"| d11(["/robot_1/cross_track_error (no subscribers)"]) + n4 -->|"/robot_1/droan/background_expanded
Image"| d12(["/robot_1/droan/background_expanded (no subscribers)"]) + d13(["/robot_1/droan/clear_map (no publishers)"]) -->|"/robot_1/droan/clear_map
Empty"| n4 + d14(["/robot_1/droan/disparity_graph (no publishers)"]) -->|"/robot_1/droan/disparity_graph
MarkerArray"| n78 + d15(["/robot_1/droan/disparity_map_debug (no publishers)"]) -->|"/robot_1/droan/disparity_map_debug
MarkerArray"| n78 + d16(["/robot_1/droan/expansion_cloud (no publishers)"]) -->|"/robot_1/droan/expansion_cloud
PointCloud2"| n78 + d17(["/robot_1/droan/expansion_poly (no publishers)"]) -->|"/robot_1/droan/expansion_poly
MarkerArray"| n78 + n4 -->|"/robot_1/droan/fg_bg_cloud
PointCloud2"| n78 + n4 -->|"/robot_1/droan/foreground_expanded
Image"| d18(["/robot_1/droan/foreground_expanded (no subscribers)"]) + d19(["/robot_1/droan/frustum (no publishers)"]) -->|"/robot_1/droan/frustum
Marker"| n78 + n4 -->|"/robot_1/droan/graph_vis
MarkerArray"| n78 + n4 -->|"/robot_1/droan/local_planner_global_plan_vis
MarkerArray"| n78 + d20(["/robot_1/droan/reset_stuck (no publishers)"]) -->|"/robot_1/droan/reset_stuck
Empty"| n4 + n4 -->|"/robot_1/droan/rewind_info
MarkerArray"| n78 + n4 -->|"/robot_1/droan/stuck
Bool"| d21(["/robot_1/droan/stuck (no subscribers)"]) + n4 -->|"/robot_1/droan/traj_debug
MarkerArray"| n78 + d22(["/robot_1/droan/trajectory_library_vis (no publishers)"]) -->|"/robot_1/droan/trajectory_library_vis
MarkerArray"| n78 + d23(["/robot_1/droan/virtual_obstacles (no publishers)"]) -->|"/robot_1/droan/virtual_obstacles
MarkerArray"| n78 + n69 -->|"/robot_1/global_plan
Path"| n4 + n69 -->|"/robot_1/global_plan
Path"| n5 + n69 -->|"/robot_1/global_plan
Path"| n78 + n74 -->|"/robot_1/global_plan
Path"| n4 + n74 -->|"/robot_1/global_plan
Path"| n5 + n74 -->|"/robot_1/global_plan
Path"| n78 + d24(["/robot_1/interface/attitude_thrust_command (no publishers)"]) -->|"/robot_1/interface/attitude_thrust_command
AttitudeThrust"| n70 + d25(["/robot_1/interface/cmd_attitude_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_attitude_thrust
AttitudeThrust"| n70 + n69 -->|"/robot_1/interface/cmd_pose
PoseStamped"| n70 + d26(["/robot_1/interface/cmd_rate_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_rate_thrust
RateThrust"| n70 + n3 -->|"/robot_1/interface/cmd_roll_pitch_yawrate_thrust
RollPitchYawrateThrust"| n70 + d27(["/robot_1/interface/cmd_torque_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_torque_thrust
TorqueThrust"| n70 + n69 -->|"/robot_1/interface/cmd_velocity
TwistStamped"| n70 + n70 -->|"/robot_1/interface/has_control
Bool"| n77 + n70 -->|"/robot_1/interface/is_armed
Bool"| n77 + d28(["/robot_1/interface/mavros/actuator_control (no publishers)"]) -->|"/robot_1/interface/mavros/actuator_control
ActuatorControl"| n6 + d29(["/robot_1/interface/mavros/adsb/send (no publishers)"]) -->|"/robot_1/interface/mavros/adsb/send
ADSBVehicle"| n7 + n7 -->|"/robot_1/interface/mavros/adsb/vehicle
ADSBVehicle"| d30(["/robot_1/interface/mavros/adsb/vehicle (no subscribers)"]) + n8 -->|"/robot_1/interface/mavros/altitude
Altitude"| d31(["/robot_1/interface/mavros/altitude (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/battery
BatteryState"| d32(["/robot_1/interface/mavros/battery (no subscribers)"]) + n9 -->|"/robot_1/interface/mavros/cam_imu_sync/cam_imu_stamp
CamIMUStamp"| d33(["/robot_1/interface/mavros/cam_imu_sync/cam_imu_stamp (no subscribers)"]) + n10 -->|"/robot_1/interface/mavros/camera/image_captured
CameraImageCaptured"| d34(["/robot_1/interface/mavros/camera/image_captured (no subscribers)"]) + d35(["/robot_1/interface/mavros/cellular_status/status (no publishers)"]) -->|"/robot_1/interface/mavros/cellular_status/status
CellularStatus"| n11 + d36(["/robot_1/interface/mavros/companion_process/status (no publishers)"]) -->|"/robot_1/interface/mavros/companion_process/status
CompanionProcessStatus"| n13 + n14 -->|"/robot_1/interface/mavros/debug_value/debug
DebugValue"| d37(["/robot_1/interface/mavros/debug_value/debug (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/debug_float_array
DebugValue"| d38(["/robot_1/interface/mavros/debug_value/debug_float_array (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/debug_vector
DebugValue"| d39(["/robot_1/interface/mavros/debug_value/debug_vector (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/named_value_float
DebugValue"| d40(["/robot_1/interface/mavros/debug_value/named_value_float (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/named_value_int
DebugValue"| d41(["/robot_1/interface/mavros/debug_value/named_value_int (no subscribers)"]) + d42(["/robot_1/interface/mavros/debug_value/send (no publishers)"]) -->|"/robot_1/interface/mavros/debug_value/send
DebugValue"| n14 + n15 -->|"/robot_1/interface/mavros/esc_status/info
ESCInfo"| d43(["/robot_1/interface/mavros/esc_status/info (no subscribers)"]) + n15 -->|"/robot_1/interface/mavros/esc_status/status
ESCStatus"| d44(["/robot_1/interface/mavros/esc_status/status (no subscribers)"]) + n16 -->|"/robot_1/interface/mavros/esc_telemetry/telemetry
ESCTelemetry"| d45(["/robot_1/interface/mavros/esc_telemetry/telemetry (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/estimator_status
EstimatorStatus"| d46(["/robot_1/interface/mavros/estimator_status (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/extended_state
ExtendedState"| n77 + d47(["/robot_1/interface/mavros/fake_gps/mocap/tf (no publishers)"]) -->|"/robot_1/interface/mavros/fake_gps/mocap/tf
TransformStamped"| n17 + n19 -->|"/robot_1/interface/mavros/geofence/fences
WaypointList"| d48(["/robot_1/interface/mavros/geofence/fences (no subscribers)"]) + n20 -->|"/robot_1/interface/mavros/gimbal_control/device/attitude_status
GimbalDeviceAttitudeStatus"| d49(["/robot_1/interface/mavros/gimbal_control/device/attitude_status (no subscribers)"]) + n20 -->|"/robot_1/interface/mavros/gimbal_control/device/info
GimbalDeviceInformation"| d50(["/robot_1/interface/mavros/gimbal_control/device/info (no subscribers)"]) + d51(["/robot_1/interface/mavros/gimbal_control/device/set_attitude (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/device/set_attitude
GimbalDeviceSetAttitude"| n20 + n20 -->|"/robot_1/interface/mavros/gimbal_control/manager/info
GimbalManagerInformation"| d52(["/robot_1/interface/mavros/gimbal_control/manager/info (no subscribers)"]) + d53(["/robot_1/interface/mavros/gimbal_control/manager/set_attitude (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_attitude
GimbalManagerSetAttitude"| n20 + d54(["/robot_1/interface/mavros/gimbal_control/manager/set_manual_control (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_manual_control
GimbalManagerSetPitchyaw"| n20 + d55(["/robot_1/interface/mavros/gimbal_control/manager/set_pitchyaw (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_pitchyaw
GimbalManagerSetPitchyaw"| n20 + n20 -->|"/robot_1/interface/mavros/gimbal_control/manager/status
GimbalManagerStatus"| d56(["/robot_1/interface/mavros/gimbal_control/manager/status (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/compass_hdg
Float64"| n5 + n21 -->|"/robot_1/interface/mavros/global_position/global
NavSatFix"| n54 + n21 -->|"/robot_1/interface/mavros/global_position/global
NavSatFix"| n70 + n21 -->|"/robot_1/interface/mavros/global_position/gp_lp_offset
PoseStamped"| d57(["/robot_1/interface/mavros/global_position/gp_lp_offset (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/gp_origin
GeoPointStamped"| n25 + n21 -->|"/robot_1/interface/mavros/global_position/local
Odometry"| n70 + n21 -->|"/robot_1/interface/mavros/global_position/raw/fix
NavSatFix"| n5 + n21 -->|"/robot_1/interface/mavros/global_position/raw/gps_vel
TwistStamped"| d58(["/robot_1/interface/mavros/global_position/raw/gps_vel (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/raw/satellites
UInt32"| d59(["/robot_1/interface/mavros/global_position/raw/satellites (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/rel_alt
Float64"| d60(["/robot_1/interface/mavros/global_position/rel_alt (no subscribers)"]) + d61(["/robot_1/interface/mavros/global_position/set_gp_origin (no publishers)"]) -->|"/robot_1/interface/mavros/global_position/set_gp_origin
GeoPointStamped"| n21 + d62(["/robot_1/interface/mavros/gps_input/gps_input (no publishers)"]) -->|"/robot_1/interface/mavros/gps_input/gps_input
GPSINPUT"| n22 + n23 -->|"/robot_1/interface/mavros/gps_rtk/rtk_baseline
RTKBaseline"| d63(["/robot_1/interface/mavros/gps_rtk/rtk_baseline (no subscribers)"]) + d64(["/robot_1/interface/mavros/gps_rtk/send_rtcm (no publishers)"]) -->|"/robot_1/interface/mavros/gps_rtk/send_rtcm
RTCM"| n23 + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps1/raw
GPSRAW"| d65(["/robot_1/interface/mavros/gpsstatus/gps1/raw (no subscribers)"]) + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps1/rtk
GPSRTK"| d66(["/robot_1/interface/mavros/gpsstatus/gps1/rtk (no subscribers)"]) + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps2/raw
GPSRAW"| d67(["/robot_1/interface/mavros/gpsstatus/gps2/raw (no subscribers)"]) + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps2/rtk
GPSRTK"| d68(["/robot_1/interface/mavros/gpsstatus/gps2/rtk (no subscribers)"]) + n26 -->|"/robot_1/interface/mavros/hil/actuator_controls
HilActuatorControls"| d69(["/robot_1/interface/mavros/hil/actuator_controls (no subscribers)"]) + n26 -->|"/robot_1/interface/mavros/hil/controls
HilControls"| d70(["/robot_1/interface/mavros/hil/controls (no subscribers)"]) + d71(["/robot_1/interface/mavros/hil/gps (no publishers)"]) -->|"/robot_1/interface/mavros/hil/gps
HilGPS"| n26 + d72(["/robot_1/interface/mavros/hil/imu_ned (no publishers)"]) -->|"/robot_1/interface/mavros/hil/imu_ned
HilSensor"| n26 + d73(["/robot_1/interface/mavros/hil/optical_flow (no publishers)"]) -->|"/robot_1/interface/mavros/hil/optical_flow
OpticalFlowRad"| n26 + d74(["/robot_1/interface/mavros/hil/rc_inputs (no publishers)"]) -->|"/robot_1/interface/mavros/hil/rc_inputs
RCIn"| n26 + d75(["/robot_1/interface/mavros/hil/state (no publishers)"]) -->|"/robot_1/interface/mavros/hil/state
HilStateQuaternion"| n26 + n27 -->|"/robot_1/interface/mavros/home_position/home
HomePosition"| n21 + n27 -->|"/robot_1/interface/mavros/home_position/home
HomePosition"| n70 + n70 -->|"/robot_1/interface/mavros/home_position/set
HomePosition"| n27 + n28 -->|"/robot_1/interface/mavros/imu/data
Imu"| d76(["/robot_1/interface/mavros/imu/data (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/data_raw
Imu"| d77(["/robot_1/interface/mavros/imu/data_raw (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/diff_pressure
FluidPressure"| d78(["/robot_1/interface/mavros/imu/diff_pressure (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/mag
MagneticField"| d79(["/robot_1/interface/mavros/imu/mag (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/static_pressure
FluidPressure"| d80(["/robot_1/interface/mavros/imu/static_pressure (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/temperature_baro
Temperature"| d81(["/robot_1/interface/mavros/imu/temperature_baro (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/temperature_imu
Temperature"| d82(["/robot_1/interface/mavros/imu/temperature_imu (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/landing_target/lt_marker
Vector3Stamped"| d83(["/robot_1/interface/mavros/landing_target/lt_marker (no subscribers)"]) + d84(["/robot_1/interface/mavros/landing_target/pose (no publishers)"]) -->|"/robot_1/interface/mavros/landing_target/pose
PoseStamped"| n29 + n29 -->|"/robot_1/interface/mavros/landing_target/pose_in
PoseStamped"| d85(["/robot_1/interface/mavros/landing_target/pose_in (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/accel
AccelWithCovarianceStamped"| d86(["/robot_1/interface/mavros/local_position/accel (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/odom
Odometry"| n71 + n30 -->|"/robot_1/interface/mavros/local_position/pose
PoseStamped"| n54 + n30 -->|"/robot_1/interface/mavros/local_position/pose_cov
PoseWithCovarianceStamped"| d87(["/robot_1/interface/mavros/local_position/pose_cov (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/velocity_body
TwistStamped"| d88(["/robot_1/interface/mavros/local_position/velocity_body (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/velocity_body_cov
TwistWithCovarianceStamped"| d89(["/robot_1/interface/mavros/local_position/velocity_body_cov (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/velocity_local
TwistStamped"| d90(["/robot_1/interface/mavros/local_position/velocity_local (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/log_transfer/raw/log_data
LogData"| d91(["/robot_1/interface/mavros/log_transfer/raw/log_data (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/log_transfer/raw/log_entry
LogEntry"| d92(["/robot_1/interface/mavros/log_transfer/raw/log_entry (no subscribers)"]) + n32 -->|"/robot_1/interface/mavros/mag_calibration/report
MagnetometerReporter"| d93(["/robot_1/interface/mavros/mag_calibration/report (no subscribers)"]) + n32 -->|"/robot_1/interface/mavros/mag_calibration/status
UInt8"| d94(["/robot_1/interface/mavros/mag_calibration/status (no subscribers)"]) + n33 -->|"/robot_1/interface/mavros/manual_control/control
ManualControl"| d95(["/robot_1/interface/mavros/manual_control/control (no subscribers)"]) + d96(["/robot_1/interface/mavros/manual_control/send (no publishers)"]) -->|"/robot_1/interface/mavros/manual_control/send
ManualControl"| n33 + n37 -->|"/robot_1/interface/mavros/mission/reached
WaypointReached"| d97(["/robot_1/interface/mavros/mission/reached (no subscribers)"]) + n37 -->|"/robot_1/interface/mavros/mission/waypoints
WaypointList"| d98(["/robot_1/interface/mavros/mission/waypoints (no subscribers)"]) + d99(["/robot_1/interface/mavros/mocap/pose (no publishers)"]) -->|"/robot_1/interface/mavros/mocap/pose
PoseStamped"| n38 + d100(["/robot_1/interface/mavros/mocap/tf (no publishers)"]) -->|"/robot_1/interface/mavros/mocap/tf
TransformStamped"| n38 + d101(["/robot_1/interface/mavros/mount_control/command (no publishers)"]) -->|"/robot_1/interface/mavros/mount_control/command
MountControl"| n39 + n39 -->|"/robot_1/interface/mavros/mount_control/orientation
Quaternion"| d102(["/robot_1/interface/mavros/mount_control/orientation (no subscribers)"]) + n39 -->|"/robot_1/interface/mavros/mount_control/status
Vector3Stamped"| d103(["/robot_1/interface/mavros/mount_control/status (no subscribers)"]) + n40 -->|"/robot_1/interface/mavros/nav_controller_output/output
NavControllerOutput"| d104(["/robot_1/interface/mavros/nav_controller_output/output (no subscribers)"]) + d105(["/robot_1/interface/mavros/obstacle/send (no publishers)"]) -->|"/robot_1/interface/mavros/obstacle/send
LaserScan"| n41 + d106(["/robot_1/interface/mavros/obstacle_distance_3d/send (no publishers)"]) -->|"/robot_1/interface/mavros/obstacle_distance_3d/send
ObstacleDistance3D"| n42 + n43 -->|"/robot_1/interface/mavros/odometry/in
Odometry"| d107(["/robot_1/interface/mavros/odometry/in (no subscribers)"]) + d108(["/robot_1/interface/mavros/odometry/out (no publishers)"]) -->|"/robot_1/interface/mavros/odometry/out
Odometry"| n43 + d109(["/robot_1/interface/mavros/onboard_computer/status (no publishers)"]) -->|"/robot_1/interface/mavros/onboard_computer/status
OnboardComputerStatus"| n44 + d110(["/robot_1/interface/mavros/open_drone_id/basic_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/basic_id
OpenDroneIDBasicID"| n45 + d111(["/robot_1/interface/mavros/open_drone_id/operator_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/operator_id
OpenDroneIDOperatorID"| n45 + d112(["/robot_1/interface/mavros/open_drone_id/self_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/self_id
OpenDroneIDSelfID"| n45 + d113(["/robot_1/interface/mavros/open_drone_id/system (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/system
OpenDroneIDSystem"| n45 + d114(["/robot_1/interface/mavros/open_drone_id/system_update (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/system_update
OpenDroneIDSystemUpdate"| n45 + n46 -->|"/robot_1/interface/mavros/optical_flow/ground_distance
Range"| d115(["/robot_1/interface/mavros/optical_flow/ground_distance (no subscribers)"]) + n46 -->|"/robot_1/interface/mavros/optical_flow/raw/optical_flow
OpticalFlow"| d116(["/robot_1/interface/mavros/optical_flow/raw/optical_flow (no subscribers)"]) + d117(["/robot_1/interface/mavros/optical_flow/raw/send (no publishers)"]) -->|"/robot_1/interface/mavros/optical_flow/raw/send
OpticalFlow"| n46 + n47 -->|"/robot_1/interface/mavros/param/event
ParamEvent"| d118(["/robot_1/interface/mavros/param/event (no subscribers)"]) + d119(["/robot_1/interface/mavros/play_tune (no publishers)"]) -->|"/robot_1/interface/mavros/play_tune
PlayTuneV2"| n48 + n49 -->|"/robot_1/interface/mavros/px4flow/ground_distance
Range"| d120(["/robot_1/interface/mavros/px4flow/ground_distance (no subscribers)"]) + n49 -->|"/robot_1/interface/mavros/px4flow/raw/optical_flow_rad
OpticalFlowRad"| d121(["/robot_1/interface/mavros/px4flow/raw/optical_flow_rad (no subscribers)"]) + d122(["/robot_1/interface/mavros/px4flow/raw/send (no publishers)"]) -->|"/robot_1/interface/mavros/px4flow/raw/send
OpticalFlowRad"| n49 + n49 -->|"/robot_1/interface/mavros/px4flow/temperature
Temperature"| d123(["/robot_1/interface/mavros/px4flow/temperature (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/radio_status
RadioStatus"| d124(["/robot_1/interface/mavros/radio_status (no subscribers)"]) + n50 -->|"/robot_1/interface/mavros/rallypoint/rallypoints
WaypointList"| d125(["/robot_1/interface/mavros/rallypoint/rallypoints (no subscribers)"]) + n51 -->|"/robot_1/interface/mavros/rc/in
RCIn"| d126(["/robot_1/interface/mavros/rc/in (no subscribers)"]) + n51 -->|"/robot_1/interface/mavros/rc/out
RCOut"| d127(["/robot_1/interface/mavros/rc/out (no subscribers)"]) + d128(["/robot_1/interface/mavros/rc/override (no publishers)"]) -->|"/robot_1/interface/mavros/rc/override
OverrideRCIn"| n51 + d129(["/robot_1/interface/mavros/setpoint_accel/accel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_accel/accel
Vector3Stamped"| n52 + d130(["/robot_1/interface/mavros/setpoint_attitude/cmd_vel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_attitude/cmd_vel
TwistStamped"| n53 + d131(["/robot_1/interface/mavros/setpoint_attitude/thrust (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_attitude/thrust
Thrust"| n53 + n70 -->|"/robot_1/interface/mavros/setpoint_position/global
GeoPoseStamped"| n54 + d132(["/robot_1/interface/mavros/setpoint_position/global_to_local (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_position/global_to_local
GeoPoseStamped"| n54 + n70 -->|"/robot_1/interface/mavros/setpoint_position/local
PoseStamped"| n54 + n70 -->|"/robot_1/interface/mavros/setpoint_raw/attitude
AttitudeTarget"| n55 + d133(["/robot_1/interface/mavros/setpoint_raw/global (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_raw/global
GlobalPositionTarget"| n55 + n70 -->|"/robot_1/interface/mavros/setpoint_raw/local
PositionTarget"| n55 + n55 -->|"/robot_1/interface/mavros/setpoint_raw/target_attitude
AttitudeTarget"| d134(["/robot_1/interface/mavros/setpoint_raw/target_attitude (no subscribers)"]) + n55 -->|"/robot_1/interface/mavros/setpoint_raw/target_global
GlobalPositionTarget"| d135(["/robot_1/interface/mavros/setpoint_raw/target_global (no subscribers)"]) + n55 -->|"/robot_1/interface/mavros/setpoint_raw/target_local
PositionTarget"| d136(["/robot_1/interface/mavros/setpoint_raw/target_local (no subscribers)"]) + n56 -->|"/robot_1/interface/mavros/setpoint_trajectory/desired
Path"| d137(["/robot_1/interface/mavros/setpoint_trajectory/desired (no subscribers)"]) + d138(["/robot_1/interface/mavros/setpoint_trajectory/local (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_trajectory/local
MultiDOFJointTrajectory"| n56 + d139(["/robot_1/interface/mavros/setpoint_velocity/cmd_vel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_velocity/cmd_vel
TwistStamped"| n57 + d140(["/robot_1/interface/mavros/setpoint_velocity/cmd_vel_unstamped (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_velocity/cmd_vel_unstamped
Twist"| n57 + n58 -->|"/robot_1/interface/mavros/sim_state/acceleration
Vector3Stamped"| d141(["/robot_1/interface/mavros/sim_state/acceleration (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/attitude
Imu"| d142(["/robot_1/interface/mavros/sim_state/attitude (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/global_position
NavSatFix"| d143(["/robot_1/interface/mavros/sim_state/global_position (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/velocity_body
TwistStamped"| d144(["/robot_1/interface/mavros/sim_state/velocity_body (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/velocity_local
TwistStamped"| d145(["/robot_1/interface/mavros/sim_state/velocity_local (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/state
State"| n70 + n59 -->|"/robot_1/interface/mavros/status_event
StatusEvent"| d146(["/robot_1/interface/mavros/status_event (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/statustext/recv
StatusText"| d147(["/robot_1/interface/mavros/statustext/recv (no subscribers)"]) + d148(["/robot_1/interface/mavros/statustext/send (no publishers)"]) -->|"/robot_1/interface/mavros/statustext/send
StatusText"| n59 + n59 -->|"/robot_1/interface/mavros/sys_status
SysStatus"| d149(["/robot_1/interface/mavros/sys_status (no subscribers)"]) + n6 -->|"/robot_1/interface/mavros/target_actuator_control
ActuatorControl"| d150(["/robot_1/interface/mavros/target_actuator_control (no subscribers)"]) + n61 -->|"/robot_1/interface/mavros/terrain/report
TerrainReport"| d151(["/robot_1/interface/mavros/terrain/report (no subscribers)"]) + n62 -->|"/robot_1/interface/mavros/time_reference
TimeReference"| d152(["/robot_1/interface/mavros/time_reference (no subscribers)"]) + n62 -->|"/robot_1/interface/mavros/timesync_status
TimesyncStatus"| d153(["/robot_1/interface/mavros/timesync_status (no subscribers)"]) + n63 -->|"/robot_1/interface/mavros/trajectory/desired
Trajectory"| d154(["/robot_1/interface/mavros/trajectory/desired (no subscribers)"]) + d155(["/robot_1/interface/mavros/trajectory/generated (no publishers)"]) -->|"/robot_1/interface/mavros/trajectory/generated
Trajectory"| n63 + d156(["/robot_1/interface/mavros/trajectory/path (no publishers)"]) -->|"/robot_1/interface/mavros/trajectory/path
Path"| n63 + d157(["/robot_1/interface/mavros/tunnel/in (no publishers)"]) -->|"/robot_1/interface/mavros/tunnel/in
Tunnel"| n64 + n64 -->|"/robot_1/interface/mavros/tunnel/out
Tunnel"| d158(["/robot_1/interface/mavros/tunnel/out (no subscribers)"]) + n65 -->|"/robot_1/interface/mavros/vfr_hud
VfrHud"| d159(["/robot_1/interface/mavros/vfr_hud (no subscribers)"]) + d160(["/robot_1/interface/mavros/vision_pose/pose (no publishers)"]) -->|"/robot_1/interface/mavros/vision_pose/pose
PoseStamped"| n66 + d161(["/robot_1/interface/mavros/vision_pose/pose_cov (no publishers)"]) -->|"/robot_1/interface/mavros/vision_pose/pose_cov
PoseWithCovarianceStamped"| n66 + d162(["/robot_1/interface/mavros/vision_speed/speed_twist (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_twist
TwistStamped"| n67 + d163(["/robot_1/interface/mavros/vision_speed/speed_twist_cov (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_twist_cov
TwistWithCovarianceStamped"| n67 + d164(["/robot_1/interface/mavros/vision_speed/speed_vector (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_vector
Vector3Stamped"| n67 + n68 -->|"/robot_1/interface/mavros/wind_estimation
TwistWithCovarianceStamped"| d165(["/robot_1/interface/mavros/wind_estimation (no subscribers)"]) + d166(["/robot_1/interface/pose_command (no publishers)"]) -->|"/robot_1/interface/pose_command
PoseStamped"| n70 + d167(["/robot_1/interface/rate_thrust_command (no publishers)"]) -->|"/robot_1/interface/rate_thrust_command
RateThrust"| n70 + d168(["/robot_1/interface/roll_pitch_yawrate_thrust_command (no publishers)"]) -->|"/robot_1/interface/roll_pitch_yawrate_thrust_command
RollPitchYawrateThrust"| n70 + d169(["/robot_1/interface/torque_thrust_command (no publishers)"]) -->|"/robot_1/interface/torque_thrust_command
TorqueThrust"| n70 + d170(["/robot_1/interface/velocity_command (no publishers)"]) -->|"/robot_1/interface/velocity_command
TwistStamped"| n70 + d171(["/robot_1/joint_states (no publishers)"]) -->|"/robot_1/joint_states
JointState"| n75 + d172(["/robot_1/macvo/odometry (no publishers)"]) -->|"/robot_1/macvo/odometry
Odometry"| n78 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n2 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n3 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n69 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n74 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n77 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n78 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n79 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n80 + d173(["/robot_1/perception/macvo/disparity (no publishers)"]) -->|"/robot_1/perception/macvo/disparity
Image"| n78 + d174(["/robot_1/perception/macvo/point_cloud (no publishers)"]) -->|"/robot_1/perception/macvo/point_cloud
PointCloud"| n78 + n72 -->|"/robot_1/perception/stereo_image_proc/disparity
DisparityImage"| n4 + n72 -->|"/robot_1/perception/stereo_image_proc/disparity
DisparityImage"| n73 + n73 -->|"/robot_1/perception/stereo_image_proc/point_cloud
PointCloud2"| n78 + n74 -->|"/robot_1/random_walk_node/goal_point_viz
Marker"| d175(["/robot_1/random_walk_node/goal_point_viz (no subscribers)"]) + n74 -->|"/robot_1/random_walk_node/traj_viz
Marker"| d176(["/robot_1/random_walk_node/traj_viz (no subscribers)"]) + n75 -->|"/robot_1/robot_description
String"| d177(["/robot_1/robot_description (no subscribers)"]) + d178(["/robot_1/sensors/front_stereo/left/camera_info (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n72 + d178 -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n73 + d178 -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n78 + d179(["/robot_1/sensors/front_stereo/left/depth_ground_truth (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/depth_ground_truth
Image"| n78 + d180(["/robot_1/sensors/front_stereo/left/image_rect (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n72 + d180 -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n73 + d180 -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n78 + d181(["/robot_1/sensors/front_stereo/right/camera_info (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n4 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n72 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n73 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n78 + d182(["/robot_1/sensors/front_stereo/right/depth_ground_truth (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/depth_ground_truth
Image"| n78 + d183(["/robot_1/sensors/front_stereo/right/image_rect (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/image_rect
Image"| n72 + d183 -->|"/robot_1/sensors/front_stereo/right/image_rect
Image"| n78 + d184(["/robot_1/sensors/lidar/point_cloud (no publishers)"]) -->|"/robot_1/sensors/lidar/point_cloud
PointCloud2"| n78 + n76 -->|"/robot_1/sensors/ouster/point_cloud
PointCloud2"| n81 + d185(["/robot_1/sensors/ouster/point_cloud_raw (no publishers)"]) -->|"/robot_1/sensors/ouster/point_cloud_raw
PointCloud2"| n76 + n77 -->|"/robot_1/takeoff_landing_planner/is_airborne
Bool"| d186(["/robot_1/takeoff_landing_planner/is_airborne (no subscribers)"]) + d187(["/robot_1/takeoff_landing_planner/trajectory_completion_percentage (no publishers)"]) -->|"/robot_1/takeoff_landing_planner/trajectory_completion_percentage
Float32"| n77 + n80 -->|"/robot_1/trajectory_controller/closest_point
Odometry"| d188(["/robot_1/trajectory_controller/closest_point (no subscribers)"]) + n80 -->|"/robot_1/trajectory_controller/look_ahead
Odometry"| n4 + n80 -->|"/robot_1/trajectory_controller/projected_drone_pose
PoseStamped"| n69 + n80 -->|"/robot_1/trajectory_controller/tracking_error
Float32"| d189(["/robot_1/trajectory_controller/tracking_error (no subscribers)"]) + n80 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n3 + n80 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n4 + n80 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n69 + n80 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n77 + n80 -->|"/robot_1/trajectory_controller/tracking_point_velocity_magnitude
Float32"| d190(["/robot_1/trajectory_controller/tracking_point_velocity_magnitude (no subscribers)"]) + n80 -->|"/robot_1/trajectory_controller/traj_drone_point
Odometry"| d191(["/robot_1/trajectory_controller/traj_drone_point (no subscribers)"]) + n80 -->|"/robot_1/trajectory_controller/trajectory_completion_percentage
Float32"| n79 + n80 -->|"/robot_1/trajectory_controller/trajectory_controller_debug_markers
MarkerArray"| n78 + n77 -->|"/robot_1/trajectory_controller/trajectory_override
TrajectoryXYZVYaw"| n80 + n79 -->|"/robot_1/trajectory_controller/trajectory_override
TrajectoryXYZVYaw"| n80 + n4 -->|"/robot_1/trajectory_controller/trajectory_segment_to_add
TrajectoryXYZVYaw"| n80 + n80 -->|"/robot_1/trajectory_controller/trajectory_time
Float32"| d192(["/robot_1/trajectory_controller/trajectory_time (no subscribers)"]) + n80 -->|"/robot_1/trajectory_controller/trajectory_vis
MarkerArray"| n78 + n80 -->|"/robot_1/trajectory_controller/virtual_tracking_point
Odometry"| d193(["/robot_1/trajectory_controller/virtual_tracking_point (no subscribers)"]) + n81 -->|"/robot_1/vdb_mapping/vdb_map_overwrites
UpdateGrid"| d194(["/robot_1/vdb_mapping/vdb_map_overwrites (no subscribers)"]) + n81 -->|"/robot_1/vdb_mapping/vdb_map_pointcloud
PointCloud2"| d195(["/robot_1/vdb_mapping/vdb_map_pointcloud (no subscribers)"]) + n81 -->|"/robot_1/vdb_mapping/vdb_map_sections
UpdateGrid"| d196(["/robot_1/vdb_mapping/vdb_map_sections (no subscribers)"]) + n81 -->|"/robot_1/vdb_mapping/vdb_map_updates
UpdateGrid"| d197(["/robot_1/vdb_mapping/vdb_map_updates (no subscribers)"]) + n81 -->|"/robot_1/vdb_mapping/vdb_map_visualization
Marker"| n74 + n81 -->|"/robot_1/vdb_mapping/vdb_map_visualization
Marker"| n78 + n34 -->|"/tf
TFMessage"| n69 + n34 -->|"/tf
TFMessage"| n78 + n71 -->|"/tf
TFMessage"| n69 + n71 -->|"/tf
TFMessage"| n78 + n75 -->|"/tf
TFMessage"| n69 + n75 -->|"/tf
TFMessage"| n78 + n80 -->|"/tf
TFMessage"| n69 + n80 -->|"/tf
TFMessage"| n78 + n34 -->|"/tf_static
TFMessage"| n69 + n34 -->|"/tf_static
TFMessage"| n78 + n75 -->|"/tf_static
TFMessage"| n69 + n75 -->|"/tf_static
TFMessage"| n78 + n82 -->|"/tf_static
TFMessage"| n69 + n82 -->|"/tf_static
TFMessage"| n78 + n34 -->|"/uas2/mavlink_sink
Mavlink"| n36 + n36 -->|"/uas2/mavlink_source
Mavlink"| n34 +``` + + diff --git a/stacks/full_droan_cpu/wiring.md b/stacks/full_droan_cpu/wiring.md new file mode 100644 index 000000000..92ab3380a --- /dev/null +++ b/stacks/full_droan_cpu/wiring.md @@ -0,0 +1,7865 @@ +# Wiring snapshot: full_droan_cpu + +- **generated-by**: tests/system/test_wiring_snapshot.py +- **date**: 2026-08-20 22:16:26 +- **sim**: isaacsim +- **num_robots**: 1 +- **source-sha**: d02a3467b72d + +```mermaid +graph LR + subgraph g0["behavior"] + n2["/robot_1/behavior/drone_safety_monitor/drone_safety_monitor"] + end + subgraph g1["control"] + n3["/robot_1/control/pid_controller"] + end + subgraph g2["droan"] + n4["/robot_1/droan/disparity_expansion"] + n5["/robot_1/droan/droan_local_planner"] + end + subgraph g3["interface"] + n7["/robot_1/interface/mavros/actuator_control"] + n8["/robot_1/interface/mavros/adsb"] + n9["/robot_1/interface/mavros/altitude"] + n10["/robot_1/interface/mavros/cam_imu_sync"] + n11["/robot_1/interface/mavros/camera"] + n12["/robot_1/interface/mavros/cellular_status"] + n13["/robot_1/interface/mavros/cmd"] + n14["/robot_1/interface/mavros/companion_process"] + n15["/robot_1/interface/mavros/debug_value"] + n16["/robot_1/interface/mavros/esc_status"] + n17["/robot_1/interface/mavros/esc_telemetry"] + n18["/robot_1/interface/mavros/fake_gps"] + n19["/robot_1/interface/mavros/ftp"] + n20["/robot_1/interface/mavros/geofence"] + n21["/robot_1/interface/mavros/gimbal_control"] + n22["/robot_1/interface/mavros/global_position"] + n23["/robot_1/interface/mavros/gps_input"] + n24["/robot_1/interface/mavros/gps_rtk"] + n25["/robot_1/interface/mavros/gpsstatus"] + n26["/robot_1/interface/mavros/guided_target"] + n27["/robot_1/interface/mavros/hil"] + n28["/robot_1/interface/mavros/home_position"] + n29["/robot_1/interface/mavros/imu"] + n30["/robot_1/interface/mavros/landing_target"] + n31["/robot_1/interface/mavros/local_position"] + n32["/robot_1/interface/mavros/log_transfer"] + n33["/robot_1/interface/mavros/mag_calibration"] + n34["/robot_1/interface/mavros/manual_control"] + n35["/robot_1/interface/mavros/mavros"] + n36["/robot_1/interface/mavros/mavros_node"] + n37["/robot_1/interface/mavros/mavros_router"] + n38["/robot_1/interface/mavros/mission"] + n39["/robot_1/interface/mavros/mocap"] + n40["/robot_1/interface/mavros/mount_control"] + n41["/robot_1/interface/mavros/nav_controller_output"] + n42["/robot_1/interface/mavros/obstacle"] + n43["/robot_1/interface/mavros/obstacle_distance_3d"] + n44["/robot_1/interface/mavros/odometry"] + n45["/robot_1/interface/mavros/onboard_computer"] + n46["/robot_1/interface/mavros/open_drone_id"] + n47["/robot_1/interface/mavros/optical_flow"] + n48["/robot_1/interface/mavros/param"] + n49["/robot_1/interface/mavros/play_tune"] + n50["/robot_1/interface/mavros/px4flow"] + n51["/robot_1/interface/mavros/rallypoint"] + n52["/robot_1/interface/mavros/rc"] + n53["/robot_1/interface/mavros/setpoint_accel"] + n54["/robot_1/interface/mavros/setpoint_attitude"] + n55["/robot_1/interface/mavros/setpoint_position"] + n56["/robot_1/interface/mavros/setpoint_raw"] + n57["/robot_1/interface/mavros/setpoint_trajectory"] + n58["/robot_1/interface/mavros/setpoint_velocity"] + n59["/robot_1/interface/mavros/sim_state"] + n60["/robot_1/interface/mavros/sys"] + n61["/robot_1/interface/mavros/tdr_radio"] + n62["/robot_1/interface/mavros/terrain"] + n63["/robot_1/interface/mavros/time"] + n64["/robot_1/interface/mavros/trajectory"] + n65["/robot_1/interface/mavros/tunnel"] + n66["/robot_1/interface/mavros/vfr_hud"] + n67["/robot_1/interface/mavros/vision_pose"] + n68["/robot_1/interface/mavros/vision_speed"] + n69["/robot_1/interface/mavros/wind"] + n70["/robot_1/interface/odom_modifier"] + n71["/robot_1/interface/robot_interface"] + end + subgraph g4["odometry_conversion"] + n72["/robot_1/odometry_conversion/odometry_conversion"] + end + subgraph g5["perception"] + n73["/robot_1/perception/stereo_image_proc/disparity_node"] + n74["/robot_1/perception/stereo_pointcloud"] + end + subgraph g6["robot_1"] + n1["/robot_1/Container"] + n6["/robot_1/gossip_node"] + n75["/robot_1/random_walk_node"] + n76["/robot_1/robot_state_publisher"] + n79["/robot_1/topic_keepalive"] + n82["/robot_1/vdb_mapping"] + n83["/robot_1/world_to_map_broadcaster"] + end + subgraph g7["root"] + n0["/action_relay_client"] + end + subgraph g8["sensors"] + n77["/robot_1/sensors/lidar_point_cloud_filter"] + end + subgraph g9["takeoff_landing_planner"] + n78["/robot_1/takeoff_landing_planner/takeoff_landing_task"] + end + subgraph g10["trajectory_controller"] + n80["/robot_1/trajectory_controller/fixed_trajectory_task"] + n81["/robot_1/trajectory_controller/trajectory_control_node"] + end + d0(["/clock (no publishers)"]) -->|"/clock
Clock"| n1 + d0 -->|"/clock
Clock"| n2 + d0 -->|"/clock
Clock"| n3 + d0 -->|"/clock
Clock"| n4 + d0 -->|"/clock
Clock"| n5 + d0 -->|"/clock
Clock"| n6 + d0 -->|"/clock
Clock"| n7 + d0 -->|"/clock
Clock"| n8 + d0 -->|"/clock
Clock"| n9 + d0 -->|"/clock
Clock"| n10 + d0 -->|"/clock
Clock"| n11 + d0 -->|"/clock
Clock"| n12 + d0 -->|"/clock
Clock"| n13 + d0 -->|"/clock
Clock"| n14 + d0 -->|"/clock
Clock"| n15 + d0 -->|"/clock
Clock"| n16 + d0 -->|"/clock
Clock"| n17 + d0 -->|"/clock
Clock"| n18 + d0 -->|"/clock
Clock"| n19 + d0 -->|"/clock
Clock"| n20 + d0 -->|"/clock
Clock"| n21 + d0 -->|"/clock
Clock"| n22 + d0 -->|"/clock
Clock"| n23 + d0 -->|"/clock
Clock"| n24 + d0 -->|"/clock
Clock"| n25 + d0 -->|"/clock
Clock"| n26 + d0 -->|"/clock
Clock"| n27 + d0 -->|"/clock
Clock"| n28 + d0 -->|"/clock
Clock"| n29 + d0 -->|"/clock
Clock"| n30 + d0 -->|"/clock
Clock"| n31 + d0 -->|"/clock
Clock"| n32 + d0 -->|"/clock
Clock"| n33 + d0 -->|"/clock
Clock"| n34 + d0 -->|"/clock
Clock"| n35 + d0 -->|"/clock
Clock"| n36 + d0 -->|"/clock
Clock"| n37 + d0 -->|"/clock
Clock"| n38 + d0 -->|"/clock
Clock"| n39 + d0 -->|"/clock
Clock"| n40 + d0 -->|"/clock
Clock"| n41 + d0 -->|"/clock
Clock"| n42 + d0 -->|"/clock
Clock"| n43 + d0 -->|"/clock
Clock"| n44 + d0 -->|"/clock
Clock"| n45 + d0 -->|"/clock
Clock"| n46 + d0 -->|"/clock
Clock"| n47 + d0 -->|"/clock
Clock"| n48 + d0 -->|"/clock
Clock"| n49 + d0 -->|"/clock
Clock"| n50 + d0 -->|"/clock
Clock"| n51 + d0 -->|"/clock
Clock"| n52 + d0 -->|"/clock
Clock"| n53 + d0 -->|"/clock
Clock"| n54 + d0 -->|"/clock
Clock"| n55 + d0 -->|"/clock
Clock"| n56 + d0 -->|"/clock
Clock"| n57 + d0 -->|"/clock
Clock"| n58 + d0 -->|"/clock
Clock"| n59 + d0 -->|"/clock
Clock"| n60 + d0 -->|"/clock
Clock"| n61 + d0 -->|"/clock
Clock"| n62 + d0 -->|"/clock
Clock"| n63 + d0 -->|"/clock
Clock"| n64 + d0 -->|"/clock
Clock"| n65 + d0 -->|"/clock
Clock"| n66 + d0 -->|"/clock
Clock"| n67 + d0 -->|"/clock
Clock"| n68 + d0 -->|"/clock
Clock"| n69 + d0 -->|"/clock
Clock"| n70 + d0 -->|"/clock
Clock"| n71 + d0 -->|"/clock
Clock"| n72 + d0 -->|"/clock
Clock"| n73 + d0 -->|"/clock
Clock"| n74 + d0 -->|"/clock
Clock"| n75 + d0 -->|"/clock
Clock"| n76 + d0 -->|"/clock
Clock"| n77 + d0 -->|"/clock
Clock"| n78 + d0 -->|"/clock
Clock"| n79 + d0 -->|"/clock
Clock"| n80 + d0 -->|"/clock
Clock"| n81 + d0 -->|"/clock
Clock"| n82 + d0 -->|"/clock
Clock"| n83 + n35 -->|"/diagnostics
DiagnosticArray"| d1(["/diagnostics (no subscribers)"]) + n37 -->|"/diagnostics
DiagnosticArray"| d1 + n6 -->|"/gossip/peers
PeerProfile"| n6 + n26 -->|"/move_base_simple/goal
PoseStamped"| d2(["/move_base_simple/goal (no subscribers)"]) + d3(["/robot_1/behavior/drone_safety_monitor/command (no publishers)"]) -->|"/robot_1/behavior/drone_safety_monitor/command
String"| n2 + n2 -->|"/robot_1/behavior/drone_safety_monitor/state_estimate_timed_out
Bool"| n78 + n71 -->|"/robot_1/control/reset_integrators
Empty"| n3 + n3 -->|"/robot_1/control/vx_pid_info
PIDInfo"| d4(["/robot_1/control/vx_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/vy_pid_info
PIDInfo"| d5(["/robot_1/control/vy_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/vz_pid_info
PIDInfo"| d6(["/robot_1/control/vz_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/x_pid_info
PIDInfo"| d7(["/robot_1/control/x_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/y_pid_info
PIDInfo"| d8(["/robot_1/control/y_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/z_pid_info
PIDInfo"| d9(["/robot_1/control/z_pid_info (no subscribers)"]) + n6 -->|"/robot_1/coordination/peer_registry
PeerProfile"| d10(["/robot_1/coordination/peer_registry (no subscribers)"]) + n70 -->|"/robot_1/cross_track_error
PoseStamped"| d11(["/robot_1/cross_track_error (no subscribers)"]) + d12(["/robot_1/droan/clear_map (no publishers)"]) -->|"/robot_1/droan/clear_map
Empty"| n5 + d13(["/robot_1/droan/custom_waypoint (no publishers)"]) -->|"/robot_1/droan/custom_waypoint
PoseStamped"| n5 + n5 -->|"/robot_1/droan/disparity_graph
MarkerArray"| n79 + n5 -->|"/robot_1/droan/disparity_map_debug
MarkerArray"| n79 + n4 -->|"/robot_1/droan/expanded_disparity_bg
Image"| n5 + n4 -->|"/robot_1/droan/expanded_disparity_fg
Image"| n5 + n4 -->|"/robot_1/droan/expansion_cloud
PointCloud2"| n79 + n4 -->|"/robot_1/droan/expansion_poly
MarkerArray"| n79 + d14(["/robot_1/droan/fg_bg_cloud (no publishers)"]) -->|"/robot_1/droan/fg_bg_cloud
PointCloud2"| n79 + n4 -->|"/robot_1/droan/frustum
Marker"| n79 + d15(["/robot_1/droan/graph_vis (no publishers)"]) -->|"/robot_1/droan/graph_vis
MarkerArray"| n79 + n5 -->|"/robot_1/droan/local_planner_global_plan_vis
MarkerArray"| n79 + n5 -->|"/robot_1/droan/map_clearing_point
PoseStamped"| d16(["/robot_1/droan/map_clearing_point (no subscribers)"]) + d17(["/robot_1/droan/none (no publishers)"]) -->|"/robot_1/droan/none
Image"| n4 + n5 -->|"/robot_1/droan/obstacle_vis
Range"| d18(["/robot_1/droan/obstacle_vis (no subscribers)"]) + d19(["/robot_1/droan/reset_stuck (no publishers)"]) -->|"/robot_1/droan/reset_stuck
Empty"| n5 + n5 -->|"/robot_1/droan/rewind_info
MarkerArray"| n79 + n5 -->|"/robot_1/droan/stuck
Bool"| d20(["/robot_1/droan/stuck (no subscribers)"]) + d21(["/robot_1/droan/traj_debug (no publishers)"]) -->|"/robot_1/droan/traj_debug
MarkerArray"| n79 + n5 -->|"/robot_1/droan/trajectory_library_vis
MarkerArray"| n79 + n5 -->|"/robot_1/droan/trajectory_override
TrajectoryXYZVYaw"| d22(["/robot_1/droan/trajectory_override (no subscribers)"]) + n5 -->|"/robot_1/droan/virtual_obstacles
MarkerArray"| n79 + d23(["/robot_1/droan/way_point (no publishers)"]) -->|"/robot_1/droan/way_point
PointStamped"| n5 + n70 -->|"/robot_1/global_plan
Path"| n5 + n70 -->|"/robot_1/global_plan
Path"| n6 + n70 -->|"/robot_1/global_plan
Path"| n79 + n75 -->|"/robot_1/global_plan
Path"| n5 + n75 -->|"/robot_1/global_plan
Path"| n6 + n75 -->|"/robot_1/global_plan
Path"| n79 + d24(["/robot_1/interface/attitude_thrust_command (no publishers)"]) -->|"/robot_1/interface/attitude_thrust_command
AttitudeThrust"| n71 + d25(["/robot_1/interface/cmd_attitude_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_attitude_thrust
AttitudeThrust"| n71 + n70 -->|"/robot_1/interface/cmd_pose
PoseStamped"| n71 + d26(["/robot_1/interface/cmd_rate_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_rate_thrust
RateThrust"| n71 + n3 -->|"/robot_1/interface/cmd_roll_pitch_yawrate_thrust
RollPitchYawrateThrust"| n71 + d27(["/robot_1/interface/cmd_torque_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_torque_thrust
TorqueThrust"| n71 + n70 -->|"/robot_1/interface/cmd_velocity
TwistStamped"| n71 + n71 -->|"/robot_1/interface/has_control
Bool"| n78 + n71 -->|"/robot_1/interface/is_armed
Bool"| n78 + d28(["/robot_1/interface/mavros/actuator_control (no publishers)"]) -->|"/robot_1/interface/mavros/actuator_control
ActuatorControl"| n7 + d29(["/robot_1/interface/mavros/adsb/send (no publishers)"]) -->|"/robot_1/interface/mavros/adsb/send
ADSBVehicle"| n8 + n8 -->|"/robot_1/interface/mavros/adsb/vehicle
ADSBVehicle"| d30(["/robot_1/interface/mavros/adsb/vehicle (no subscribers)"]) + n9 -->|"/robot_1/interface/mavros/altitude
Altitude"| d31(["/robot_1/interface/mavros/altitude (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/battery
BatteryState"| d32(["/robot_1/interface/mavros/battery (no subscribers)"]) + n10 -->|"/robot_1/interface/mavros/cam_imu_sync/cam_imu_stamp
CamIMUStamp"| d33(["/robot_1/interface/mavros/cam_imu_sync/cam_imu_stamp (no subscribers)"]) + n11 -->|"/robot_1/interface/mavros/camera/image_captured
CameraImageCaptured"| d34(["/robot_1/interface/mavros/camera/image_captured (no subscribers)"]) + d35(["/robot_1/interface/mavros/cellular_status/status (no publishers)"]) -->|"/robot_1/interface/mavros/cellular_status/status
CellularStatus"| n12 + d36(["/robot_1/interface/mavros/companion_process/status (no publishers)"]) -->|"/robot_1/interface/mavros/companion_process/status
CompanionProcessStatus"| n14 + n15 -->|"/robot_1/interface/mavros/debug_value/debug
DebugValue"| d37(["/robot_1/interface/mavros/debug_value/debug (no subscribers)"]) + n15 -->|"/robot_1/interface/mavros/debug_value/debug_float_array
DebugValue"| d38(["/robot_1/interface/mavros/debug_value/debug_float_array (no subscribers)"]) + n15 -->|"/robot_1/interface/mavros/debug_value/debug_vector
DebugValue"| d39(["/robot_1/interface/mavros/debug_value/debug_vector (no subscribers)"]) + n15 -->|"/robot_1/interface/mavros/debug_value/named_value_float
DebugValue"| d40(["/robot_1/interface/mavros/debug_value/named_value_float (no subscribers)"]) + n15 -->|"/robot_1/interface/mavros/debug_value/named_value_int
DebugValue"| d41(["/robot_1/interface/mavros/debug_value/named_value_int (no subscribers)"]) + d42(["/robot_1/interface/mavros/debug_value/send (no publishers)"]) -->|"/robot_1/interface/mavros/debug_value/send
DebugValue"| n15 + n16 -->|"/robot_1/interface/mavros/esc_status/info
ESCInfo"| d43(["/robot_1/interface/mavros/esc_status/info (no subscribers)"]) + n16 -->|"/robot_1/interface/mavros/esc_status/status
ESCStatus"| d44(["/robot_1/interface/mavros/esc_status/status (no subscribers)"]) + n17 -->|"/robot_1/interface/mavros/esc_telemetry/telemetry
ESCTelemetry"| d45(["/robot_1/interface/mavros/esc_telemetry/telemetry (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/estimator_status
EstimatorStatus"| d46(["/robot_1/interface/mavros/estimator_status (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/extended_state
ExtendedState"| n78 + d47(["/robot_1/interface/mavros/fake_gps/mocap/tf (no publishers)"]) -->|"/robot_1/interface/mavros/fake_gps/mocap/tf
TransformStamped"| n18 + n20 -->|"/robot_1/interface/mavros/geofence/fences
WaypointList"| d48(["/robot_1/interface/mavros/geofence/fences (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/gimbal_control/device/attitude_status
GimbalDeviceAttitudeStatus"| d49(["/robot_1/interface/mavros/gimbal_control/device/attitude_status (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/gimbal_control/device/info
GimbalDeviceInformation"| d50(["/robot_1/interface/mavros/gimbal_control/device/info (no subscribers)"]) + d51(["/robot_1/interface/mavros/gimbal_control/device/set_attitude (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/device/set_attitude
GimbalDeviceSetAttitude"| n21 + n21 -->|"/robot_1/interface/mavros/gimbal_control/manager/info
GimbalManagerInformation"| d52(["/robot_1/interface/mavros/gimbal_control/manager/info (no subscribers)"]) + d53(["/robot_1/interface/mavros/gimbal_control/manager/set_attitude (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_attitude
GimbalManagerSetAttitude"| n21 + d54(["/robot_1/interface/mavros/gimbal_control/manager/set_manual_control (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_manual_control
GimbalManagerSetPitchyaw"| n21 + d55(["/robot_1/interface/mavros/gimbal_control/manager/set_pitchyaw (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_pitchyaw
GimbalManagerSetPitchyaw"| n21 + n21 -->|"/robot_1/interface/mavros/gimbal_control/manager/status
GimbalManagerStatus"| d56(["/robot_1/interface/mavros/gimbal_control/manager/status (no subscribers)"]) + n22 -->|"/robot_1/interface/mavros/global_position/compass_hdg
Float64"| n6 + n22 -->|"/robot_1/interface/mavros/global_position/global
NavSatFix"| n55 + n22 -->|"/robot_1/interface/mavros/global_position/global
NavSatFix"| n71 + n22 -->|"/robot_1/interface/mavros/global_position/gp_lp_offset
PoseStamped"| d57(["/robot_1/interface/mavros/global_position/gp_lp_offset (no subscribers)"]) + n22 -->|"/robot_1/interface/mavros/global_position/gp_origin
GeoPointStamped"| n26 + n22 -->|"/robot_1/interface/mavros/global_position/local
Odometry"| n71 + n22 -->|"/robot_1/interface/mavros/global_position/raw/fix
NavSatFix"| n6 + n22 -->|"/robot_1/interface/mavros/global_position/raw/gps_vel
TwistStamped"| d58(["/robot_1/interface/mavros/global_position/raw/gps_vel (no subscribers)"]) + n22 -->|"/robot_1/interface/mavros/global_position/raw/satellites
UInt32"| d59(["/robot_1/interface/mavros/global_position/raw/satellites (no subscribers)"]) + n22 -->|"/robot_1/interface/mavros/global_position/rel_alt
Float64"| d60(["/robot_1/interface/mavros/global_position/rel_alt (no subscribers)"]) + d61(["/robot_1/interface/mavros/global_position/set_gp_origin (no publishers)"]) -->|"/robot_1/interface/mavros/global_position/set_gp_origin
GeoPointStamped"| n22 + d62(["/robot_1/interface/mavros/gps_input/gps_input (no publishers)"]) -->|"/robot_1/interface/mavros/gps_input/gps_input
GPSINPUT"| n23 + n24 -->|"/robot_1/interface/mavros/gps_rtk/rtk_baseline
RTKBaseline"| d63(["/robot_1/interface/mavros/gps_rtk/rtk_baseline (no subscribers)"]) + d64(["/robot_1/interface/mavros/gps_rtk/send_rtcm (no publishers)"]) -->|"/robot_1/interface/mavros/gps_rtk/send_rtcm
RTCM"| n24 + n25 -->|"/robot_1/interface/mavros/gpsstatus/gps1/raw
GPSRAW"| d65(["/robot_1/interface/mavros/gpsstatus/gps1/raw (no subscribers)"]) + n25 -->|"/robot_1/interface/mavros/gpsstatus/gps1/rtk
GPSRTK"| d66(["/robot_1/interface/mavros/gpsstatus/gps1/rtk (no subscribers)"]) + n25 -->|"/robot_1/interface/mavros/gpsstatus/gps2/raw
GPSRAW"| d67(["/robot_1/interface/mavros/gpsstatus/gps2/raw (no subscribers)"]) + n25 -->|"/robot_1/interface/mavros/gpsstatus/gps2/rtk
GPSRTK"| d68(["/robot_1/interface/mavros/gpsstatus/gps2/rtk (no subscribers)"]) + n27 -->|"/robot_1/interface/mavros/hil/actuator_controls
HilActuatorControls"| d69(["/robot_1/interface/mavros/hil/actuator_controls (no subscribers)"]) + n27 -->|"/robot_1/interface/mavros/hil/controls
HilControls"| d70(["/robot_1/interface/mavros/hil/controls (no subscribers)"]) + d71(["/robot_1/interface/mavros/hil/gps (no publishers)"]) -->|"/robot_1/interface/mavros/hil/gps
HilGPS"| n27 + d72(["/robot_1/interface/mavros/hil/imu_ned (no publishers)"]) -->|"/robot_1/interface/mavros/hil/imu_ned
HilSensor"| n27 + d73(["/robot_1/interface/mavros/hil/optical_flow (no publishers)"]) -->|"/robot_1/interface/mavros/hil/optical_flow
OpticalFlowRad"| n27 + d74(["/robot_1/interface/mavros/hil/rc_inputs (no publishers)"]) -->|"/robot_1/interface/mavros/hil/rc_inputs
RCIn"| n27 + d75(["/robot_1/interface/mavros/hil/state (no publishers)"]) -->|"/robot_1/interface/mavros/hil/state
HilStateQuaternion"| n27 + n28 -->|"/robot_1/interface/mavros/home_position/home
HomePosition"| n22 + n28 -->|"/robot_1/interface/mavros/home_position/home
HomePosition"| n71 + n71 -->|"/robot_1/interface/mavros/home_position/set
HomePosition"| n28 + n29 -->|"/robot_1/interface/mavros/imu/data
Imu"| d76(["/robot_1/interface/mavros/imu/data (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/imu/data_raw
Imu"| d77(["/robot_1/interface/mavros/imu/data_raw (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/imu/diff_pressure
FluidPressure"| d78(["/robot_1/interface/mavros/imu/diff_pressure (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/imu/mag
MagneticField"| d79(["/robot_1/interface/mavros/imu/mag (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/imu/static_pressure
FluidPressure"| d80(["/robot_1/interface/mavros/imu/static_pressure (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/imu/temperature_baro
Temperature"| d81(["/robot_1/interface/mavros/imu/temperature_baro (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/imu/temperature_imu
Temperature"| d82(["/robot_1/interface/mavros/imu/temperature_imu (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/landing_target/lt_marker
Vector3Stamped"| d83(["/robot_1/interface/mavros/landing_target/lt_marker (no subscribers)"]) + d84(["/robot_1/interface/mavros/landing_target/pose (no publishers)"]) -->|"/robot_1/interface/mavros/landing_target/pose
PoseStamped"| n30 + n30 -->|"/robot_1/interface/mavros/landing_target/pose_in
PoseStamped"| d85(["/robot_1/interface/mavros/landing_target/pose_in (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/local_position/accel
AccelWithCovarianceStamped"| d86(["/robot_1/interface/mavros/local_position/accel (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/local_position/odom
Odometry"| n72 + n31 -->|"/robot_1/interface/mavros/local_position/pose
PoseStamped"| n55 + n31 -->|"/robot_1/interface/mavros/local_position/pose_cov
PoseWithCovarianceStamped"| d87(["/robot_1/interface/mavros/local_position/pose_cov (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/local_position/velocity_body
TwistStamped"| d88(["/robot_1/interface/mavros/local_position/velocity_body (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/local_position/velocity_body_cov
TwistWithCovarianceStamped"| d89(["/robot_1/interface/mavros/local_position/velocity_body_cov (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/local_position/velocity_local
TwistStamped"| d90(["/robot_1/interface/mavros/local_position/velocity_local (no subscribers)"]) + n32 -->|"/robot_1/interface/mavros/log_transfer/raw/log_data
LogData"| d91(["/robot_1/interface/mavros/log_transfer/raw/log_data (no subscribers)"]) + n32 -->|"/robot_1/interface/mavros/log_transfer/raw/log_entry
LogEntry"| d92(["/robot_1/interface/mavros/log_transfer/raw/log_entry (no subscribers)"]) + n33 -->|"/robot_1/interface/mavros/mag_calibration/report
MagnetometerReporter"| d93(["/robot_1/interface/mavros/mag_calibration/report (no subscribers)"]) + n33 -->|"/robot_1/interface/mavros/mag_calibration/status
UInt8"| d94(["/robot_1/interface/mavros/mag_calibration/status (no subscribers)"]) + n34 -->|"/robot_1/interface/mavros/manual_control/control
ManualControl"| d95(["/robot_1/interface/mavros/manual_control/control (no subscribers)"]) + d96(["/robot_1/interface/mavros/manual_control/send (no publishers)"]) -->|"/robot_1/interface/mavros/manual_control/send
ManualControl"| n34 + n38 -->|"/robot_1/interface/mavros/mission/reached
WaypointReached"| d97(["/robot_1/interface/mavros/mission/reached (no subscribers)"]) + n38 -->|"/robot_1/interface/mavros/mission/waypoints
WaypointList"| d98(["/robot_1/interface/mavros/mission/waypoints (no subscribers)"]) + d99(["/robot_1/interface/mavros/mocap/pose (no publishers)"]) -->|"/robot_1/interface/mavros/mocap/pose
PoseStamped"| n39 + d100(["/robot_1/interface/mavros/mocap/tf (no publishers)"]) -->|"/robot_1/interface/mavros/mocap/tf
TransformStamped"| n39 + d101(["/robot_1/interface/mavros/mount_control/command (no publishers)"]) -->|"/robot_1/interface/mavros/mount_control/command
MountControl"| n40 + n40 -->|"/robot_1/interface/mavros/mount_control/orientation
Quaternion"| d102(["/robot_1/interface/mavros/mount_control/orientation (no subscribers)"]) + n40 -->|"/robot_1/interface/mavros/mount_control/status
Vector3Stamped"| d103(["/robot_1/interface/mavros/mount_control/status (no subscribers)"]) + n41 -->|"/robot_1/interface/mavros/nav_controller_output/output
NavControllerOutput"| d104(["/robot_1/interface/mavros/nav_controller_output/output (no subscribers)"]) + d105(["/robot_1/interface/mavros/obstacle/send (no publishers)"]) -->|"/robot_1/interface/mavros/obstacle/send
LaserScan"| n42 + d106(["/robot_1/interface/mavros/obstacle_distance_3d/send (no publishers)"]) -->|"/robot_1/interface/mavros/obstacle_distance_3d/send
ObstacleDistance3D"| n43 + n44 -->|"/robot_1/interface/mavros/odometry/in
Odometry"| d107(["/robot_1/interface/mavros/odometry/in (no subscribers)"]) + d108(["/robot_1/interface/mavros/odometry/out (no publishers)"]) -->|"/robot_1/interface/mavros/odometry/out
Odometry"| n44 + d109(["/robot_1/interface/mavros/onboard_computer/status (no publishers)"]) -->|"/robot_1/interface/mavros/onboard_computer/status
OnboardComputerStatus"| n45 + d110(["/robot_1/interface/mavros/open_drone_id/basic_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/basic_id
OpenDroneIDBasicID"| n46 + d111(["/robot_1/interface/mavros/open_drone_id/operator_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/operator_id
OpenDroneIDOperatorID"| n46 + d112(["/robot_1/interface/mavros/open_drone_id/self_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/self_id
OpenDroneIDSelfID"| n46 + d113(["/robot_1/interface/mavros/open_drone_id/system (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/system
OpenDroneIDSystem"| n46 + d114(["/robot_1/interface/mavros/open_drone_id/system_update (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/system_update
OpenDroneIDSystemUpdate"| n46 + n47 -->|"/robot_1/interface/mavros/optical_flow/ground_distance
Range"| d115(["/robot_1/interface/mavros/optical_flow/ground_distance (no subscribers)"]) + n47 -->|"/robot_1/interface/mavros/optical_flow/raw/optical_flow
OpticalFlow"| d116(["/robot_1/interface/mavros/optical_flow/raw/optical_flow (no subscribers)"]) + d117(["/robot_1/interface/mavros/optical_flow/raw/send (no publishers)"]) -->|"/robot_1/interface/mavros/optical_flow/raw/send
OpticalFlow"| n47 + n48 -->|"/robot_1/interface/mavros/param/event
ParamEvent"| d118(["/robot_1/interface/mavros/param/event (no subscribers)"]) + d119(["/robot_1/interface/mavros/play_tune (no publishers)"]) -->|"/robot_1/interface/mavros/play_tune
PlayTuneV2"| n49 + n50 -->|"/robot_1/interface/mavros/px4flow/ground_distance
Range"| d120(["/robot_1/interface/mavros/px4flow/ground_distance (no subscribers)"]) + n50 -->|"/robot_1/interface/mavros/px4flow/raw/optical_flow_rad
OpticalFlowRad"| d121(["/robot_1/interface/mavros/px4flow/raw/optical_flow_rad (no subscribers)"]) + d122(["/robot_1/interface/mavros/px4flow/raw/send (no publishers)"]) -->|"/robot_1/interface/mavros/px4flow/raw/send
OpticalFlowRad"| n50 + n50 -->|"/robot_1/interface/mavros/px4flow/temperature
Temperature"| d123(["/robot_1/interface/mavros/px4flow/temperature (no subscribers)"]) + n61 -->|"/robot_1/interface/mavros/radio_status
RadioStatus"| d124(["/robot_1/interface/mavros/radio_status (no subscribers)"]) + n51 -->|"/robot_1/interface/mavros/rallypoint/rallypoints
WaypointList"| d125(["/robot_1/interface/mavros/rallypoint/rallypoints (no subscribers)"]) + n52 -->|"/robot_1/interface/mavros/rc/in
RCIn"| d126(["/robot_1/interface/mavros/rc/in (no subscribers)"]) + n52 -->|"/robot_1/interface/mavros/rc/out
RCOut"| d127(["/robot_1/interface/mavros/rc/out (no subscribers)"]) + d128(["/robot_1/interface/mavros/rc/override (no publishers)"]) -->|"/robot_1/interface/mavros/rc/override
OverrideRCIn"| n52 + d129(["/robot_1/interface/mavros/setpoint_accel/accel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_accel/accel
Vector3Stamped"| n53 + d130(["/robot_1/interface/mavros/setpoint_attitude/cmd_vel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_attitude/cmd_vel
TwistStamped"| n54 + d131(["/robot_1/interface/mavros/setpoint_attitude/thrust (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_attitude/thrust
Thrust"| n54 + n71 -->|"/robot_1/interface/mavros/setpoint_position/global
GeoPoseStamped"| n55 + d132(["/robot_1/interface/mavros/setpoint_position/global_to_local (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_position/global_to_local
GeoPoseStamped"| n55 + n71 -->|"/robot_1/interface/mavros/setpoint_position/local
PoseStamped"| n55 + n71 -->|"/robot_1/interface/mavros/setpoint_raw/attitude
AttitudeTarget"| n56 + d133(["/robot_1/interface/mavros/setpoint_raw/global (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_raw/global
GlobalPositionTarget"| n56 + n71 -->|"/robot_1/interface/mavros/setpoint_raw/local
PositionTarget"| n56 + n56 -->|"/robot_1/interface/mavros/setpoint_raw/target_attitude
AttitudeTarget"| d134(["/robot_1/interface/mavros/setpoint_raw/target_attitude (no subscribers)"]) + n56 -->|"/robot_1/interface/mavros/setpoint_raw/target_global
GlobalPositionTarget"| d135(["/robot_1/interface/mavros/setpoint_raw/target_global (no subscribers)"]) + n56 -->|"/robot_1/interface/mavros/setpoint_raw/target_local
PositionTarget"| d136(["/robot_1/interface/mavros/setpoint_raw/target_local (no subscribers)"]) + n57 -->|"/robot_1/interface/mavros/setpoint_trajectory/desired
Path"| d137(["/robot_1/interface/mavros/setpoint_trajectory/desired (no subscribers)"]) + d138(["/robot_1/interface/mavros/setpoint_trajectory/local (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_trajectory/local
MultiDOFJointTrajectory"| n57 + d139(["/robot_1/interface/mavros/setpoint_velocity/cmd_vel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_velocity/cmd_vel
TwistStamped"| n58 + d140(["/robot_1/interface/mavros/setpoint_velocity/cmd_vel_unstamped (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_velocity/cmd_vel_unstamped
Twist"| n58 + n59 -->|"/robot_1/interface/mavros/sim_state/acceleration
Vector3Stamped"| d141(["/robot_1/interface/mavros/sim_state/acceleration (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/sim_state/attitude
Imu"| d142(["/robot_1/interface/mavros/sim_state/attitude (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/sim_state/global_position
NavSatFix"| d143(["/robot_1/interface/mavros/sim_state/global_position (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/sim_state/velocity_body
TwistStamped"| d144(["/robot_1/interface/mavros/sim_state/velocity_body (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/sim_state/velocity_local
TwistStamped"| d145(["/robot_1/interface/mavros/sim_state/velocity_local (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/state
State"| n71 + n60 -->|"/robot_1/interface/mavros/status_event
StatusEvent"| d146(["/robot_1/interface/mavros/status_event (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/statustext/recv
StatusText"| d147(["/robot_1/interface/mavros/statustext/recv (no subscribers)"]) + d148(["/robot_1/interface/mavros/statustext/send (no publishers)"]) -->|"/robot_1/interface/mavros/statustext/send
StatusText"| n60 + n60 -->|"/robot_1/interface/mavros/sys_status
SysStatus"| d149(["/robot_1/interface/mavros/sys_status (no subscribers)"]) + n7 -->|"/robot_1/interface/mavros/target_actuator_control
ActuatorControl"| d150(["/robot_1/interface/mavros/target_actuator_control (no subscribers)"]) + n62 -->|"/robot_1/interface/mavros/terrain/report
TerrainReport"| d151(["/robot_1/interface/mavros/terrain/report (no subscribers)"]) + n63 -->|"/robot_1/interface/mavros/time_reference
TimeReference"| d152(["/robot_1/interface/mavros/time_reference (no subscribers)"]) + n63 -->|"/robot_1/interface/mavros/timesync_status
TimesyncStatus"| d153(["/robot_1/interface/mavros/timesync_status (no subscribers)"]) + n64 -->|"/robot_1/interface/mavros/trajectory/desired
Trajectory"| d154(["/robot_1/interface/mavros/trajectory/desired (no subscribers)"]) + d155(["/robot_1/interface/mavros/trajectory/generated (no publishers)"]) -->|"/robot_1/interface/mavros/trajectory/generated
Trajectory"| n64 + d156(["/robot_1/interface/mavros/trajectory/path (no publishers)"]) -->|"/robot_1/interface/mavros/trajectory/path
Path"| n64 + d157(["/robot_1/interface/mavros/tunnel/in (no publishers)"]) -->|"/robot_1/interface/mavros/tunnel/in
Tunnel"| n65 + n65 -->|"/robot_1/interface/mavros/tunnel/out
Tunnel"| d158(["/robot_1/interface/mavros/tunnel/out (no subscribers)"]) + n66 -->|"/robot_1/interface/mavros/vfr_hud
VfrHud"| d159(["/robot_1/interface/mavros/vfr_hud (no subscribers)"]) + d160(["/robot_1/interface/mavros/vision_pose/pose (no publishers)"]) -->|"/robot_1/interface/mavros/vision_pose/pose
PoseStamped"| n67 + d161(["/robot_1/interface/mavros/vision_pose/pose_cov (no publishers)"]) -->|"/robot_1/interface/mavros/vision_pose/pose_cov
PoseWithCovarianceStamped"| n67 + d162(["/robot_1/interface/mavros/vision_speed/speed_twist (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_twist
TwistStamped"| n68 + d163(["/robot_1/interface/mavros/vision_speed/speed_twist_cov (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_twist_cov
TwistWithCovarianceStamped"| n68 + d164(["/robot_1/interface/mavros/vision_speed/speed_vector (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_vector
Vector3Stamped"| n68 + n69 -->|"/robot_1/interface/mavros/wind_estimation
TwistWithCovarianceStamped"| d165(["/robot_1/interface/mavros/wind_estimation (no subscribers)"]) + d166(["/robot_1/interface/pose_command (no publishers)"]) -->|"/robot_1/interface/pose_command
PoseStamped"| n71 + d167(["/robot_1/interface/rate_thrust_command (no publishers)"]) -->|"/robot_1/interface/rate_thrust_command
RateThrust"| n71 + d168(["/robot_1/interface/roll_pitch_yawrate_thrust_command (no publishers)"]) -->|"/robot_1/interface/roll_pitch_yawrate_thrust_command
RollPitchYawrateThrust"| n71 + d169(["/robot_1/interface/torque_thrust_command (no publishers)"]) -->|"/robot_1/interface/torque_thrust_command
TorqueThrust"| n71 + d170(["/robot_1/interface/velocity_command (no publishers)"]) -->|"/robot_1/interface/velocity_command
TwistStamped"| n71 + d171(["/robot_1/joint_states (no publishers)"]) -->|"/robot_1/joint_states
JointState"| n76 + d172(["/robot_1/macvo/odometry (no publishers)"]) -->|"/robot_1/macvo/odometry
Odometry"| n79 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n2 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n3 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n70 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n75 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n78 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n79 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n80 + n72 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n81 + d173(["/robot_1/perception/macvo/disparity (no publishers)"]) -->|"/robot_1/perception/macvo/disparity
Image"| n79 + d174(["/robot_1/perception/macvo/point_cloud (no publishers)"]) -->|"/robot_1/perception/macvo/point_cloud
PointCloud"| n79 + n73 -->|"/robot_1/perception/stereo_image_proc/disparity
DisparityImage"| n4 + n73 -->|"/robot_1/perception/stereo_image_proc/disparity
DisparityImage"| n74 + n74 -->|"/robot_1/perception/stereo_image_proc/point_cloud
PointCloud2"| n79 + n75 -->|"/robot_1/random_walk_node/goal_point_viz
Marker"| d175(["/robot_1/random_walk_node/goal_point_viz (no subscribers)"]) + n75 -->|"/robot_1/random_walk_node/traj_viz
Marker"| d176(["/robot_1/random_walk_node/traj_viz (no subscribers)"]) + n76 -->|"/robot_1/robot_description
String"| d177(["/robot_1/robot_description (no subscribers)"]) + d178(["/robot_1/sensors/front_stereo/left/camera_info (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n73 + d178 -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n74 + d178 -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n79 + d179(["/robot_1/sensors/front_stereo/left/depth_ground_truth (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/depth_ground_truth
Image"| n79 + d180(["/robot_1/sensors/front_stereo/left/image_rect (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n73 + d180 -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n74 + d180 -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n79 + d181(["/robot_1/sensors/front_stereo/right/camera_info (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n4 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n5 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n73 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n74 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n79 + d182(["/robot_1/sensors/front_stereo/right/depth_ground_truth (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/depth_ground_truth
Image"| n79 + d183(["/robot_1/sensors/front_stereo/right/image_rect (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/image_rect
Image"| n73 + d183 -->|"/robot_1/sensors/front_stereo/right/image_rect
Image"| n79 + d184(["/robot_1/sensors/lidar/point_cloud (no publishers)"]) -->|"/robot_1/sensors/lidar/point_cloud
PointCloud2"| n79 + n77 -->|"/robot_1/sensors/ouster/point_cloud
PointCloud2"| n82 + d185(["/robot_1/sensors/ouster/point_cloud_raw (no publishers)"]) -->|"/robot_1/sensors/ouster/point_cloud_raw
PointCloud2"| n77 + n78 -->|"/robot_1/takeoff_landing_planner/is_airborne
Bool"| d186(["/robot_1/takeoff_landing_planner/is_airborne (no subscribers)"]) + d187(["/robot_1/takeoff_landing_planner/trajectory_completion_percentage (no publishers)"]) -->|"/robot_1/takeoff_landing_planner/trajectory_completion_percentage
Float32"| n78 + n81 -->|"/robot_1/trajectory_controller/closest_point
Odometry"| d188(["/robot_1/trajectory_controller/closest_point (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/look_ahead
Odometry"| n5 + n81 -->|"/robot_1/trajectory_controller/projected_drone_pose
PoseStamped"| n70 + n81 -->|"/robot_1/trajectory_controller/tracking_error
Float32"| d189(["/robot_1/trajectory_controller/tracking_error (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n3 + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n5 + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n70 + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n78 + n81 -->|"/robot_1/trajectory_controller/tracking_point_velocity_magnitude
Float32"| d190(["/robot_1/trajectory_controller/tracking_point_velocity_magnitude (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/traj_drone_point
Odometry"| d191(["/robot_1/trajectory_controller/traj_drone_point (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/trajectory_completion_percentage
Float32"| n80 + n81 -->|"/robot_1/trajectory_controller/trajectory_controller_debug_markers
MarkerArray"| n79 + n78 -->|"/robot_1/trajectory_controller/trajectory_override
TrajectoryXYZVYaw"| n81 + n80 -->|"/robot_1/trajectory_controller/trajectory_override
TrajectoryXYZVYaw"| n81 + n5 -->|"/robot_1/trajectory_controller/trajectory_segment_to_add
TrajectoryXYZVYaw"| n81 + n81 -->|"/robot_1/trajectory_controller/trajectory_time
Float32"| d192(["/robot_1/trajectory_controller/trajectory_time (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/trajectory_vis
MarkerArray"| n79 + n81 -->|"/robot_1/trajectory_controller/virtual_tracking_point
Odometry"| d193(["/robot_1/trajectory_controller/virtual_tracking_point (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_overwrites
UpdateGrid"| d194(["/robot_1/vdb_mapping/vdb_map_overwrites (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_pointcloud
PointCloud2"| d195(["/robot_1/vdb_mapping/vdb_map_pointcloud (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_sections
UpdateGrid"| d196(["/robot_1/vdb_mapping/vdb_map_sections (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_updates
UpdateGrid"| d197(["/robot_1/vdb_mapping/vdb_map_updates (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_visualization
Marker"| n75 + n82 -->|"/robot_1/vdb_mapping/vdb_map_visualization
Marker"| n79 + n35 -->|"/tf
TFMessage"| n70 + n35 -->|"/tf
TFMessage"| n79 + n72 -->|"/tf
TFMessage"| n70 + n72 -->|"/tf
TFMessage"| n79 + n76 -->|"/tf
TFMessage"| n70 + n76 -->|"/tf
TFMessage"| n79 + n81 -->|"/tf
TFMessage"| n70 + n81 -->|"/tf
TFMessage"| n79 + n35 -->|"/tf_static
TFMessage"| n70 + n35 -->|"/tf_static
TFMessage"| n79 + n76 -->|"/tf_static
TFMessage"| n70 + n76 -->|"/tf_static
TFMessage"| n79 + n83 -->|"/tf_static
TFMessage"| n70 + n83 -->|"/tf_static
TFMessage"| n79 + n35 -->|"/uas2/mavlink_sink
Mavlink"| n37 + n37 -->|"/uas2/mavlink_source
Mavlink"| n35 +``` + + diff --git a/stacks/full_macvo/wiring.md b/stacks/full_macvo/wiring.md new file mode 100644 index 000000000..ffcc15e8a --- /dev/null +++ b/stacks/full_macvo/wiring.md @@ -0,0 +1,7662 @@ +# Wiring snapshot: full_macvo + +- **generated-by**: tests/system/test_wiring_snapshot.py +- **date**: 2026-08-20 22:17:32 +- **sim**: isaacsim +- **num_robots**: 1 +- **source-sha**: d02a3467b72d + +```mermaid +graph LR + subgraph g0["behavior"] + n2["/robot_1/behavior/drone_safety_monitor/drone_safety_monitor"] + end + subgraph g1["control"] + n3["/robot_1/control/pid_controller"] + end + subgraph g2["droan"] + n4["/robot_1/droan/disparity_expander_node"] + end + subgraph g3["interface"] + n6["/robot_1/interface/mavros/actuator_control"] + n7["/robot_1/interface/mavros/adsb"] + n8["/robot_1/interface/mavros/altitude"] + n9["/robot_1/interface/mavros/cam_imu_sync"] + n10["/robot_1/interface/mavros/camera"] + n11["/robot_1/interface/mavros/cellular_status"] + n12["/robot_1/interface/mavros/cmd"] + n13["/robot_1/interface/mavros/companion_process"] + n14["/robot_1/interface/mavros/debug_value"] + n15["/robot_1/interface/mavros/esc_status"] + n16["/robot_1/interface/mavros/esc_telemetry"] + n17["/robot_1/interface/mavros/fake_gps"] + n18["/robot_1/interface/mavros/ftp"] + n19["/robot_1/interface/mavros/geofence"] + n20["/robot_1/interface/mavros/gimbal_control"] + n21["/robot_1/interface/mavros/global_position"] + n22["/robot_1/interface/mavros/gps_input"] + n23["/robot_1/interface/mavros/gps_rtk"] + n24["/robot_1/interface/mavros/gpsstatus"] + n25["/robot_1/interface/mavros/guided_target"] + n26["/robot_1/interface/mavros/hil"] + n27["/robot_1/interface/mavros/home_position"] + n28["/robot_1/interface/mavros/imu"] + n29["/robot_1/interface/mavros/landing_target"] + n30["/robot_1/interface/mavros/local_position"] + n31["/robot_1/interface/mavros/log_transfer"] + n32["/robot_1/interface/mavros/mag_calibration"] + n33["/robot_1/interface/mavros/manual_control"] + n34["/robot_1/interface/mavros/mavros"] + n35["/robot_1/interface/mavros/mavros_node"] + n36["/robot_1/interface/mavros/mavros_router"] + n37["/robot_1/interface/mavros/mission"] + n38["/robot_1/interface/mavros/mocap"] + n39["/robot_1/interface/mavros/mount_control"] + n40["/robot_1/interface/mavros/nav_controller_output"] + n41["/robot_1/interface/mavros/obstacle"] + n42["/robot_1/interface/mavros/obstacle_distance_3d"] + n43["/robot_1/interface/mavros/odometry"] + n44["/robot_1/interface/mavros/onboard_computer"] + n45["/robot_1/interface/mavros/open_drone_id"] + n46["/robot_1/interface/mavros/optical_flow"] + n47["/robot_1/interface/mavros/param"] + n48["/robot_1/interface/mavros/play_tune"] + n49["/robot_1/interface/mavros/px4flow"] + n50["/robot_1/interface/mavros/rallypoint"] + n51["/robot_1/interface/mavros/rc"] + n52["/robot_1/interface/mavros/setpoint_accel"] + n53["/robot_1/interface/mavros/setpoint_attitude"] + n54["/robot_1/interface/mavros/setpoint_position"] + n55["/robot_1/interface/mavros/setpoint_raw"] + n56["/robot_1/interface/mavros/setpoint_trajectory"] + n57["/robot_1/interface/mavros/setpoint_velocity"] + n58["/robot_1/interface/mavros/sim_state"] + n59["/robot_1/interface/mavros/sys"] + n60["/robot_1/interface/mavros/tdr_radio"] + n61["/robot_1/interface/mavros/terrain"] + n62["/robot_1/interface/mavros/time"] + n63["/robot_1/interface/mavros/trajectory"] + n64["/robot_1/interface/mavros/tunnel"] + n65["/robot_1/interface/mavros/vfr_hud"] + n66["/robot_1/interface/mavros/vision_pose"] + n67["/robot_1/interface/mavros/vision_speed"] + n68["/robot_1/interface/mavros/wind"] + n69["/robot_1/interface/odom_modifier"] + n70["/robot_1/interface/robot_interface"] + end + subgraph g4["odometry_conversion"] + n71["/robot_1/odometry_conversion/odometry_conversion"] + end + subgraph g5["perception"] + n72["/robot_1/perception/static_transform_publisher"] + n73["/robot_1/perception/stereo_image_proc/disparity_node"] + n74["/robot_1/perception/stereo_pointcloud"] + end + subgraph g6["robot_1"] + n1["/robot_1/Container"] + n5["/robot_1/gossip_node"] + n75["/robot_1/random_walk_node"] + n76["/robot_1/robot_state_publisher"] + n79["/robot_1/topic_keepalive"] + n82["/robot_1/vdb_mapping"] + n83["/robot_1/world_to_map_broadcaster"] + end + subgraph g7["root"] + n0["/action_relay_client"] + end + subgraph g8["sensors"] + n77["/robot_1/sensors/lidar_point_cloud_filter"] + end + subgraph g9["takeoff_landing_planner"] + n78["/robot_1/takeoff_landing_planner/takeoff_landing_task"] + end + subgraph g10["trajectory_controller"] + n80["/robot_1/trajectory_controller/fixed_trajectory_task"] + n81["/robot_1/trajectory_controller/trajectory_control_node"] + end + d0(["/clock (no publishers)"]) -->|"/clock
Clock"| n1 + d0 -->|"/clock
Clock"| n2 + d0 -->|"/clock
Clock"| n3 + d0 -->|"/clock
Clock"| n4 + d0 -->|"/clock
Clock"| n5 + d0 -->|"/clock
Clock"| n6 + d0 -->|"/clock
Clock"| n7 + d0 -->|"/clock
Clock"| n8 + d0 -->|"/clock
Clock"| n9 + d0 -->|"/clock
Clock"| n10 + d0 -->|"/clock
Clock"| n11 + d0 -->|"/clock
Clock"| n12 + d0 -->|"/clock
Clock"| n13 + d0 -->|"/clock
Clock"| n14 + d0 -->|"/clock
Clock"| n15 + d0 -->|"/clock
Clock"| n16 + d0 -->|"/clock
Clock"| n17 + d0 -->|"/clock
Clock"| n18 + d0 -->|"/clock
Clock"| n19 + d0 -->|"/clock
Clock"| n20 + d0 -->|"/clock
Clock"| n21 + d0 -->|"/clock
Clock"| n22 + d0 -->|"/clock
Clock"| n23 + d0 -->|"/clock
Clock"| n24 + d0 -->|"/clock
Clock"| n25 + d0 -->|"/clock
Clock"| n26 + d0 -->|"/clock
Clock"| n27 + d0 -->|"/clock
Clock"| n28 + d0 -->|"/clock
Clock"| n29 + d0 -->|"/clock
Clock"| n30 + d0 -->|"/clock
Clock"| n31 + d0 -->|"/clock
Clock"| n32 + d0 -->|"/clock
Clock"| n33 + d0 -->|"/clock
Clock"| n34 + d0 -->|"/clock
Clock"| n35 + d0 -->|"/clock
Clock"| n36 + d0 -->|"/clock
Clock"| n37 + d0 -->|"/clock
Clock"| n38 + d0 -->|"/clock
Clock"| n39 + d0 -->|"/clock
Clock"| n40 + d0 -->|"/clock
Clock"| n41 + d0 -->|"/clock
Clock"| n42 + d0 -->|"/clock
Clock"| n43 + d0 -->|"/clock
Clock"| n44 + d0 -->|"/clock
Clock"| n45 + d0 -->|"/clock
Clock"| n46 + d0 -->|"/clock
Clock"| n47 + d0 -->|"/clock
Clock"| n48 + d0 -->|"/clock
Clock"| n49 + d0 -->|"/clock
Clock"| n50 + d0 -->|"/clock
Clock"| n51 + d0 -->|"/clock
Clock"| n52 + d0 -->|"/clock
Clock"| n53 + d0 -->|"/clock
Clock"| n54 + d0 -->|"/clock
Clock"| n55 + d0 -->|"/clock
Clock"| n56 + d0 -->|"/clock
Clock"| n57 + d0 -->|"/clock
Clock"| n58 + d0 -->|"/clock
Clock"| n59 + d0 -->|"/clock
Clock"| n60 + d0 -->|"/clock
Clock"| n61 + d0 -->|"/clock
Clock"| n62 + d0 -->|"/clock
Clock"| n63 + d0 -->|"/clock
Clock"| n64 + d0 -->|"/clock
Clock"| n65 + d0 -->|"/clock
Clock"| n66 + d0 -->|"/clock
Clock"| n67 + d0 -->|"/clock
Clock"| n68 + d0 -->|"/clock
Clock"| n69 + d0 -->|"/clock
Clock"| n70 + d0 -->|"/clock
Clock"| n71 + d0 -->|"/clock
Clock"| n72 + d0 -->|"/clock
Clock"| n73 + d0 -->|"/clock
Clock"| n74 + d0 -->|"/clock
Clock"| n75 + d0 -->|"/clock
Clock"| n76 + d0 -->|"/clock
Clock"| n77 + d0 -->|"/clock
Clock"| n78 + d0 -->|"/clock
Clock"| n79 + d0 -->|"/clock
Clock"| n80 + d0 -->|"/clock
Clock"| n81 + d0 -->|"/clock
Clock"| n82 + d0 -->|"/clock
Clock"| n83 + n34 -->|"/diagnostics
DiagnosticArray"| d1(["/diagnostics (no subscribers)"]) + n36 -->|"/diagnostics
DiagnosticArray"| d1 + n5 -->|"/gossip/peers
PeerProfile"| n5 + n25 -->|"/move_base_simple/goal
PoseStamped"| d2(["/move_base_simple/goal (no subscribers)"]) + d3(["/robot_1/behavior/drone_safety_monitor/command (no publishers)"]) -->|"/robot_1/behavior/drone_safety_monitor/command
String"| n2 + n2 -->|"/robot_1/behavior/drone_safety_monitor/state_estimate_timed_out
Bool"| n78 + n70 -->|"/robot_1/control/reset_integrators
Empty"| n3 + n3 -->|"/robot_1/control/vx_pid_info
PIDInfo"| d4(["/robot_1/control/vx_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/vy_pid_info
PIDInfo"| d5(["/robot_1/control/vy_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/vz_pid_info
PIDInfo"| d6(["/robot_1/control/vz_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/x_pid_info
PIDInfo"| d7(["/robot_1/control/x_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/y_pid_info
PIDInfo"| d8(["/robot_1/control/y_pid_info (no subscribers)"]) + n3 -->|"/robot_1/control/z_pid_info
PIDInfo"| d9(["/robot_1/control/z_pid_info (no subscribers)"]) + n5 -->|"/robot_1/coordination/peer_registry
PeerProfile"| d10(["/robot_1/coordination/peer_registry (no subscribers)"]) + n69 -->|"/robot_1/cross_track_error
PoseStamped"| d11(["/robot_1/cross_track_error (no subscribers)"]) + n4 -->|"/robot_1/droan/background_expanded
Image"| d12(["/robot_1/droan/background_expanded (no subscribers)"]) + d13(["/robot_1/droan/clear_map (no publishers)"]) -->|"/robot_1/droan/clear_map
Empty"| n4 + d14(["/robot_1/droan/disparity_graph (no publishers)"]) -->|"/robot_1/droan/disparity_graph
MarkerArray"| n79 + d15(["/robot_1/droan/disparity_map_debug (no publishers)"]) -->|"/robot_1/droan/disparity_map_debug
MarkerArray"| n79 + d16(["/robot_1/droan/expansion_cloud (no publishers)"]) -->|"/robot_1/droan/expansion_cloud
PointCloud2"| n79 + d17(["/robot_1/droan/expansion_poly (no publishers)"]) -->|"/robot_1/droan/expansion_poly
MarkerArray"| n79 + n4 -->|"/robot_1/droan/fg_bg_cloud
PointCloud2"| n79 + n4 -->|"/robot_1/droan/foreground_expanded
Image"| d18(["/robot_1/droan/foreground_expanded (no subscribers)"]) + d19(["/robot_1/droan/frustum (no publishers)"]) -->|"/robot_1/droan/frustum
Marker"| n79 + n4 -->|"/robot_1/droan/graph_vis
MarkerArray"| n79 + n4 -->|"/robot_1/droan/local_planner_global_plan_vis
MarkerArray"| n79 + d20(["/robot_1/droan/reset_stuck (no publishers)"]) -->|"/robot_1/droan/reset_stuck
Empty"| n4 + n4 -->|"/robot_1/droan/rewind_info
MarkerArray"| n79 + n4 -->|"/robot_1/droan/stuck
Bool"| d21(["/robot_1/droan/stuck (no subscribers)"]) + n4 -->|"/robot_1/droan/traj_debug
MarkerArray"| n79 + d22(["/robot_1/droan/trajectory_library_vis (no publishers)"]) -->|"/robot_1/droan/trajectory_library_vis
MarkerArray"| n79 + d23(["/robot_1/droan/virtual_obstacles (no publishers)"]) -->|"/robot_1/droan/virtual_obstacles
MarkerArray"| n79 + n69 -->|"/robot_1/global_plan
Path"| n4 + n69 -->|"/robot_1/global_plan
Path"| n5 + n69 -->|"/robot_1/global_plan
Path"| n79 + n75 -->|"/robot_1/global_plan
Path"| n4 + n75 -->|"/robot_1/global_plan
Path"| n5 + n75 -->|"/robot_1/global_plan
Path"| n79 + d24(["/robot_1/interface/attitude_thrust_command (no publishers)"]) -->|"/robot_1/interface/attitude_thrust_command
AttitudeThrust"| n70 + d25(["/robot_1/interface/cmd_attitude_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_attitude_thrust
AttitudeThrust"| n70 + n69 -->|"/robot_1/interface/cmd_pose
PoseStamped"| n70 + d26(["/robot_1/interface/cmd_rate_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_rate_thrust
RateThrust"| n70 + n3 -->|"/robot_1/interface/cmd_roll_pitch_yawrate_thrust
RollPitchYawrateThrust"| n70 + d27(["/robot_1/interface/cmd_torque_thrust (no publishers)"]) -->|"/robot_1/interface/cmd_torque_thrust
TorqueThrust"| n70 + n69 -->|"/robot_1/interface/cmd_velocity
TwistStamped"| n70 + n70 -->|"/robot_1/interface/has_control
Bool"| n78 + n70 -->|"/robot_1/interface/is_armed
Bool"| n78 + d28(["/robot_1/interface/mavros/actuator_control (no publishers)"]) -->|"/robot_1/interface/mavros/actuator_control
ActuatorControl"| n6 + d29(["/robot_1/interface/mavros/adsb/send (no publishers)"]) -->|"/robot_1/interface/mavros/adsb/send
ADSBVehicle"| n7 + n7 -->|"/robot_1/interface/mavros/adsb/vehicle
ADSBVehicle"| d30(["/robot_1/interface/mavros/adsb/vehicle (no subscribers)"]) + n8 -->|"/robot_1/interface/mavros/altitude
Altitude"| d31(["/robot_1/interface/mavros/altitude (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/battery
BatteryState"| d32(["/robot_1/interface/mavros/battery (no subscribers)"]) + n9 -->|"/robot_1/interface/mavros/cam_imu_sync/cam_imu_stamp
CamIMUStamp"| d33(["/robot_1/interface/mavros/cam_imu_sync/cam_imu_stamp (no subscribers)"]) + n10 -->|"/robot_1/interface/mavros/camera/image_captured
CameraImageCaptured"| d34(["/robot_1/interface/mavros/camera/image_captured (no subscribers)"]) + d35(["/robot_1/interface/mavros/cellular_status/status (no publishers)"]) -->|"/robot_1/interface/mavros/cellular_status/status
CellularStatus"| n11 + d36(["/robot_1/interface/mavros/companion_process/status (no publishers)"]) -->|"/robot_1/interface/mavros/companion_process/status
CompanionProcessStatus"| n13 + n14 -->|"/robot_1/interface/mavros/debug_value/debug
DebugValue"| d37(["/robot_1/interface/mavros/debug_value/debug (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/debug_float_array
DebugValue"| d38(["/robot_1/interface/mavros/debug_value/debug_float_array (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/debug_vector
DebugValue"| d39(["/robot_1/interface/mavros/debug_value/debug_vector (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/named_value_float
DebugValue"| d40(["/robot_1/interface/mavros/debug_value/named_value_float (no subscribers)"]) + n14 -->|"/robot_1/interface/mavros/debug_value/named_value_int
DebugValue"| d41(["/robot_1/interface/mavros/debug_value/named_value_int (no subscribers)"]) + d42(["/robot_1/interface/mavros/debug_value/send (no publishers)"]) -->|"/robot_1/interface/mavros/debug_value/send
DebugValue"| n14 + n15 -->|"/robot_1/interface/mavros/esc_status/info
ESCInfo"| d43(["/robot_1/interface/mavros/esc_status/info (no subscribers)"]) + n15 -->|"/robot_1/interface/mavros/esc_status/status
ESCStatus"| d44(["/robot_1/interface/mavros/esc_status/status (no subscribers)"]) + n16 -->|"/robot_1/interface/mavros/esc_telemetry/telemetry
ESCTelemetry"| d45(["/robot_1/interface/mavros/esc_telemetry/telemetry (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/estimator_status
EstimatorStatus"| d46(["/robot_1/interface/mavros/estimator_status (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/extended_state
ExtendedState"| n78 + d47(["/robot_1/interface/mavros/fake_gps/mocap/tf (no publishers)"]) -->|"/robot_1/interface/mavros/fake_gps/mocap/tf
TransformStamped"| n17 + n19 -->|"/robot_1/interface/mavros/geofence/fences
WaypointList"| d48(["/robot_1/interface/mavros/geofence/fences (no subscribers)"]) + n20 -->|"/robot_1/interface/mavros/gimbal_control/device/attitude_status
GimbalDeviceAttitudeStatus"| d49(["/robot_1/interface/mavros/gimbal_control/device/attitude_status (no subscribers)"]) + n20 -->|"/robot_1/interface/mavros/gimbal_control/device/info
GimbalDeviceInformation"| d50(["/robot_1/interface/mavros/gimbal_control/device/info (no subscribers)"]) + d51(["/robot_1/interface/mavros/gimbal_control/device/set_attitude (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/device/set_attitude
GimbalDeviceSetAttitude"| n20 + n20 -->|"/robot_1/interface/mavros/gimbal_control/manager/info
GimbalManagerInformation"| d52(["/robot_1/interface/mavros/gimbal_control/manager/info (no subscribers)"]) + d53(["/robot_1/interface/mavros/gimbal_control/manager/set_attitude (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_attitude
GimbalManagerSetAttitude"| n20 + d54(["/robot_1/interface/mavros/gimbal_control/manager/set_manual_control (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_manual_control
GimbalManagerSetPitchyaw"| n20 + d55(["/robot_1/interface/mavros/gimbal_control/manager/set_pitchyaw (no publishers)"]) -->|"/robot_1/interface/mavros/gimbal_control/manager/set_pitchyaw
GimbalManagerSetPitchyaw"| n20 + n20 -->|"/robot_1/interface/mavros/gimbal_control/manager/status
GimbalManagerStatus"| d56(["/robot_1/interface/mavros/gimbal_control/manager/status (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/compass_hdg
Float64"| n5 + n21 -->|"/robot_1/interface/mavros/global_position/global
NavSatFix"| n54 + n21 -->|"/robot_1/interface/mavros/global_position/global
NavSatFix"| n70 + n21 -->|"/robot_1/interface/mavros/global_position/gp_lp_offset
PoseStamped"| d57(["/robot_1/interface/mavros/global_position/gp_lp_offset (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/gp_origin
GeoPointStamped"| n25 + n21 -->|"/robot_1/interface/mavros/global_position/local
Odometry"| n70 + n21 -->|"/robot_1/interface/mavros/global_position/raw/fix
NavSatFix"| n5 + n21 -->|"/robot_1/interface/mavros/global_position/raw/gps_vel
TwistStamped"| d58(["/robot_1/interface/mavros/global_position/raw/gps_vel (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/raw/satellites
UInt32"| d59(["/robot_1/interface/mavros/global_position/raw/satellites (no subscribers)"]) + n21 -->|"/robot_1/interface/mavros/global_position/rel_alt
Float64"| d60(["/robot_1/interface/mavros/global_position/rel_alt (no subscribers)"]) + d61(["/robot_1/interface/mavros/global_position/set_gp_origin (no publishers)"]) -->|"/robot_1/interface/mavros/global_position/set_gp_origin
GeoPointStamped"| n21 + d62(["/robot_1/interface/mavros/gps_input/gps_input (no publishers)"]) -->|"/robot_1/interface/mavros/gps_input/gps_input
GPSINPUT"| n22 + n23 -->|"/robot_1/interface/mavros/gps_rtk/rtk_baseline
RTKBaseline"| d63(["/robot_1/interface/mavros/gps_rtk/rtk_baseline (no subscribers)"]) + d64(["/robot_1/interface/mavros/gps_rtk/send_rtcm (no publishers)"]) -->|"/robot_1/interface/mavros/gps_rtk/send_rtcm
RTCM"| n23 + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps1/raw
GPSRAW"| d65(["/robot_1/interface/mavros/gpsstatus/gps1/raw (no subscribers)"]) + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps1/rtk
GPSRTK"| d66(["/robot_1/interface/mavros/gpsstatus/gps1/rtk (no subscribers)"]) + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps2/raw
GPSRAW"| d67(["/robot_1/interface/mavros/gpsstatus/gps2/raw (no subscribers)"]) + n24 -->|"/robot_1/interface/mavros/gpsstatus/gps2/rtk
GPSRTK"| d68(["/robot_1/interface/mavros/gpsstatus/gps2/rtk (no subscribers)"]) + n26 -->|"/robot_1/interface/mavros/hil/actuator_controls
HilActuatorControls"| d69(["/robot_1/interface/mavros/hil/actuator_controls (no subscribers)"]) + n26 -->|"/robot_1/interface/mavros/hil/controls
HilControls"| d70(["/robot_1/interface/mavros/hil/controls (no subscribers)"]) + d71(["/robot_1/interface/mavros/hil/gps (no publishers)"]) -->|"/robot_1/interface/mavros/hil/gps
HilGPS"| n26 + d72(["/robot_1/interface/mavros/hil/imu_ned (no publishers)"]) -->|"/robot_1/interface/mavros/hil/imu_ned
HilSensor"| n26 + d73(["/robot_1/interface/mavros/hil/optical_flow (no publishers)"]) -->|"/robot_1/interface/mavros/hil/optical_flow
OpticalFlowRad"| n26 + d74(["/robot_1/interface/mavros/hil/rc_inputs (no publishers)"]) -->|"/robot_1/interface/mavros/hil/rc_inputs
RCIn"| n26 + d75(["/robot_1/interface/mavros/hil/state (no publishers)"]) -->|"/robot_1/interface/mavros/hil/state
HilStateQuaternion"| n26 + n27 -->|"/robot_1/interface/mavros/home_position/home
HomePosition"| n21 + n27 -->|"/robot_1/interface/mavros/home_position/home
HomePosition"| n70 + n70 -->|"/robot_1/interface/mavros/home_position/set
HomePosition"| n27 + n28 -->|"/robot_1/interface/mavros/imu/data
Imu"| d76(["/robot_1/interface/mavros/imu/data (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/data_raw
Imu"| d77(["/robot_1/interface/mavros/imu/data_raw (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/diff_pressure
FluidPressure"| d78(["/robot_1/interface/mavros/imu/diff_pressure (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/mag
MagneticField"| d79(["/robot_1/interface/mavros/imu/mag (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/static_pressure
FluidPressure"| d80(["/robot_1/interface/mavros/imu/static_pressure (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/temperature_baro
Temperature"| d81(["/robot_1/interface/mavros/imu/temperature_baro (no subscribers)"]) + n28 -->|"/robot_1/interface/mavros/imu/temperature_imu
Temperature"| d82(["/robot_1/interface/mavros/imu/temperature_imu (no subscribers)"]) + n29 -->|"/robot_1/interface/mavros/landing_target/lt_marker
Vector3Stamped"| d83(["/robot_1/interface/mavros/landing_target/lt_marker (no subscribers)"]) + d84(["/robot_1/interface/mavros/landing_target/pose (no publishers)"]) -->|"/robot_1/interface/mavros/landing_target/pose
PoseStamped"| n29 + n29 -->|"/robot_1/interface/mavros/landing_target/pose_in
PoseStamped"| d85(["/robot_1/interface/mavros/landing_target/pose_in (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/accel
AccelWithCovarianceStamped"| d86(["/robot_1/interface/mavros/local_position/accel (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/odom
Odometry"| n71 + n30 -->|"/robot_1/interface/mavros/local_position/pose
PoseStamped"| n54 + n30 -->|"/robot_1/interface/mavros/local_position/pose_cov
PoseWithCovarianceStamped"| d87(["/robot_1/interface/mavros/local_position/pose_cov (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/velocity_body
TwistStamped"| d88(["/robot_1/interface/mavros/local_position/velocity_body (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/velocity_body_cov
TwistWithCovarianceStamped"| d89(["/robot_1/interface/mavros/local_position/velocity_body_cov (no subscribers)"]) + n30 -->|"/robot_1/interface/mavros/local_position/velocity_local
TwistStamped"| d90(["/robot_1/interface/mavros/local_position/velocity_local (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/log_transfer/raw/log_data
LogData"| d91(["/robot_1/interface/mavros/log_transfer/raw/log_data (no subscribers)"]) + n31 -->|"/robot_1/interface/mavros/log_transfer/raw/log_entry
LogEntry"| d92(["/robot_1/interface/mavros/log_transfer/raw/log_entry (no subscribers)"]) + n32 -->|"/robot_1/interface/mavros/mag_calibration/report
MagnetometerReporter"| d93(["/robot_1/interface/mavros/mag_calibration/report (no subscribers)"]) + n32 -->|"/robot_1/interface/mavros/mag_calibration/status
UInt8"| d94(["/robot_1/interface/mavros/mag_calibration/status (no subscribers)"]) + n33 -->|"/robot_1/interface/mavros/manual_control/control
ManualControl"| d95(["/robot_1/interface/mavros/manual_control/control (no subscribers)"]) + d96(["/robot_1/interface/mavros/manual_control/send (no publishers)"]) -->|"/robot_1/interface/mavros/manual_control/send
ManualControl"| n33 + n37 -->|"/robot_1/interface/mavros/mission/reached
WaypointReached"| d97(["/robot_1/interface/mavros/mission/reached (no subscribers)"]) + n37 -->|"/robot_1/interface/mavros/mission/waypoints
WaypointList"| d98(["/robot_1/interface/mavros/mission/waypoints (no subscribers)"]) + d99(["/robot_1/interface/mavros/mocap/pose (no publishers)"]) -->|"/robot_1/interface/mavros/mocap/pose
PoseStamped"| n38 + d100(["/robot_1/interface/mavros/mocap/tf (no publishers)"]) -->|"/robot_1/interface/mavros/mocap/tf
TransformStamped"| n38 + d101(["/robot_1/interface/mavros/mount_control/command (no publishers)"]) -->|"/robot_1/interface/mavros/mount_control/command
MountControl"| n39 + n39 -->|"/robot_1/interface/mavros/mount_control/orientation
Quaternion"| d102(["/robot_1/interface/mavros/mount_control/orientation (no subscribers)"]) + n39 -->|"/robot_1/interface/mavros/mount_control/status
Vector3Stamped"| d103(["/robot_1/interface/mavros/mount_control/status (no subscribers)"]) + n40 -->|"/robot_1/interface/mavros/nav_controller_output/output
NavControllerOutput"| d104(["/robot_1/interface/mavros/nav_controller_output/output (no subscribers)"]) + d105(["/robot_1/interface/mavros/obstacle/send (no publishers)"]) -->|"/robot_1/interface/mavros/obstacle/send
LaserScan"| n41 + d106(["/robot_1/interface/mavros/obstacle_distance_3d/send (no publishers)"]) -->|"/robot_1/interface/mavros/obstacle_distance_3d/send
ObstacleDistance3D"| n42 + n43 -->|"/robot_1/interface/mavros/odometry/in
Odometry"| d107(["/robot_1/interface/mavros/odometry/in (no subscribers)"]) + d108(["/robot_1/interface/mavros/odometry/out (no publishers)"]) -->|"/robot_1/interface/mavros/odometry/out
Odometry"| n43 + d109(["/robot_1/interface/mavros/onboard_computer/status (no publishers)"]) -->|"/robot_1/interface/mavros/onboard_computer/status
OnboardComputerStatus"| n44 + d110(["/robot_1/interface/mavros/open_drone_id/basic_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/basic_id
OpenDroneIDBasicID"| n45 + d111(["/robot_1/interface/mavros/open_drone_id/operator_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/operator_id
OpenDroneIDOperatorID"| n45 + d112(["/robot_1/interface/mavros/open_drone_id/self_id (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/self_id
OpenDroneIDSelfID"| n45 + d113(["/robot_1/interface/mavros/open_drone_id/system (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/system
OpenDroneIDSystem"| n45 + d114(["/robot_1/interface/mavros/open_drone_id/system_update (no publishers)"]) -->|"/robot_1/interface/mavros/open_drone_id/system_update
OpenDroneIDSystemUpdate"| n45 + n46 -->|"/robot_1/interface/mavros/optical_flow/ground_distance
Range"| d115(["/robot_1/interface/mavros/optical_flow/ground_distance (no subscribers)"]) + n46 -->|"/robot_1/interface/mavros/optical_flow/raw/optical_flow
OpticalFlow"| d116(["/robot_1/interface/mavros/optical_flow/raw/optical_flow (no subscribers)"]) + d117(["/robot_1/interface/mavros/optical_flow/raw/send (no publishers)"]) -->|"/robot_1/interface/mavros/optical_flow/raw/send
OpticalFlow"| n46 + n47 -->|"/robot_1/interface/mavros/param/event
ParamEvent"| d118(["/robot_1/interface/mavros/param/event (no subscribers)"]) + d119(["/robot_1/interface/mavros/play_tune (no publishers)"]) -->|"/robot_1/interface/mavros/play_tune
PlayTuneV2"| n48 + n49 -->|"/robot_1/interface/mavros/px4flow/ground_distance
Range"| d120(["/robot_1/interface/mavros/px4flow/ground_distance (no subscribers)"]) + n49 -->|"/robot_1/interface/mavros/px4flow/raw/optical_flow_rad
OpticalFlowRad"| d121(["/robot_1/interface/mavros/px4flow/raw/optical_flow_rad (no subscribers)"]) + d122(["/robot_1/interface/mavros/px4flow/raw/send (no publishers)"]) -->|"/robot_1/interface/mavros/px4flow/raw/send
OpticalFlowRad"| n49 + n49 -->|"/robot_1/interface/mavros/px4flow/temperature
Temperature"| d123(["/robot_1/interface/mavros/px4flow/temperature (no subscribers)"]) + n60 -->|"/robot_1/interface/mavros/radio_status
RadioStatus"| d124(["/robot_1/interface/mavros/radio_status (no subscribers)"]) + n50 -->|"/robot_1/interface/mavros/rallypoint/rallypoints
WaypointList"| d125(["/robot_1/interface/mavros/rallypoint/rallypoints (no subscribers)"]) + n51 -->|"/robot_1/interface/mavros/rc/in
RCIn"| d126(["/robot_1/interface/mavros/rc/in (no subscribers)"]) + n51 -->|"/robot_1/interface/mavros/rc/out
RCOut"| d127(["/robot_1/interface/mavros/rc/out (no subscribers)"]) + d128(["/robot_1/interface/mavros/rc/override (no publishers)"]) -->|"/robot_1/interface/mavros/rc/override
OverrideRCIn"| n51 + d129(["/robot_1/interface/mavros/setpoint_accel/accel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_accel/accel
Vector3Stamped"| n52 + d130(["/robot_1/interface/mavros/setpoint_attitude/cmd_vel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_attitude/cmd_vel
TwistStamped"| n53 + d131(["/robot_1/interface/mavros/setpoint_attitude/thrust (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_attitude/thrust
Thrust"| n53 + n70 -->|"/robot_1/interface/mavros/setpoint_position/global
GeoPoseStamped"| n54 + d132(["/robot_1/interface/mavros/setpoint_position/global_to_local (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_position/global_to_local
GeoPoseStamped"| n54 + n70 -->|"/robot_1/interface/mavros/setpoint_position/local
PoseStamped"| n54 + n70 -->|"/robot_1/interface/mavros/setpoint_raw/attitude
AttitudeTarget"| n55 + d133(["/robot_1/interface/mavros/setpoint_raw/global (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_raw/global
GlobalPositionTarget"| n55 + n70 -->|"/robot_1/interface/mavros/setpoint_raw/local
PositionTarget"| n55 + n55 -->|"/robot_1/interface/mavros/setpoint_raw/target_attitude
AttitudeTarget"| d134(["/robot_1/interface/mavros/setpoint_raw/target_attitude (no subscribers)"]) + n55 -->|"/robot_1/interface/mavros/setpoint_raw/target_global
GlobalPositionTarget"| d135(["/robot_1/interface/mavros/setpoint_raw/target_global (no subscribers)"]) + n55 -->|"/robot_1/interface/mavros/setpoint_raw/target_local
PositionTarget"| d136(["/robot_1/interface/mavros/setpoint_raw/target_local (no subscribers)"]) + n56 -->|"/robot_1/interface/mavros/setpoint_trajectory/desired
Path"| d137(["/robot_1/interface/mavros/setpoint_trajectory/desired (no subscribers)"]) + d138(["/robot_1/interface/mavros/setpoint_trajectory/local (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_trajectory/local
MultiDOFJointTrajectory"| n56 + d139(["/robot_1/interface/mavros/setpoint_velocity/cmd_vel (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_velocity/cmd_vel
TwistStamped"| n57 + d140(["/robot_1/interface/mavros/setpoint_velocity/cmd_vel_unstamped (no publishers)"]) -->|"/robot_1/interface/mavros/setpoint_velocity/cmd_vel_unstamped
Twist"| n57 + n58 -->|"/robot_1/interface/mavros/sim_state/acceleration
Vector3Stamped"| d141(["/robot_1/interface/mavros/sim_state/acceleration (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/attitude
Imu"| d142(["/robot_1/interface/mavros/sim_state/attitude (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/global_position
NavSatFix"| d143(["/robot_1/interface/mavros/sim_state/global_position (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/velocity_body
TwistStamped"| d144(["/robot_1/interface/mavros/sim_state/velocity_body (no subscribers)"]) + n58 -->|"/robot_1/interface/mavros/sim_state/velocity_local
TwistStamped"| d145(["/robot_1/interface/mavros/sim_state/velocity_local (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/state
State"| n70 + n59 -->|"/robot_1/interface/mavros/status_event
StatusEvent"| d146(["/robot_1/interface/mavros/status_event (no subscribers)"]) + n59 -->|"/robot_1/interface/mavros/statustext/recv
StatusText"| d147(["/robot_1/interface/mavros/statustext/recv (no subscribers)"]) + d148(["/robot_1/interface/mavros/statustext/send (no publishers)"]) -->|"/robot_1/interface/mavros/statustext/send
StatusText"| n59 + n59 -->|"/robot_1/interface/mavros/sys_status
SysStatus"| d149(["/robot_1/interface/mavros/sys_status (no subscribers)"]) + n6 -->|"/robot_1/interface/mavros/target_actuator_control
ActuatorControl"| d150(["/robot_1/interface/mavros/target_actuator_control (no subscribers)"]) + n61 -->|"/robot_1/interface/mavros/terrain/report
TerrainReport"| d151(["/robot_1/interface/mavros/terrain/report (no subscribers)"]) + n62 -->|"/robot_1/interface/mavros/time_reference
TimeReference"| d152(["/robot_1/interface/mavros/time_reference (no subscribers)"]) + n62 -->|"/robot_1/interface/mavros/timesync_status
TimesyncStatus"| d153(["/robot_1/interface/mavros/timesync_status (no subscribers)"]) + n63 -->|"/robot_1/interface/mavros/trajectory/desired
Trajectory"| d154(["/robot_1/interface/mavros/trajectory/desired (no subscribers)"]) + d155(["/robot_1/interface/mavros/trajectory/generated (no publishers)"]) -->|"/robot_1/interface/mavros/trajectory/generated
Trajectory"| n63 + d156(["/robot_1/interface/mavros/trajectory/path (no publishers)"]) -->|"/robot_1/interface/mavros/trajectory/path
Path"| n63 + d157(["/robot_1/interface/mavros/tunnel/in (no publishers)"]) -->|"/robot_1/interface/mavros/tunnel/in
Tunnel"| n64 + n64 -->|"/robot_1/interface/mavros/tunnel/out
Tunnel"| d158(["/robot_1/interface/mavros/tunnel/out (no subscribers)"]) + n65 -->|"/robot_1/interface/mavros/vfr_hud
VfrHud"| d159(["/robot_1/interface/mavros/vfr_hud (no subscribers)"]) + d160(["/robot_1/interface/mavros/vision_pose/pose (no publishers)"]) -->|"/robot_1/interface/mavros/vision_pose/pose
PoseStamped"| n66 + d161(["/robot_1/interface/mavros/vision_pose/pose_cov (no publishers)"]) -->|"/robot_1/interface/mavros/vision_pose/pose_cov
PoseWithCovarianceStamped"| n66 + d162(["/robot_1/interface/mavros/vision_speed/speed_twist (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_twist
TwistStamped"| n67 + d163(["/robot_1/interface/mavros/vision_speed/speed_twist_cov (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_twist_cov
TwistWithCovarianceStamped"| n67 + d164(["/robot_1/interface/mavros/vision_speed/speed_vector (no publishers)"]) -->|"/robot_1/interface/mavros/vision_speed/speed_vector
Vector3Stamped"| n67 + n68 -->|"/robot_1/interface/mavros/wind_estimation
TwistWithCovarianceStamped"| d165(["/robot_1/interface/mavros/wind_estimation (no subscribers)"]) + d166(["/robot_1/interface/pose_command (no publishers)"]) -->|"/robot_1/interface/pose_command
PoseStamped"| n70 + d167(["/robot_1/interface/rate_thrust_command (no publishers)"]) -->|"/robot_1/interface/rate_thrust_command
RateThrust"| n70 + d168(["/robot_1/interface/roll_pitch_yawrate_thrust_command (no publishers)"]) -->|"/robot_1/interface/roll_pitch_yawrate_thrust_command
RollPitchYawrateThrust"| n70 + d169(["/robot_1/interface/torque_thrust_command (no publishers)"]) -->|"/robot_1/interface/torque_thrust_command
TorqueThrust"| n70 + d170(["/robot_1/interface/velocity_command (no publishers)"]) -->|"/robot_1/interface/velocity_command
TwistStamped"| n70 + d171(["/robot_1/joint_states (no publishers)"]) -->|"/robot_1/joint_states
JointState"| n76 + d172(["/robot_1/macvo/odometry (no publishers)"]) -->|"/robot_1/macvo/odometry
Odometry"| n79 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n2 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n3 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n69 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n75 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n78 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n79 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n80 + n71 -->|"/robot_1/odometry_conversion/odometry
Odometry"| n81 + d173(["/robot_1/perception/macvo/disparity (no publishers)"]) -->|"/robot_1/perception/macvo/disparity
DisparityImage"| n4 + d173 -->|"/robot_1/perception/macvo/disparity
DisparityImage"| n79 + d174(["/robot_1/perception/macvo/point_cloud (no publishers)"]) -->|"/robot_1/perception/macvo/point_cloud
PointCloud"| n79 + n73 -->|"/robot_1/perception/stereo_image_proc/disparity
DisparityImage"| n74 + n74 -->|"/robot_1/perception/stereo_image_proc/point_cloud
PointCloud2"| n79 + n75 -->|"/robot_1/random_walk_node/goal_point_viz
Marker"| d175(["/robot_1/random_walk_node/goal_point_viz (no subscribers)"]) + n75 -->|"/robot_1/random_walk_node/traj_viz
Marker"| d176(["/robot_1/random_walk_node/traj_viz (no subscribers)"]) + n76 -->|"/robot_1/robot_description
String"| d177(["/robot_1/robot_description (no subscribers)"]) + d178(["/robot_1/sensors/front_stereo/left/camera_info (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n73 + d178 -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n74 + d178 -->|"/robot_1/sensors/front_stereo/left/camera_info
CameraInfo"| n79 + d179(["/robot_1/sensors/front_stereo/left/depth_ground_truth (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/depth_ground_truth
Image"| n79 + d180(["/robot_1/sensors/front_stereo/left/image_rect (no publishers)"]) -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n73 + d180 -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n74 + d180 -->|"/robot_1/sensors/front_stereo/left/image_rect
Image"| n79 + d181(["/robot_1/sensors/front_stereo/right/camera_info (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n4 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n73 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n74 + d181 -->|"/robot_1/sensors/front_stereo/right/camera_info
CameraInfo"| n79 + d182(["/robot_1/sensors/front_stereo/right/depth_ground_truth (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/depth_ground_truth
Image"| n79 + d183(["/robot_1/sensors/front_stereo/right/image_rect (no publishers)"]) -->|"/robot_1/sensors/front_stereo/right/image_rect
Image"| n73 + d183 -->|"/robot_1/sensors/front_stereo/right/image_rect
Image"| n79 + d184(["/robot_1/sensors/lidar/point_cloud (no publishers)"]) -->|"/robot_1/sensors/lidar/point_cloud
PointCloud2"| n79 + n77 -->|"/robot_1/sensors/ouster/point_cloud
PointCloud2"| n82 + d185(["/robot_1/sensors/ouster/point_cloud_raw (no publishers)"]) -->|"/robot_1/sensors/ouster/point_cloud_raw
PointCloud2"| n77 + n78 -->|"/robot_1/takeoff_landing_planner/is_airborne
Bool"| d186(["/robot_1/takeoff_landing_planner/is_airborne (no subscribers)"]) + d187(["/robot_1/takeoff_landing_planner/trajectory_completion_percentage (no publishers)"]) -->|"/robot_1/takeoff_landing_planner/trajectory_completion_percentage
Float32"| n78 + n81 -->|"/robot_1/trajectory_controller/closest_point
Odometry"| d188(["/robot_1/trajectory_controller/closest_point (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/look_ahead
Odometry"| n4 + n81 -->|"/robot_1/trajectory_controller/projected_drone_pose
PoseStamped"| n69 + n81 -->|"/robot_1/trajectory_controller/tracking_error
Float32"| d189(["/robot_1/trajectory_controller/tracking_error (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n3 + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n4 + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n69 + n81 -->|"/robot_1/trajectory_controller/tracking_point
Odometry"| n78 + n81 -->|"/robot_1/trajectory_controller/tracking_point_velocity_magnitude
Float32"| d190(["/robot_1/trajectory_controller/tracking_point_velocity_magnitude (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/traj_drone_point
Odometry"| d191(["/robot_1/trajectory_controller/traj_drone_point (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/trajectory_completion_percentage
Float32"| n80 + n81 -->|"/robot_1/trajectory_controller/trajectory_controller_debug_markers
MarkerArray"| n79 + n78 -->|"/robot_1/trajectory_controller/trajectory_override
TrajectoryXYZVYaw"| n81 + n80 -->|"/robot_1/trajectory_controller/trajectory_override
TrajectoryXYZVYaw"| n81 + n4 -->|"/robot_1/trajectory_controller/trajectory_segment_to_add
TrajectoryXYZVYaw"| n81 + n81 -->|"/robot_1/trajectory_controller/trajectory_time
Float32"| d192(["/robot_1/trajectory_controller/trajectory_time (no subscribers)"]) + n81 -->|"/robot_1/trajectory_controller/trajectory_vis
MarkerArray"| n79 + n81 -->|"/robot_1/trajectory_controller/virtual_tracking_point
Odometry"| d193(["/robot_1/trajectory_controller/virtual_tracking_point (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_overwrites
UpdateGrid"| d194(["/robot_1/vdb_mapping/vdb_map_overwrites (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_pointcloud
PointCloud2"| d195(["/robot_1/vdb_mapping/vdb_map_pointcloud (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_sections
UpdateGrid"| d196(["/robot_1/vdb_mapping/vdb_map_sections (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_updates
UpdateGrid"| d197(["/robot_1/vdb_mapping/vdb_map_updates (no subscribers)"]) + n82 -->|"/robot_1/vdb_mapping/vdb_map_visualization
Marker"| n75 + n82 -->|"/robot_1/vdb_mapping/vdb_map_visualization
Marker"| n79 + n34 -->|"/tf
TFMessage"| n69 + n34 -->|"/tf
TFMessage"| n79 + n71 -->|"/tf
TFMessage"| n69 + n71 -->|"/tf
TFMessage"| n79 + n76 -->|"/tf
TFMessage"| n69 + n76 -->|"/tf
TFMessage"| n79 + n81 -->|"/tf
TFMessage"| n69 + n81 -->|"/tf
TFMessage"| n79 + n34 -->|"/tf_static
TFMessage"| n69 + n34 -->|"/tf_static
TFMessage"| n79 + n72 -->|"/tf_static
TFMessage"| n69 + n72 -->|"/tf_static
TFMessage"| n79 + n76 -->|"/tf_static
TFMessage"| n69 + n76 -->|"/tf_static
TFMessage"| n79 + n83 -->|"/tf_static
TFMessage"| n69 + n83 -->|"/tf_static
TFMessage"| n79 + n34 -->|"/uas2/mavlink_sink
Mavlink"| n36 + n36 -->|"/uas2/mavlink_source
Mavlink"| n34 +``` + + From e361ef18ac9ab4189dc1976de3c2c73ac2a44b79 Mon Sep 17 00:00:00 2001 From: Andrew Jong Date: Thu, 20 Aug 2026 18:55:33 -0400 Subject: [PATCH 3/4] P3(rfc-379): build module Docker layers in the reusable workflow Gap found by the asm_macvo extraction: the workflow ran module add/sync (layer plan only) but never 'module lock --build', so tier-2/3 Dockerfile.module / overlay layers went unbuilt and untested in module CI. The build step runs after trunk image-ensure (the chain builds FROM those bases); the zero-module identity rule keeps it a no-op for dep-free modules. Co-Authored-By: Claude Fable 5 --- .github/workflows/module-system-tests.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/module-system-tests.yml b/.github/workflows/module-system-tests.yml index cc743b19e..3b403c466 100644 --- a/.github/workflows/module-system-tests.yml +++ b/.github/workflows/module-system-tests.yml @@ -461,6 +461,16 @@ jobs: echo "All required images present after pull/retag — skipping build." fi + # Build the module's Docker layer chain (RFC #379 §6) — must run AFTER + # the trunk base images are present (chain builds FROM them). The + # zero-module identity rule makes this a fast no-op for modules with no + # docker-relevant declarations; for tier-2/3 modules (Dockerfile.module / + # overlay_image) it builds the composed image and points the generated + # compose override at it, so the test bring-up actually exercises the + # module's dependency layers. + - name: Build module Docker layers + run: ./airstack.sh module lock --build + - name: Run tests env: AIRSTACK_ROOT: ${{ github.workspace }} From f1c1352b5b47d599b921dcc8a33113c17eb1a2a2 Mon Sep 17 00:00:00 2001 From: Andrew Jong Date: Sat, 22 Aug 2026 04:02:10 -0400 Subject: [PATCH 4/4] Bump version to 0.20.0-alpha.3 Co-Authored-By: Claude Fable 5 --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index eba42f47d..1d087323e 100644 --- a/.env +++ b/.env @@ -12,7 +12,7 @@ PROJECT_NAME="airstack" # If you've run ./airstack.sh setup, then this will auto-generate from the git commit hash every time a change is made # to a Dockerfile or docker-compose.yaml file. Otherwise this can also be set explicitly to make a release version. # auto-generated from git commit hash -VERSION="0.20.0-alpha.2" +VERSION="0.20.0-alpha.3" # Choose "dev" or "prebuilt". "dev" is for mounted code that must be built live. "prebuilt" is for built ros_ws baked into the image DOCKER_IMAGE_BUILD_MODE="dev" # Where to push and pull images from. Can replace with your docker hub username if using docker hub.