diff --git a/.agents/skills/add-ros2-package/assets/package_template/README.md b/.agents/skills/add-ros2-package/assets/package_template/README.md index b8d228260..07c9d215e 100644 --- a/.agents/skills/add-ros2-package/assets/package_template/README.md +++ b/.agents/skills/add-ros2-package/assets/package_template/README.md @@ -351,8 +351,6 @@ Apache-2.0 (consistent with AirStack) - **Maintainer:** Your Name (your.email@example.com) - **Contributors:** List additional contributors here -## Changelog - -### Version 0.0.1 (YYYY-MM-DD) -- Initial implementation -- TODO: Add changelog entries as the module evolves + diff --git a/.agents/skills/bump-version-and-release/SKILL.md b/.agents/skills/bump-version-and-release/SKILL.md index e925a5452..4d8f98341 100644 --- a/.agents/skills/bump-version-and-release/SKILL.md +++ b/.agents/skills/bump-version-and-release/SKILL.md @@ -1,6 +1,6 @@ --- name: bump-version-and-release -description: Bump the AirStack VERSION in .env (semver) before merging a PR that changes Docker image content, and update CHANGELOG. Required to pass the check-version-increment gate and to trigger the docker-build release workflow. +description: Bump the AirStack VERSION in .env (semver) before merging a PR that changes Docker image content, and record the change in the versioned Release Notes (docs/release_notes/index.md). Required to pass the check-version-increment gate and to trigger the docker-build release workflow. license: BSD-3-Clause-Clear metadata: author: AirLab CMU @@ -127,16 +127,21 @@ Open `/.env` and change exactly the `VERSION=` line. Keep the surrounding commen The validator strips surrounding `"` or `'`, so either quoting style works, but match the existing style (double quotes). -### 3. Update `CHANGELOG.md` +### 3. Update the Release Notes -Add an entry under `## [Unreleased]` describing your change (see "CHANGELOG Conventions" below). For a true release (no pre-release suffix), promote `[Unreleased]` to a new dated version section. +All change records live in the versioned Release Notes page, +`docs/release_notes/index.md` (there is no CHANGELOG.md — this page is the +single source). Add your bullets under the current version's `##` section +(see "Release Notes Conventions" below). For a true release (no pre-release +suffix), stamp that section's heading with the release date and open a fresh +`## (Unreleased)` section above it. ### 4. Verify locally ```bash airstack version # prints the new value grep '^VERSION=' .env # sanity-check the literal line -git diff .env CHANGELOG.md # review the diff +git diff .env docs/release_notes/index.md # review the diff ``` Optional regex preflight (mirrors the CI check): @@ -155,19 +160,25 @@ Bump version to 0.18.0-alpha.8 Recent commits in this repo use exactly this phrasing (`Bump version to 0.17.0`, `Bump version to 0.16.1`). -## CHANGELOG Conventions +## Release Notes Conventions -`CHANGELOG.md` follows [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) and Semantic Versioning. The literal layout in the repo is: +`docs/release_notes/index.md` is the versioned change record rendered on +the docs site — one `##` section per version, newest first, with the current +in-progress version marked `(Unreleased)`. It is also the ONLY place +change-relative language ("changed from", "renamed", "removed", RFC/PR +references) is allowed; feature docs describe only the current system (see +the `write-mkdocs-documentation` skill). + +Layout: ```markdown -# Changelog +# Release Notes -All notable changes to this project will be documented in this file. + -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## (Unreleased) -## [Unreleased] + ### Added @@ -177,38 +188,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - -### Fixed +### Removed - -## [1.0.0] - 2024-12-19 - -First official public release. - -### Added - -- - ### Fixed -- - -### Changed - -- +- -### Removed +## -- +... ``` Rules: -- Use the H2 sections **Added**, **Changed**, **Fixed**, **Removed**, **Deprecated**, **Security** as needed (Keep a Changelog standard set). -- For pre-release bumps (`-alpha.N`, `-beta.N`, `-rc.N`), keep your bullets under `## [Unreleased]`. Do not create a section per alpha. -- For a release bump (no suffix), rename `[Unreleased]` to `## [] - ` and add a fresh empty `## [Unreleased]` above it. -- Use ISO date format `YYYY-MM-DD`. -- Write user-facing prose, not commit log dumps. Mention new modules, breaking changes, and notable behavior shifts. +- Use the H3 sections **Added**, **Changed**, **Fixed**, **Removed**, **Deprecated**, **Security** as needed; a release may also open with a short narrative and breaking-changes subsection. +- For pre-release bumps (`-alpha.N`, `-beta.N`, `-rc.N`), keep your bullets under the current `(Unreleased)` section. Do not create a section per alpha. +- For a release bump (no suffix), retitle the section to `## ` and open a fresh `## (Unreleased)` above it. +- Write user-facing prose, not commit log dumps. Mention new modules, breaking changes, and notable behavior shifts, with what changed FROM what. ## Common Pitfalls @@ -216,11 +214,11 @@ Rules: - **Invalid semver.** Forms like `1.2`, `1.2.3-rc1`, `1.2.3-dev`, `1.2.3+sha.abc`, `v1.2.3`, or empty strings fail with `::error::VERSION '' does not match the required format.` The only allowed pre-release tags are exactly `alpha`, `beta`, `rc`, each followed by a literal dot and an integer (e.g. `-rc.1`, never `-rc1`). - **Going backwards.** `0.18.0 → 0.18.0-rc.1` looks like progress but is a regression: release > rc. Always move forward in the comparison tuple. - **Two PRs racing for the same number.** Whichever merges last wins; the loser's `check-version-increment` will start failing the moment the base advances past it. Rebase on the updated base branch and bump again. -- **Bumping but forgetting the CHANGELOG.** No CI gate enforces this, but reviewers will (and the release docs workflow lists what shipped per version, so missing entries become invisible history). +- **Bumping but forgetting the Release Notes.** No CI gate enforces this, but reviewers will (and the versioned docs deploys snapshot the page per release, so missing entries become invisible history). - **Bumping for pure docs PRs.** Wastes a registry tag. Prefer to keep docs-only changes off `.env` if possible — but if the gate is failing, an alpha bump is the path of least resistance. - **Editing `VERSION=` quoting.** The extractor regex `^VERSION\s*=\s*["\']?([^"\'#\s]+)` handles double quotes, single quotes, or no quotes, and stops at `#`/whitespace. Don't add inline comments after the value (e.g. `VERSION="0.18.1" # bumped`) — the trailing `# bumped` will be stripped from the value but obscures intent; put comments on their own line above. - **Touching only sub-compose `.env` files.** The check looks at the **repo-root** `.env` only. `robot/docker/.env` and friends are container env files, not the version source of truth. -- **Force-pushing after merge to fix CHANGELOG.** Don't. Land a follow-up PR with the CHANGELOG correction (and, by the rules above, another tiny VERSION bump). +- **Force-pushing after merge to fix Release Notes.** Don't. Land a follow-up PR with the correction (docs-only, so no VERSION bump needed unless the gate demands one). ## Release Checklist @@ -229,8 +227,8 @@ For a normal feature/fix PR: 1. [ ] Confirm the PR changes Docker image content or otherwise warrants a bump (see "When to Use"). 2. [ ] Pick the bump type (see "Choosing the Bump Type"). 3. [ ] Edit `/.env` — change only the `VERSION=` line. -4. [ ] Update `CHANGELOG.md` under `## [Unreleased]`. -5. [ ] `airstack version` and `git diff .env CHANGELOG.md` to verify. +4. [ ] Add your bullets to `docs/release_notes/index.md` under the current `(Unreleased)` version section. +5. [ ] `airstack version` and `git diff .env docs/release_notes/index.md` to verify. 6. [ ] Commit (`Bump version to ` is the established style). 7. [ ] Push and open the PR. Confirm `Check VERSION Increment` passes green. 8. [ ] After review, merge into `develop` (or `main` per branch policy). @@ -240,7 +238,7 @@ For a true release (dropping the pre-release suffix): 1. [ ] Land final fixes on `develop` with `-rc.N` bumps. 2. [ ] Open a PR that bumps `VERSION="X.Y.Z-rc.N"` → `VERSION="X.Y.Z"`. -3. [ ] In the same PR, promote `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD` and add a fresh empty `## [Unreleased]`. +3. [ ] In the same PR, retitle the Release Notes section to `## X.Y.Z — YYYY-MM-DD` and open a fresh `## (Unreleased)` above it. 4. [ ] Merge to `main`. 5. [ ] Wait for `docker-build.yml` to push and sign all images. 6. [ ] Create a GitHub Release with tag `X.Y.Z` (matching `VERSION` exactly). Publishing the release fires `deploy_docs_from_release.yaml`, which runs `mike deploy --push --update-aliases X.Y.Z latest` and updates the versioned docs site. @@ -249,13 +247,12 @@ For a true release (dropping the pre-release suffix): ## References - [`/.env`](../../../.env) — source of truth for `VERSION=` -- [`/CHANGELOG.md`](../../../CHANGELOG.md) — release history +- [`/docs/release_notes/index.md`](../../../docs/release_notes/index.md) — the versioned Release Notes (release history) - [`/.github/workflows/check-version-increment.yml`](../../../.github/workflows/check-version-increment.yml) — the PR gate (semver regex lives here) - [`/.github/workflows/docker-build.yml`](../../../.github/workflows/docker-build.yml) — build/push/sign on tag change - [`/.github/workflows/deploy_docs_from_release.yaml`](../../../.github/workflows/deploy_docs_from_release.yaml) — versioned docs on release - [`/.github/workflows/deploy_docs_from_main.yaml`](../../../.github/workflows/deploy_docs_from_main.yaml) and [`deploy_docs_from_develop.yaml`](../../../.github/workflows/deploy_docs_from_develop.yaml) — branch-tracking docs aliases - [`/airstack.sh`](../../../airstack.sh) — defines `airstack version` and `get_VERSION` (used everywhere image tags are built) -- [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) - [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) ## Related Skills diff --git a/.agents/skills/update-documentation/SKILL.md b/.agents/skills/update-documentation/SKILL.md index f08c3dd15..a61cb3ebb 100644 --- a/.agents/skills/update-documentation/SKILL.md +++ b/.agents/skills/update-documentation/SKILL.md @@ -333,14 +333,12 @@ Specify license (typically Apache-2.0 for AirStack). - **Maintainer:** Name (email) - **Contributors:** List contributors -## Changelog - -### Version X.Y.Z (YYYY-MM-DD) -- Feature: Added new capability -- Fix: Resolved issue with X -- Change: Modified behavior of Y ``` +Do not add a per-README "Changelog" section — change records live in the +versioned Release Notes (`docs/release_notes/index.md`), and READMEs +describe only the current system. + **Reference template:** `../add-ros2-package/assets/package_template/README.md` ### 2. Update mkdocs.yml Navigation @@ -585,10 +583,10 @@ class YourClass: If your module introduces breaking changes or deprecations: -**File:** `CHANGELOG.md` (repository root) +**File:** `docs/release_notes/index.md` (the versioned Release Notes — the only place change-relative language belongs; feature docs describe only the current system) ```markdown -## [Unreleased] +## (Unreleased) ### Added - New module: your_package for diff --git a/.agents/skills/write-mkdocs-documentation/SKILL.md b/.agents/skills/write-mkdocs-documentation/SKILL.md index cde34197f..9433f234c 100644 --- a/.agents/skills/write-mkdocs-documentation/SKILL.md +++ b/.agents/skills/write-mkdocs-documentation/SKILL.md @@ -19,6 +19,76 @@ metadata: ## Core Principles +### 0. Docs Are Standalone Snapshots (read this first) + +Documentation describes the system **as it exists now**, for a reader +encountering it fresh — someone with no access to the development history, +the diffs, the RFCs, or the conversations that produced the current design. +It is not a changelog, not a diff annotation, and not a record of decisions. + +**Never include change-relative language in docs pages.** Prohibited patterns: + +- "changed from X to Y", "updated from X", "previously this was X" +- "renamed from X", "moved from X", "no longer does X" +- "decided by [person]", "pinned by [person]", "per discussion with [person]" +- "as of this PR / this change / this version" framing inside feature docs +- References to RFCs, design discussions, issue/PR numbers, or review + decisions as justification ("per RFC #379", "see discussion #380") +- Any sentence that only makes sense if the reader knows the prior state + +The test: **a first-time reader must be able to parse every sentence without +knowing any previous version, diff, or conversation.** If a sentence fails +that test, it belongs in Release Notes, not in the page. + +**Where change context goes: Release Notes.** All change-relative +information coalesces into a single Release Notes page for the current +version (`docs/release_notes/index.md`, one `##` section per version). That +page is the only place where "changed from", "replaces", "deprecated", +"decided in review", and RFC/PR references are appropriate. + +When your edit involves a behavioral change, do both: + +1. Update the relevant docs pages to describe only the new behavior, written + as if it had always been that way. +2. Add an entry to the current version's Release Notes page describing what + changed, from what, and (if relevant) why or who drove the decision. + +If a Release Notes page for the current version does not exist, create one. +Never scatter per-page "changelog"/"migration" sections across the docs. + +**Motivation before mechanics.** Every page (and every major section) starts +with a brief motivation: why this component exists and why it is built the +way it is — one short paragraph. Only then the details (API, configuration, +usage, internals). A reader should be able to answer "why would I use this, +and why does it look like this?" before the first code block or parameter +table. + +❌ **Bad opening:** + +> AirStack builds are defined in `docker-compose.yaml` with per-module +> Dockerfiles. The following build arguments are supported: ... + +✅ **Good opening:** + +> AirStack targets both x86 workstations and Jetson/Orin robots from a +> single codebase, so its build system is organized around per-module Docker +> images rather than one monolithic environment. This keeps modules +> independently buildable and lets each one pin platform-specific +> dependencies without breaking the others. Builds are defined in +> `docker-compose.yaml` with per-module Dockerfiles: ... + +**Standalone-snapshot checklist** (verify before finalizing any docs edit): + +- [ ] No sentence requires knowledge of a previous version, a diff, an RFC, + or a conversation to parse. +- [ ] All change-relative content has been moved to the current version's + Release Notes page. +- [ ] The page/section opens with motivation (why it exists, why this + design) before details. +- [ ] A first-time reader could use the page without any other context. +- [ ] Names, defaults, and behaviors described match the current code, not + an intermediate state. + ### 1. Progressive Disclosure **Organize for newcomers first, depth second:** @@ -921,11 +991,14 @@ airstack up robot-desktop # Start robot container grep -r "old_topic_name" docs/ ``` -2. **Update all references** +2. **Update all references** — rewrite each page to describe only the new + behavior, as if it had always been that way (no "renamed from", + "no longer", "previously") 3. **Update diagrams** (if architecture changed) -4. **Add migration notes** (if breaking change) -5. **Update CHANGELOG.md** -6. **Verify all links still work** +4. **Record the change in Release Notes** (`docs/release_notes/index.md`, + under the current version's section) — the only place change-relative + language belongs +5. **Verify all links still work** ### Periodic Reviews @@ -941,6 +1014,13 @@ airstack up robot-desktop # Start robot container When creating or updating documentation: +- [ ] No change-relative language (no "previously", "renamed from", + "no longer", RFC/PR/discussion references) — pages are standalone + snapshots of the current system +- [ ] Behavioral changes recorded in `docs/release_notes/index.md` under + the current version's section, not as per-page migration notes +- [ ] Page and major sections open with motivation (why it exists, why this + design) before mechanics - [ ] Content follows progressive disclosure (concept → example → depth) - [ ] All lists have blank line before them - [ ] Code blocks have language tags diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..6e9321ef1 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(docker compose * config*)" + ] + } +} diff --git a/.env b/.env index d6bad4129..eb941a35e 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.8" +VERSION="0.20.0-alpha.9" # Image-tag discriminator ONLY (appears in the image tag suffix, e.g. ..._robot-x86-64_dev). # No Dockerfile consumes it: "prebuilt" does NOT bake the built ros_ws into the image today — # a real prebuilt (workspace-baked) stage is future work. Keep "dev" (mounted code, built live). diff --git a/.github/orchestrator/README.md b/.github/orchestrator/README.md index 068f98dab..4c98a535b 100644 --- a/.github/orchestrator/README.md +++ b/.github/orchestrator/README.md @@ -2,7 +2,7 @@ This describes how a small always-on orchestrator service runs GitHub Actions jobs on truly ephemeral GPU workers scheduled by [NVIDIA OSMO](https://nvidia.github.io/OSMO/). The orchestrator is a Python service that continuously polls GitHub for queued workflow jobs, submits a fresh **OSMO workflow** for each one (a single-use JIT runner in a privileged, GPU-enabled container), and reaps it when the job completes. Each CI job runs on a clean pod with no state shared between runs and no long-lived credentials on the worker. -This is a drop-in replacement for the previous OpenStack-Nova backend. The GitHub side is unchanged — `system-tests.yml` still uses `runs-on: [self-hosted, airstack-ephemeral]`, the single-use JIT runner config, and the same-repo fork guard. Only the *spawn target* changed from "create a Nova VM" to "submit an OSMO workflow", so the one-job-per-worker, destroy-after semantics are identical: when the runner's `run.sh` exits after one job, the OSMO task completes and the pod is torn down. +On the GitHub side, `system-tests.yml` uses `runs-on: [self-hosted, airstack-ephemeral]`, a single-use JIT runner config, and a same-repo fork guard. The worker semantics are one-job-per-worker, destroy-after: when the runner's `run.sh` exits after one job, the OSMO task completes and the pod is torn down. The orchestrator host is the only machine that holds the GitHub PAT and the OSMO service-account token; workers are destroyed after a single job. @@ -46,7 +46,7 @@ Key properties: - **Truly ephemeral**: every job runs on a clean pod. No Docker layer cache pollution, no leftover containers, no carry-over from prior runs. - **PAT isolation**: the GitHub PAT lives only on the orchestrator. Workers receive a single-use [JIT runner config](https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-configuration-for-a-just-in-time-runner-for-a-repository) — a base64 token bound to one runner registration, valid only for a short window. -- **Service-account auth**: the orchestrator authenticates to OSMO with a shared, non-personal [service-account token](https://nvidia.github.io/OSMO/main/deployment_guide/appendix/authentication/service_accounts.html) (the analog of the old OpenStack application credential). CI runs never route through an individual's account, so PRs don't consume anyone's personal GPU quota and nothing breaks when a person leaves. +- **Service-account auth**: the orchestrator authenticates to OSMO with a shared, non-personal [service-account token](https://nvidia.github.io/OSMO/main/deployment_guide/appendix/authentication/service_accounts.html). CI runs never route through an individual's account, so PRs don't consume anyone's personal GPU quota and nothing breaks when a person leaves. - **Crash-safe reaping**: every workflow is named `gha-runner--`. The reap loop cancels any active workflow with that prefix not present in `state.json`, so a crashed orchestrator can't leak workflows. ## Prerequisites @@ -63,7 +63,7 @@ Key properties: ### 1. Build & push the runner image -The worker image bakes in Docker CE + compose, the NVIDIA container toolkit, and the GitHub Actions runner (what cloud-init used to install at boot on the VM), so pod start is fast and the JIT token can't expire mid-bootstrap. +The worker image bakes in Docker CE + compose, the NVIDIA container toolkit, and the GitHub Actions runner, so pod start is fast and the JIT token can't expire mid-bootstrap (no install work at boot). ```bash cd .github/orchestrator @@ -172,7 +172,7 @@ repo**, and the orchestrator polls exactly one `repo:` per instance. To add an First-party only: the reusable workflow refuses callers outside the castacks org, mirroring the fork-PR block. Org-level polling across registered repos -(one instance, many repos) is the RFC #379 Phase 4 replacement for this +(one instance, many repos) is planned as a future replacement for this per-repo setup. ## Operational notes @@ -251,7 +251,7 @@ osmo workflow logs "$WF" --task runner -n 300 # last 300 lines ### 4. Break-glass shell into a running worker -If the workflow is still `RUNNING`, exec into the pod (replaces the old SSH-via-floating-IP path): +If the workflow is still `RUNNING`, exec into the pod: ```bash osmo workflow exec "$WF" runner # /bin/bash in the runner task diff --git a/.github/orchestrator/cloud-init.yaml.j2 b/.github/orchestrator/cloud-init.yaml.j2 new file mode 100644 index 000000000..921417c18 --- /dev/null +++ b/.github/orchestrator/cloud-init.yaml.j2 @@ -0,0 +1,71 @@ +#cloud-config +# Rendered per-spawn by orchestrator.py with two Jinja variables: +# encoded_jit_config - single-use base64 JIT config from GitHub +# runner_version - GitHub Actions runner version (e.g. 2.334.0) +# +# The base image (Ubuntu-24.04-GPU-Headless) already has NVIDIA drivers. +# This cloud-init adds Docker (with the compose plugin), nvidia-container-toolkit, +# downloads the GitHub Actions runner, registers it with the JIT config, runs +# exactly one job (the JIT config + --ephemeral makes the runner exit after one +# job), and shuts the VM down. The orchestrator then deletes the server. + +package_update: true +package_upgrade: false +packages: + - jq + - curl + - ca-certificates + - gnupg + +write_files: + - path: /usr/local/bin/airstack-runner-bootstrap.sh + permissions: "0755" + owner: root:root + content: | + #!/usr/bin/env bash + set -euxo pipefail + + # Install Docker (with compose plugin) from Docker's official channel. + # get.docker.com handles apt repo setup + nvidia-container-toolkit-compatible + # docker-ce, plus the docker-compose-plugin we need for `airstack up`. + curl -fsSL https://get.docker.com | sh + + # nvidia-container-toolkit is required for GPU containers (liveliness / + # autonomy tests). The base image has the NVIDIA *drivers* but we still + # need the container runtime hooks here. + distribution=$(. /etc/os-release; echo "$ID$VERSION_ID") + curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ + | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg + curl -fsSL "https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list" \ + | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ + > /etc/apt/sources.list.d/nvidia-container-toolkit.list + apt-get update + apt-get install -y nvidia-container-toolkit + nvidia-ctk runtime configure --runtime=docker + systemctl restart docker + + usermod -aG docker ubuntu + + # GitHub Actions runner. + RUNNER_VERSION="{{ runner_version }}" + RUNNER_DIR=/home/ubuntu/actions-runner + mkdir -p "$RUNNER_DIR" + cd "$RUNNER_DIR" + curl -fsSL -o runner.tar.gz \ + "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" + tar xzf runner.tar.gz + rm runner.tar.gz + chown -R ubuntu:ubuntu "$RUNNER_DIR" + + # Run exactly one job under the ubuntu user. The JIT config is single-use + # and ephemeral, so run.sh exits after one job completes. + sudo -u ubuntu --preserve-env=HOME -H bash -c \ + "cd '$RUNNER_DIR' && ./run.sh --jitconfig '{{ encoded_jit_config }}'" \ + || echo "runner exited non-zero (job failure or runner error)" + + # Backstop: power down. The orchestrator's reap loop is the authoritative + # deleter — it sees the GitHub job complete and calls Nova delete. + shutdown -h +1 + +runcmd: + - /usr/local/bin/airstack-runner-bootstrap.sh diff --git a/AGENTS.md b/AGENTS.md index 7747d9100..49867886c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -111,7 +111,7 @@ For detailed step-by-step instructions, refer to the **`.agents/skills/`** direc | [add-behavior-tree-node](.agents/skills/add-behavior-tree-node) | Creating behavior tree nodes | | [use-airstack-cli](.agents/skills/use-airstack-cli) | Using the `airstack` CLI and the non-interactive `docker exec` pattern | | [configure-multi-robot](.agents/skills/configure-multi-robot) | Setting up multiple robots — fleet files (`--fleet`, heterogeneous + split placement) and legacy NUM_ROBOTS, ROBOT_NAME namespacing, ROS_DOMAIN_ID isolation | -| [bump-version-and-release](.agents/skills/bump-version-and-release) | Bumping `.env` VERSION and CHANGELOG before merge to clear the version-check gate | +| [bump-version-and-release](.agents/skills/bump-version-and-release) | Bumping `.env` VERSION and recording the change in the versioned Release Notes (docs/release_notes/index.md) before merge to clear the version-check gate | | [capture-discovered-knowledge](.agents/skills/capture-discovered-knowledge) | After long context-discovery / surprising findings, persist to AGENTS.md or a new skill so the next agent doesn't redo the work | | [use-feature-notebook](.agents/skills/use-feature-notebook) | At the start of EVERY feature implementation: create `notebook/NNN-feature-slug/design_spec.md`, store test artifacts under `results/`, write `results/results_summary.md`, and populate the PR from it | @@ -155,7 +155,7 @@ Study these well-structured modules as examples for different types: | **Controller** | Trajectory Controller | `robot/ros_ws/src/local/c_controls/trajectory_controller` | | **Global Planner** | Random Walk | `robot/ros_ws/src/global/planners/random_walk` | | **Global World Model** | VDB Mapping | `robot/ros_ws/src/global/world_models/vdb_mapping_ros2` | -| **Behavior** | Behavior Tree | `robot/ros_ws/src/behavior/behavior_tree` | +| **Behavior** | Drone Safety Monitor | `robot/ros_ws/src/behavior/drone_safety_monitor` | Each reference shows: - Package structure (CMakeLists.txt, package.xml, config, launch) diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 336fdd2e2..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,202 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -This release carries the **Modular AirStack campaign** -([RFC #379](https://github.com/castacks/AirStack/discussions/379) / -[RFC #380](https://github.com/castacks/AirStack/discussions/380)): the monolith -splits into **modules** (thin external repos pulled on demand), **stacks** -(self-contained topology folders under `stacks/`), and **fleets** -(`config/fleets/` — who exists, which vehicle, which stack, which ground hosts). - -### New repositories - -- [castacks/airstack-modules-index](https://github.com/castacks/airstack-modules-index) — the module/stack registry (one YAML per entry; DECLARED compat in the entry, VERIFIED compat CI-stamped under `compat/`) -- [castacks/asm_macvo](https://github.com/castacks/asm_macvo) — MAC-VO learned stereo visual odometry, extracted from trunk -- [castacks/asm_optitrack](https://github.com/castacks/asm_optitrack) — OptiTrack NatNet mocap integration (natnet_ros2 client, PX4 external-vision fusion, Motive-compatible NatNet server emulator for Isaac Sim), extracted from trunk -- [castacks/asm_dfm2_disturbances](https://github.com/castacks/asm_dfm2_disturbances) — Isaac Sim disturbance library (fan/vent force fields, strobe lights, lens flare) - -### Added - -- `airstack up --stack [:]` stack dispatch with **5 reference stacks** under `stacks/` (`full_default`, `full_droan_cpu`, `full_macvo`, `lite_default`, `lite_offload_global`), each carrying pinned `modules.repos` and a CI-observed `wiring.md` graph baseline -- Module CLI — `airstack module add --version ` (branches refused; local paths allowed), `list|sync|remove|create --in-tree|doctor`, workspace **overlay** of module packages, and auto-generated module compose overrides included by `airstack up`; Docker **module layers** composed via `modules.lock` (`airstack module lock --build`) -- Fleet system (RFC #380): `airstack up --fleet ` driven by `config/fleets/*.yaml` (identity, vehicle from `config/vehicles/`, stack selection, spawns) with `hosts:` split-stack placement onto ground hosts; `airstack fleet list|generate` per-robot compose for heterogeneous fleets; `airstack sync` reconciles `airstack.yaml` (modules, external stack repos, fleet validation) -- `airstack doctor [--live|--snapshot] [--stack NAME]` — observe-and-report checks with exactly **two hard gates** (module dependency-conflict gate; bridge gate: no control-setpoint / trajectory-group topics may cross a split-stack bridge); `--live` diffs the RUNNING ROS graph against the stack's committed `wiring.md` -- `airstack stack list|new |diff ` (diff compares generated wiring, not launch XML) — complementing `airstack ready` (below) -- `tests/meta/` contract-test tier (unit mark) pinning the CLI/docs/stack contracts, plus the `wiring` system-test mark: an observed wiring snapshot of the running graph drift-checked against the stack's committed `stacks//wiring.md` -- Split-stack bridging: `stacks/lite_offload_global/bridge.yaml` explicitly lists every boundary crossing and `tools/gen_dds_router.py` generates the DDS-router config from it deterministically (`--check` enforces bridge hard gate #2) -- New docs pages: [Modules](docs/development/modules.md), [Stacks](docs/development/stacks.md), [Fleets](docs/development/fleets.md), [Module CI](docs/development/module_ci.md), the generated [Module & Stack Catalog](docs/modules/index.md) marketplace, and the [Modular AirStack Walkthrough](docs/getting_started/modular_airstack.md) -- Intent flags on `airstack up` — `--sim isaac|airsim`, `--robots N`, `--headless`, `--play`/`--no-play`, `--no-autolaunch`, `--wait`, `--dry-run` — deriving the coordinated env-var sets (compose profiles, URDF, single/multi Isaac launch script) as exported leaf values, with a resolved-config banner and a per-run `.airstack/runs//effective_config.env` dump; contract-tested in `tests/meta/test_launch_intent_contract.py` (unit mark) -- `airstack ready` (and `airstack up --wait`): staged flight-readiness gates mirroring the system-test budgets — containers → sim `/clock` → per-robot sentinel nodes → PX4 MAVROS-connected + `local_position/odom` streaming (the armable signal) — with per-gate diagnostics and `--json` for scripts -- Preflight validation in `airstack up` on **resolved** configuration (env > `--env-file` > `.env`): one-simulator guard no longer bypassed by `--env-file`; `NUM_ROBOTS>1` with the single-drone Isaac script is a named hard error; missing images are listed with an `image-pull` hint before compose starts an implicit build; missing `omni_pass.env` / empty Pegasus submodule / Docker < 29 surfaced on the host (`AIRSTACK_SKIP_PREFLIGHT=1` downgrades errors to warnings) -- tmux pane output is mirrored to container stdout via shared `.tmux.conf` hooks, so `docker logs` / `airstack logs` now show colcon builds, `ros2 launch` output, sim loading, and crashes - -- Automatic `unit-tests.yml` PR gate on `ubuntu-latest`, plus `run_meta.json` outcome metadata so reports distinguish completed simulation campaigns from collection errors, empty selections, timeouts, and cancellations -- Feature notebook workflow (`use-feature-notebook` skill): every agent-implemented feature gets a local, gitignored `notebook/NNN-feature-slug/` entry with a status-tracked `design_spec.md` (written before coding) and `results/` artifacts + self-contained `results_summary.md` that populate the feature's PR description -- Battery and telemetry display in GCS RQT control panel (voltage and percentage per robot when MAVROS battery topic is bridged) -- `TARGET_ARCH` build arg (default `x86_64`) in `Dockerfile.robot` to arch-parametrize `LD_LIBRARY_PATH`; `docker-compose.yaml` passes `TARGET_ARCH: aarch64` to the `voxl` and `l4t` real-robot image builds -- `ros-${ROS_DISTRO}-mavros-extras` in the robot image (provides the vision_pose plugin used for external-pose deployments) -- `overrides/l4t-px4-realrobot.env` — site-agnostic deployment override for a single real PX4 robot on a Jetson (aarch64/l4t) -- `integration` test tier (`tests/integration/`, `integration` mark) with a shared `robot_autonomy_stack` fixture (robot container, no sim/GPU) -- `waypoint_flight` system test (`tests/system/test_waypoint_flight.py`): takeoff → ordered waypoint route via `NavigateTask` (dispatched as a dense plan) → land, judged on the odometry track by the standalone stdlib-only `tests/waypoint_checker.py` (in-order corridor arrival within `--waypoint-tolerance`, final goal within `--goal-tolerance`, per-waypoint `--waypoint-timeout`); validated end-to-end in Isaac Sim; serves as the standard acceptance check after integrating or swapping a planner module - -### Changed - -- **Stacks are the only launch path**: the autonomy topology is selected by `AIRSTACK_STACK_DIR`/`AIRSTACK_STACK_ENTRY` (exported by `airstack up --stack`), dispatched in `autonomy_bringup/launch/robot.launch.xml`. A set `AUTONOMY_ROLE` is now a **preflight hard error**; with no `--stack`, **`full_default`** is the default, machine-proven graph-identical to the old `AUTONOMY_ROLE=full` -- `robot-desktop` image slimmed **17.1 GB → 6.06 GB (−65%)** by moving MACVO's torch/TensorRT/weights into the `asm_macvo` module Docker layer — the composed image carries the 11.3 GB layer only for MACVO users -- Shared DDS-router configs moved out of the per-role `onboard_all/` tree up to `autonomy_bringup/config/`; the split-stack router config is now **generated** from the stack's `bridge.yaml` by `tools/gen_dds_router.py` (the generated config deliberately drops the legacy split's `set_trajectory_mode` crossing — doctor hard gate #2) -- Isaac launch scripts deduplicated onto a shared `pegasus_app.PegasusApp` base (`simulation/isaac-sim/launch_scripts/pegasus_app.py`): the six scripts become scenario declarations (~40–170 lines each, net −438 lines) with hooks for NatNet/scene-import extras; behavior verified by full system-test parity (liveliness, sensors, takeoff/hover/land on Isaac). `ISAAC_SIM_HEADLESS` and `ISAAC_SIM_LIVESTREAM` now work uniformly in **every** launch script (previously each was honored by only half of them) -- Launch-workflow docs corrected against actual behavior: `ISAAC_SIM_SCENE` (nonexistent) replaced by `ISAAC_SIM_SCRIPT_NAME`/`ISAAC_SIM_GUI`, getting-started reflects the paused-by-default sim and Foxglove UI, isaac docker.md defaults table matches `.env`, ms-airsim MAVROS ports/FOV/vehicle naming fixed, AGENTS.md uses the real `down`/`image-build` command names - -- Unit-test documentation now matches the co-located layout: the `add-unit-tests` and `run-system-tests` skills and the testing docs record which runner each language uses (C++ gtests via `colcon test` under the `build_packages` mark; Python via the root harness, plus `colcon test` for `ament_python` packages), and stop instructing authors to write `@pytest.mark.unit` by hand — `conftest.py` applies it by file location -- Ephemeral CI GPU runners spawn via NVIDIA OSMO (not OpenStack); `system-tests.yml` / `docker-build.yml` still use `airstack-ephemeral` -- Default system-test `--sim` is `isaacsim`; pass `--sim msairsim` to opt in to Microsoft AirSim -- `-m build_packages` CI runs pull `cache_*` images instead of baking sim images -- `docker-build.yml` retags unchanged images on VERSION bumps (content fingerprint) instead of always rebuilding; floating `cache_*` tags still seed PR layer cache -- Automatic OSMO validation runs the pull-only `build_packages` gate whenever a PR is opened, updated, or reopened; GPU-intensive simulation campaigns (including OptiTrack) are selected through `/pytest` or `workflow_dispatch` -- `robot-l4t` compose service knobs are now env-overridable (`AUTONOMY_ROLE` (removed on this branch — see below), `FCU_URL`, and the rosbag path via `BAG_STORAGE_PATH`); `FCU_URL` unquoted so the literal serial path reaches MAVROS -- `zed-l4t` image: ZED SDK 4.2 → 5.2 with the coupled ROS deps (`zed_msgs` 5.2.1, `point_cloud_transport(_plugins)` 4.x, add `backward_ros`) -- Unit tests are defined by `tests/colcon_unit_test_packages.yaml`: `conftest.py` collects each listed package's co-located `test/` dir under `--import-mode=importlib` and marks it `unit` (ament lint files are skipped and run under `colcon test`) - -### Removed - -- The legacy `AUTONOMY_ROLE` launch dispatch and its per-role launch trees (`onboard_all/`, `onboard_local_offboard_global/`) — stacks are the only launch path; a set `AUTONOMY_ROLE` is a preflight error -- **MACVO extracted from trunk** to [castacks/asm_macvo](https://github.com/castacks/asm_macvo) (install with `airstack module add`) -- **OptiTrack/NatNet extracted from trunk** to [castacks/asm_optitrack](https://github.com/castacks/asm_optitrack): the `natnet_ros2` client + PX4 external-vision fusion, the NatNet server emulator and its Isaac wrapper, the `optitrack` e2e test, the host integration tests, and the `isaac-optitrack-simulation.env` / `l4t-optitrack-realrobot.env` overrides all live in the module repo now -- The stillborn `ensemble_planner` skeleton -- The legacy Gazebo parallel-bringup tree (8 dead files under `exploration/launch/robot_launch_gazebo/`) -- `tests/goldens/wiring/` — wiring baselines now live per-stack as `stacks//wiring.md` -- Pre-co-location unit-test scaffolding: the six per-layer stub READMEs under `tests/robot/` (which instructed authors to add tests in directories tests no longer live in) and `tests/sim/motive_emulator/README.md` (superseded by `simulation/isaac-sim/extensions/optitrack.natnet.emulator/` and `tests/integration/natnet/`) - -### Fixed - -- `barebones_pegasus_launch.py` (the documented template script) crashed with `NameError: os` on construction -- `isaac-sim-livestream` compose service silently produced a black stream when `ISAAC_SIM_SCRIPT_NAME` was a multi-drone script (livestream setup existed only in the single-drone scripts) -- `NATNET_BODY_NAME`/`NATNET_TARGET_NAME` env overrides documented by the single-drone NatNet script now actually work -- `airstack up` guards (one-simulator, URDF pairing) validated `.env` only and were bypassed by `--env-file overrides/...`; they now check the resolved configuration -- `pytest tests/` now collects the co-located unit tests before mark filtering. The old guard skipped injection whenever any path was on the command line, and `tests/` is a path — CI collected 97 of 252 items and the Python unit tests ran nowhere. Narrowing (`pytest tests/system/test_x.py`) still skips injection; repository-root and empty-path collection are rejected -- Empty CI pytest arguments no longer become `pytest tests/ ""` and recurse through the repository; collection/import, setup/teardown, partial, and interrupted artifacts are reported as non-comparable instead of false 0% simulation-policy results, and metric regression runs only for an identical simulation campaign fingerprint -- Isaac Sim image: PX4 `ubuntu.sh` no longer fails dpkg configure on the NVIDIA base (`ca-certificates` / `software-properties-common`); use `--no-nuttx --no-sim-tools` like ms-airsim -- Robot image: pin `pytest<8.1` and disable `launch_testing` for colcon unit tests so ROS Jazzy's outdated pytest hook does not abort `colcon test` -- Robot name resolution now honors a pre-set `ROBOT_NAME` (e.g. injected via docker compose) instead of always overriding it from the container/hostname mapping (`robot/docker/.bashrc`) -- Robot name-map catch-all fallback now maps to `unknown_robot` (valid ROS namespace token) instead of `unknown-robot` (`default_robot_name_map.yaml`) -- l4t robot image: replace dustynv's `/ros_entrypoint.sh` with a passthrough so its prebuilt source-ROS libs (older `fastcdr`) no longer shadow the apt Jazzy runtime and crash apt-built nodes like MAVROS -- `RECORD_BAGS=true` never brought the bag recorder up on a robot: `logging.launch.xml` hardcoded `record_bag=false` and `onboard_autonomy_all.launch.xml` includes it with no arguments, so the variable was forwarded into the container and read by nobody (only `gcs.launch.xml` consumed it). With no `bag_record` node running, the GCS control panel's `set_recording_status` toggle had nothing to reach despite being bridged in `domain_bridge.yaml` / `dds_router.yaml`. It now reads `RECORD_BAGS` and selects its topic set via `LOG_CONFIG` -- Falling back to `unknown_robot` / domain 0 now logs a warning naming both fixes (rename the device `robot-` on the host, or supply a `ROBOT_NAME_MAP_CONFIG_FILE` matching your hostnames). The fallback itself is unchanged — it deliberately keeps an unidentified robot out of every real robot's namespace — but it used to resolve silently, so the symptoms surfaced far from the cause -- Dropped `ROBOT_NAME` / `ROS_DOMAIN_ID` from `overrides/l4t-px4-realrobot.env`: no compose service declares either, so an env file could never set them and the lines were inert -- `bag_record/bag_recording_status` was bridged GCS -> robot in `domain_bridge.yaml`, the same direction as the command it answers, so recorder status never reached the GCS and every recording indicator stayed blank -- `bag_record_node` passed `--exclude` to `ros2 bag record`, which Jazzy renamed to `--exclude-regex`. It is now an ambiguous prefix of four options, so argparse rejected the command and any section using `exclude:` (including `log.yaml`'s `airstack` section, i.e. everything but the cameras) recorded nothing — surfacing only as a usage dump in the node's stdout. Multiple `exclude:` entries are now alternated into one regex instead of repeating a single-valued flag, which had silently kept only the last -- `natnet_config.yaml`'s `$(env NATNET_SERVER_IP ...)` could never resolve: no compose service declared the variable, so the NatNet client always fell back to its hardcoded default and could reach neither the in-sim emulator nor a real Motive host. It is now forwarded in `robot-base-docker-compose.yaml`, defaulting to the in-sim emulator -- The NatNet rigid body tracked by `robot_1` defaulted to a site-specific body (id 1146) that no emulator streams; since the client filters frames by numeric id, that produced a connected client that never published. It now defaults to the emulator's body (`Drone`, id 1). Per-robot bodies are configured in each robot's profile in `natnet_config.yaml`, selected by `ROBOT_NAME` -- OptiTrack external-vision tuning corrected from real-flight bags: `EKF2_EV_DELAY` 8.0 → 7.0 and `EKF2_EVP_NOISE` 0.01 → 0.05. The old 0.01 gave a 5 cm innovation gate (`EKF2_EVP_GATE` × 5σ) that rejected valid mocap updates and blocked arming; `px4_params.yaml` now records the supporting measurements and the drift-and-snap misdiagnosis so neither is repeated -- The synthetic GPS origin now places the mocap floor at the shared world datum (`desired_floor_amsl: 36.0`, i.e. 90 m ellipsoidal in AMSL) rather than at sea level, so a mocap robot's reported global altitude agrees with sim and the GCS. `local_position.z` still equals the OptiTrack height either way -- The robot image could ship without the GeographicLib `egm96-5` geoid: mavros' `install_geographiclib_datasets.sh` swallows a failed download and still exits 0, so the `RUN` layer succeeded either way, and `geographiclib-tools` was only ever a transitive dependency. MAVROS builds that geoid in its UAS core before any plugin loads and throws if it is missing, so `mavros_node` died at startup on affected images. `Dockerfile.robot` now pins the tool and asserts the file exists, failing the build instead -- An unrecognised `connection_type` in `natnet_config.yaml` silently fell back to `unicast`, so a typo produced a client that connected on the wrong transport and never received frames. `validate_connection_type` now throws and `natnet_ros2_node` fails at startup naming the offending value -- `natnet_ros2_node` on `robot_1` now compares the NatNet server's MODELDEF drone-body count (`Drone` / `Drone1`…`DroneN`, excluding `Target` and skeleton bones) against `NUM_ROBOTS` after the handshake and logs an error on mismatch, so a sim launch script and `natnet_config.yaml` that disagree about how many drones exist is caught at startup rather than as a robot that silently never receives frames. `NUM_ROBOTS` is forwarded into the robot container for it -- Isaac Sim PX4 never fused the mocap stream: `EKF2_EV_CTRL` defaults to 0 and the isaac compose set no PX4 parameters, so the emulator could stream perfectly while PX4 flew on sim GPS. The compose now passes the EKF2 external-vision set as `PX4_PARAM_*` (applied by PX4 SITL's `rcS` at boot), each defaulting to PX4's own default so non-mocap sims are unaffected; the mocap path opts in -- The NatNet emulator hardcoded the drone's streaming id to 1 while the client reads `NATNET_BODY_ID`, so a real Motive id desynced the two into a connected client that never published (`example_one_px4_pegasus_natnet_launch_script.py`) -- `test_optitrack_e2e.py::test_px4_fuses_vision` asserted only that `local_position/pose` publishes, which it does off GPS — the check passed with external vision disabled. It is now the pre-flight gate (an estimate exists) and the Circle flight is the actual proof of fusion -- The NatNet emulator is now installed as a Kit extension: `Dockerfile.isaac-ros` pip-installs it editable into the Isaac python and bind-mounts the repo copy over it (the same pattern as `pegasus.simulator`), and the natnet launch scripts `enable_extension` it before importing. Being on a Kit `--ext-folder` search path only makes Kit *aware* of an extension — it does not put the package on `sys.path` — so the scripts previously died with `ModuleNotFoundError: No module named 'optitrack'` - -## [0.19.0] - 2026-08-22 - -### Added - -- Intent flags on `airstack up` — `--sim isaac|airsim`, `--robots N`, `--headless`, `--play`/`--no-play`, `--no-autolaunch`, `--wait`, `--dry-run` — deriving the coordinated env-var sets (compose profiles, URDF, single/multi Isaac launch script) as exported leaf values, with a resolved-config banner and a per-run `.airstack/runs//effective_config.env` dump; contract-tested in `tests/meta/test_launch_intent_contract.py` (unit mark) -- `airstack ready` (and `airstack up --wait`): staged flight-readiness gates mirroring the system-test budgets — containers → sim `/clock` → per-robot sentinel nodes → PX4 MAVROS-connected + `local_position/odom` streaming (the armable signal) — with per-gate diagnostics and `--json` for scripts -- Preflight validation in `airstack up` on **resolved** configuration (env > `--env-file` > `.env`): one-simulator guard no longer bypassed by `--env-file`; `NUM_ROBOTS>1` with the single-drone Isaac script is a named hard error; missing images are listed with an `image-pull` hint before compose starts an implicit build; missing `omni_pass.env` / empty Pegasus submodule / Docker < 29 surfaced on the host (`AIRSTACK_SKIP_PREFLIGHT=1` downgrades errors to warnings) -- tmux pane output is mirrored to container stdout via shared `.tmux.conf` hooks, so `docker logs` / `airstack logs` now show colcon builds, `ros2 launch` output, sim loading, and crashes - -- Automatic `unit-tests.yml` PR gate on `ubuntu-latest`, plus `run_meta.json` outcome metadata so reports distinguish completed simulation campaigns from collection errors, empty selections, timeouts, and cancellations -- `overrides/isaac-optitrack-simulation.env` — brings up Isaac Sim with the NatNet emulator and PX4 flying on mocap EKF2 external vision (GPS/baro/range aiding off), i.e. the configuration `tests/system/test_optitrack_e2e.py` runs, reproducible by hand -- `overrides/l4t-optitrack-realrobot.env` — deployment override for a real Jetson robot flying on OptiTrack mocap (PX4 EKF2 external vision instead of GPS): the NatNet server/body settings, plus the multi-NIC and FCU-parameter notes that path needs -- Feature notebook workflow (`use-feature-notebook` skill): every agent-implemented feature gets a local, gitignored `notebook/NNN-feature-slug/` entry with a status-tracked `design_spec.md` (written before coding) and `results/` artifacts + self-contained `results_summary.md` that populate the feature's PR description -- Battery and telemetry display in GCS RQT control panel (voltage and percentage per robot when MAVROS battery topic is bridged) -- `TARGET_ARCH` build arg (default `x86_64`) in `Dockerfile.robot` to arch-parametrize `LD_LIBRARY_PATH`; `docker-compose.yaml` passes `TARGET_ARCH: aarch64` to the `voxl` and `l4t` real-robot image builds -- `ros-${ROS_DISTRO}-mavros-extras` in the robot image (provides the vision_pose plugin used for external-pose deployments) -- `overrides/l4t-px4-realrobot.env` — site-agnostic deployment override for a single real PX4 robot on a Jetson (aarch64/l4t) -- `integration` test tier (`tests/integration/`, `integration` mark) with a shared `robot_autonomy_stack` fixture (robot container, no sim/GPU) -- `waypoint_flight` system test (`tests/system/test_waypoint_flight.py`): takeoff → ordered waypoint route via `NavigateTask` (dispatched as a dense plan) → land, judged on the odometry track by the standalone stdlib-only `tests/waypoint_checker.py` (in-order corridor arrival within `--waypoint-tolerance`, final goal within `--goal-tolerance`, per-waypoint `--waypoint-timeout`); validated end-to-end in Isaac Sim; serves as the standard acceptance check after integrating or swapping a planner module -- Real-robot PX4 external-vision fusion in `natnet_ros2` (OptiTrack mocap → EKF2): `mavros_gp_origin` (geoid-corrected synthetic GPS origin so `local_position.z` == OptiTrack z, fixing the ~36 m boot offset), `vision_pose_converter`, and a PX4 param **checker** (`px4_param_setter`, `auto_set` off by default; `on_mismatch` warn/halt) — setup guide at `docs/robot/px4_external_vision.md` -- NatNet server emulator (`optitrack.natnet.emulator`, protocol core) — pure-Python OptiTrack Motive server emulation so `natnet_ros2` can be driven without hardware; host integration tests (`tests/integration/natnet/`) wire it to the robot client -- Isaac wrapper for the NatNet emulator (USD scene → server) + natnet Pegasus launch scripts, and a dedicated OptiTrack sim e2e test (`optitrack` mark, `tests/system/test_optitrack_e2e.py`) that flies a **Circle trajectory on mocap EKF2 fusion** — GPS, baro and range aiding are disabled for the run, so the OptiTrack stream is the vehicle's only position source and cross-track error scores the whole chain - -### Changed - -- Isaac launch scripts deduplicated onto a shared `pegasus_app.PegasusApp` base (`simulation/isaac-sim/launch_scripts/pegasus_app.py`): the six scripts become scenario declarations (~40–170 lines each, net −438 lines) with hooks for NatNet/scene-import extras; behavior verified by full system-test parity (liveliness, sensors, takeoff/hover/land on Isaac). `ISAAC_SIM_HEADLESS` and `ISAAC_SIM_LIVESTREAM` now work uniformly in **every** launch script (previously each was honored by only half of them) -- Launch-workflow docs corrected against actual behavior: `ISAAC_SIM_SCENE` (nonexistent) replaced by `ISAAC_SIM_SCRIPT_NAME`/`ISAAC_SIM_GUI`, getting-started reflects the paused-by-default sim and Foxglove UI, isaac docker.md defaults table matches `.env`, ms-airsim MAVROS ports/FOV/vehicle naming fixed, AGENTS.md uses the real `down`/`image-build` command names - -- Unit-test documentation now matches the co-located layout: the `add-unit-tests` and `run-system-tests` skills and the testing docs record which runner each language uses (C++ gtests via `colcon test` under the `build_packages` mark; Python via the root harness, plus `colcon test` for `ament_python` packages), and stop instructing authors to write `@pytest.mark.unit` by hand — `conftest.py` applies it by file location -- Ephemeral CI GPU runners spawn via NVIDIA OSMO (not OpenStack); `system-tests.yml` / `docker-build.yml` still use `airstack-ephemeral` -- Default system-test `--sim` is `isaacsim`; pass `--sim msairsim` to opt in to Microsoft AirSim -- `-m build_packages` CI runs pull `cache_*` images instead of baking sim images -- `docker-build.yml` retags unchanged images on VERSION bumps (content fingerprint) instead of always rebuilding; floating `cache_*` tags still seed PR layer cache -- Automatic OSMO validation runs the pull-only `build_packages` gate whenever a PR is opened, updated, or reopened; GPU-intensive simulation campaigns (including OptiTrack) are selected through `/pytest` or `workflow_dispatch` -- `robot-l4t` compose service knobs are now env-overridable (`AUTONOMY_ROLE`, `FCU_URL`, and the rosbag path via `BAG_STORAGE_PATH`); `FCU_URL` unquoted so the literal serial path reaches MAVROS -- `zed-l4t` image: ZED SDK 4.2 → 5.2 with the coupled ROS deps (`zed_msgs` 5.2.1, `point_cloud_transport(_plugins)` 4.x, add `backward_ros`) -- Unit tests are defined by `tests/colcon_unit_test_packages.yaml`: `conftest.py` collects each listed package's co-located `test/` dir under `--import-mode=importlib` and marks it `unit` (ament lint files are skipped and run under `colcon test`) - -### Removed - -- Pre-co-location unit-test scaffolding: the six per-layer stub READMEs under `tests/robot/` (which instructed authors to add tests in directories tests no longer live in) and `tests/sim/motive_emulator/README.md` (superseded by `simulation/isaac-sim/extensions/optitrack.natnet.emulator/` and `tests/integration/natnet/`) - -### Fixed - -- `barebones_pegasus_launch.py` (the documented template script) crashed with `NameError: os` on construction -- `isaac-sim-livestream` compose service silently produced a black stream when `ISAAC_SIM_SCRIPT_NAME` was a multi-drone script (livestream setup existed only in the single-drone scripts) -- `NATNET_BODY_NAME`/`NATNET_TARGET_NAME` env overrides documented by the single-drone NatNet script now actually work -- `airstack up` guards (one-simulator, URDF pairing) validated `.env` only and were bypassed by `--env-file overrides/...`; they now check the resolved configuration -- `pytest tests/` now collects the co-located unit tests before mark filtering. The old guard skipped injection whenever any path was on the command line, and `tests/` is a path — CI collected 97 of 252 items and the Python unit tests ran nowhere. Narrowing (`pytest tests/system/test_x.py`) still skips injection; repository-root and empty-path collection are rejected -- Empty CI pytest arguments no longer become `pytest tests/ ""` and recurse through the repository; collection/import, setup/teardown, partial, and interrupted artifacts are reported as non-comparable instead of false 0% simulation-policy results, and metric regression runs only for an identical simulation campaign fingerprint -- Isaac Sim image: PX4 `ubuntu.sh` no longer fails dpkg configure on the NVIDIA base (`ca-certificates` / `software-properties-common`); use `--no-nuttx --no-sim-tools` like ms-airsim -- Robot image: pin `pytest<8.1` and disable `launch_testing` for colcon unit tests so ROS Jazzy's outdated pytest hook does not abort `colcon test` -- Robot name resolution now honors a pre-set `ROBOT_NAME` (e.g. injected via docker compose) instead of always overriding it from the container/hostname mapping (`robot/docker/.bashrc`) -- Robot name-map catch-all fallback now maps to `unknown_robot` (valid ROS namespace token) instead of `unknown-robot` (`default_robot_name_map.yaml`) -- l4t robot image: replace dustynv's `/ros_entrypoint.sh` with a passthrough so its prebuilt source-ROS libs (older `fastcdr`) no longer shadow the apt Jazzy runtime and crash apt-built nodes like MAVROS -- `RECORD_BAGS=true` never brought the bag recorder up on a robot: `logging.launch.xml` hardcoded `record_bag=false` and `onboard_autonomy_all.launch.xml` includes it with no arguments, so the variable was forwarded into the container and read by nobody (only `gcs.launch.xml` consumed it). With no `bag_record` node running, the GCS control panel's `set_recording_status` toggle had nothing to reach despite being bridged in `domain_bridge.yaml` / `dds_router.yaml`. It now reads `RECORD_BAGS` and selects its topic set via `LOG_CONFIG` -- Falling back to `unknown_robot` / domain 0 now logs a warning naming both fixes (rename the device `robot-` on the host, or supply a `ROBOT_NAME_MAP_CONFIG_FILE` matching your hostnames). The fallback itself is unchanged — it deliberately keeps an unidentified robot out of every real robot's namespace — but it used to resolve silently, so the symptoms surfaced far from the cause -- Dropped `ROBOT_NAME` / `ROS_DOMAIN_ID` from `overrides/l4t-px4-realrobot.env`: no compose service declares either, so an env file could never set them and the lines were inert -- `bag_record/bag_recording_status` was bridged GCS -> robot in `domain_bridge.yaml`, the same direction as the command it answers, so recorder status never reached the GCS and every recording indicator stayed blank -- `bag_record_node` passed `--exclude` to `ros2 bag record`, which Jazzy renamed to `--exclude-regex`. It is now an ambiguous prefix of four options, so argparse rejected the command and any section using `exclude:` (including `log.yaml`'s `airstack` section, i.e. everything but the cameras) recorded nothing — surfacing only as a usage dump in the node's stdout. Multiple `exclude:` entries are now alternated into one regex instead of repeating a single-valued flag, which had silently kept only the last -- `natnet_config.yaml`'s `$(env NATNET_SERVER_IP ...)` could never resolve: no compose service declared the variable, so the NatNet client always fell back to its hardcoded default and could reach neither the in-sim emulator nor a real Motive host. It is now forwarded in `robot-base-docker-compose.yaml`, defaulting to the in-sim emulator -- The NatNet rigid body tracked by `robot_1` defaulted to a site-specific body (id 1146) that no emulator streams; since the client filters frames by numeric id, that produced a connected client that never published. It now defaults to the emulator's body (`Drone`, id 1). Per-robot bodies are configured in each robot's profile in `natnet_config.yaml`, selected by `ROBOT_NAME` -- OptiTrack external-vision tuning corrected from real-flight bags: `EKF2_EV_DELAY` 8.0 → 7.0 and `EKF2_EVP_NOISE` 0.01 → 0.05. The old 0.01 gave a 5 cm innovation gate (`EKF2_EVP_GATE` × 5σ) that rejected valid mocap updates and blocked arming; `px4_params.yaml` now records the supporting measurements and the drift-and-snap misdiagnosis so neither is repeated -- The synthetic GPS origin now places the mocap floor at the shared world datum (`desired_floor_amsl: 36.0`, i.e. 90 m ellipsoidal in AMSL) rather than at sea level, so a mocap robot's reported global altitude agrees with sim and the GCS. `local_position.z` still equals the OptiTrack height either way -- The robot image could ship without the GeographicLib `egm96-5` geoid: mavros' `install_geographiclib_datasets.sh` swallows a failed download and still exits 0, so the `RUN` layer succeeded either way, and `geographiclib-tools` was only ever a transitive dependency. MAVROS builds that geoid in its UAS core before any plugin loads and throws if it is missing, so `mavros_node` died at startup on affected images. `Dockerfile.robot` now pins the tool and asserts the file exists, failing the build instead -- An unrecognised `connection_type` in `natnet_config.yaml` silently fell back to `unicast`, so a typo produced a client that connected on the wrong transport and never received frames. `validate_connection_type` now throws and `natnet_ros2_node` fails at startup naming the offending value -- `natnet_ros2_node` on `robot_1` now compares the NatNet server's MODELDEF drone-body count (`Drone` / `Drone1`…`DroneN`, excluding `Target` and skeleton bones) against `NUM_ROBOTS` after the handshake and logs an error on mismatch, so a sim launch script and `natnet_config.yaml` that disagree about how many drones exist is caught at startup rather than as a robot that silently never receives frames. `NUM_ROBOTS` is forwarded into the robot container for it -- Isaac Sim PX4 never fused the mocap stream: `EKF2_EV_CTRL` defaults to 0 and the isaac compose set no PX4 parameters, so the emulator could stream perfectly while PX4 flew on sim GPS. The compose now passes the EKF2 external-vision set as `PX4_PARAM_*` (applied by PX4 SITL's `rcS` at boot), each defaulting to PX4's own default so non-mocap sims are unaffected; the mocap path opts in -- The NatNet emulator hardcoded the drone's streaming id to 1 while the client reads `NATNET_BODY_ID`, so a real Motive id desynced the two into a connected client that never published (`example_one_px4_pegasus_natnet_launch_script.py`) -- `test_optitrack_e2e.py::test_px4_fuses_vision` asserted only that `local_position/pose` publishes, which it does off GPS — the check passed with external vision disabled. It is now the pre-flight gate (an estimate exists) and the Circle flight is the actual proof of fusion -- The NatNet emulator is now installed as a Kit extension: `Dockerfile.isaac-ros` pip-installs it editable into the Isaac python and bind-mounts the repo copy over it (the same pattern as `pegasus.simulator`), and the natnet launch scripts `enable_extension` it before importing. Being on a Kit `--ext-folder` search path only makes Kit *aware* of an extension — it does not put the package on `sys.path` — so the scripts previously died with `ModuleNotFoundError: No module named 'optitrack'` - -## [1.0.0] - 2024-12-19 - -First official public release. - -### Added - -- Docker image robot-l4t for Jetson AGX -- Automatically load and play Isaac Sim scene upon launch -- Random walk planner -- DROAN trajectory-library based local planner -- Initial GCS rviz capable of visualizing multiple robots - -### Fixed - -- A bunch of stuff honestly - -### Changed - -- Upgrade Isaac Sim from 4.1.0 to 4.2.0 -- Unified docker image naming to use AirStack's version. -- Condensed GCS TAK docker images to single docker image - -### Removed - -- Duplicate TAK images diff --git a/README.md b/README.md index 16db13685..f1fd0d816 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ and hardware deployment tools — all running in Docker. ## Modular architecture -AirStack is organized around three concepts ([RFC #379](https://github.com/castacks/AirStack/discussions/379) / [RFC #380](https://github.com/castacks/AirStack/discussions/380)): +AirStack is organized around three concepts: **modules** — thin external repos with a small `module.yaml`, pulled on demand (`airstack module add --version `) and discovered through the [module registry](https://github.com/castacks/airstack-modules-index); diff --git a/common/ros_packages/logging/bag_recorder_pid/README.md b/common/ros_packages/logging/bag_recorder_pid/README.md index 30979753c..a24afee2c 100644 --- a/common/ros_packages/logging/bag_recorder_pid/README.md +++ b/common/ros_packages/logging/bag_recorder_pid/README.md @@ -60,22 +60,24 @@ ros2 launch bag_record_pid bag_record_pid_namespaced.launch.py \ ### Controlling Recording +The control topics are named after the node (`//...`). The node's default name is `bag_record_node`; in the AirStack stacks it is launched as `bag_record` under the robot namespace, so the topics are `/{robot_name}/bag_record/...`. + Start recording: ```bash -ros2 topic pub --once /bag_record_pid/set_recording_status std_msgs/msg/Bool "{data: true}" +ros2 topic pub --once /bag_record_node/set_recording_status std_msgs/msg/Bool "{data: true}" ``` Stop recording: ```bash -ros2 topic pub --once /bag_record_pid/set_recording_status std_msgs/msg/Bool "{data: false}" +ros2 topic pub --once /bag_record_node/set_recording_status std_msgs/msg/Bool "{data: false}" ``` Check recording status: ```bash -ros2 topic echo /bag_record_pid/bag_recording_status +ros2 topic echo /bag_record_node/bag_recording_status ``` ## Configuration @@ -249,11 +251,11 @@ All parameters from `bag_record_pid.launch.py` plus: ### Subscribed -- `/bag_record_pid/set_recording_status` (`std_msgs/Bool`): Control recording on/off +- `//set_recording_status` (`std_msgs/Bool`): Control recording on/off ### Published -- `/bag_record_pid/bag_recording_status` (`std_msgs/Bool`): Current recording status (published at 2Hz) +- `//bag_recording_status` (`std_msgs/Bool`): Current recording status (published at 2Hz) ## Output Files @@ -297,12 +299,12 @@ class BagController(Node): super().__init__('bag_controller') self.pub = self.create_publisher( Bool, - '/bag_record_pid/set_recording_status', + '/bag_record_node/set_recording_status', 10 ) self.status_sub = self.create_subscription( Bool, - '/bag_record_pid/bag_recording_status', + '/bag_record_node/bag_recording_status', self.status_callback, 10 ) @@ -334,10 +336,10 @@ class BagController : public rclcpp::Node { public: BagController() : Node("bag_controller") { publisher_ = this->create_publisher( - "/bag_record_pid/set_recording_status", 10); + "/bag_record_node/set_recording_status", 10); subscription_ = this->create_subscription( - "/bag_record_pid/bag_recording_status", 10, + "/bag_record_node/bag_recording_status", 10, std::bind(&BagController::status_callback, this, std::placeholders::_1)); } @@ -369,15 +371,6 @@ private: ## Troubleshooting -### Output Directory Does Not Exist - -**Error**: "Output directory /logging does not exist" - -**Solution**: Ensure the output directory exists before launching: -```bash -mkdir -p /logging -``` - ### Topics Not Being Recorded **Issue**: Specified topics aren't appearing in bag files @@ -386,7 +379,7 @@ mkdir -p /logging 1. Verify topics exist: `ros2 topic list` 2. Check topic names in config (relative vs absolute paths) 3. Verify namespace settings if using namespaced launch -4. Ensure recording is started: `ros2 topic echo /bag_record_pid/bag_recording_status` +4. Ensure recording is started: `ros2 topic echo /bag_record_node/bag_recording_status` ### Bag Files Too Large @@ -456,6 +449,6 @@ Email: mmayank74567@gmail.com ## See Also -- [ROS2 Bag Documentation](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html) +- [ROS 2 Bag Documentation](https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html) - [MCAP Format](https://mcap.dev/) -- Airstack Documentation: [docs/](../../docs/) +- AirStack Documentation: [docs/robot/logging](../../../../docs/robot/logging/index.md) diff --git a/docs/README.md b/docs/README.md index 75b2e2eaa..b4d60cf94 100644 --- a/docs/README.md +++ b/docs/README.md @@ -54,7 +54,7 @@ Robot └── Behavior Layer: High-level decision making ``` -Capabilities beyond the trunk live in **modules** — thin external repos pulled on demand (`airstack module add --version `) and listed in the [module registry](https://github.com/castacks/airstack-modules-index) — and multi-robot deployments are declared by **fleet files** under `config/fleets/` (see [RFC #379](https://github.com/castacks/AirStack/discussions/379) / [RFC #380](https://github.com/castacks/AirStack/discussions/380)). +Capabilities beyond the trunk live in **modules** — thin external repos pulled on demand (`airstack module add --version `) and listed in the [module registry](https://github.com/castacks/airstack-modules-index) — and multi-robot deployments are declared by **fleet files** under `config/fleets/`. ## 📁 Repository Structure diff --git a/docs/about.md b/docs/about.md index efe8fea2a..dbb58c335 100644 --- a/docs/about.md +++ b/docs/about.md @@ -102,9 +102,9 @@ See: [System Architecture](robot/autonomy/system_architecture.md) See: [Autonomy Modes Tutorial](robot/autonomy_modes.md) !!! question "Can I use my own planner/controller?" - Yes! AirStack is designed for easy module swapping. Create a ROS 2 package with the correct interface, integrate it into the bringup layer, and you're ready. + Yes! AirStack is designed for easy module swapping. Create a ROS 2 package with the correct interface, wire it into a stack's entry launch file, and you're ready. - See: [Integration Checklist](robot/autonomy/integration_checklist.md) + See: [Integration Checklist](robot/autonomy/integration_checklist.md) and [AirStack Stacks](development/stacks.md) !!! question "How do I add a new sensor?" Add sensor integration to the sensors layer, ensuring data is published to standard topics (with correct frame IDs and timestamps). diff --git a/docs/development/advanced/ai_agent_guide.md b/docs/development/advanced/ai_agent_guide.md index 3fce3223b..90c08428f 100644 --- a/docs/development/advanced/ai_agent_guide.md +++ b/docs/development/advanced/ai_agent_guide.md @@ -25,7 +25,7 @@ This guide provides a concise reference for AI agents working with AirStack. For graph LR Study[Study Reference
Implementation] --> Create[Create Package
add-ros2-package] Create --> Implement[Implement
Algorithm] - Implement --> Integrate[Integrate to Layer
integrate-module-into-layer] + Implement --> Integrate[Integrate into Stack
integrate-module-into-layer] Integrate --> Document[Document
update-documentation] Document --> Test[Test in Sim
test-in-simulation] Test --> Debug{Works?} @@ -105,10 +105,10 @@ Quick checklist when adding a module: - [ ] Package in correct layer directory - [ ] `package.xml` with all dependencies - [ ] CMakeLists.txt / setup.py configured -- [ ] Launch file with topic remapping +- [ ] Launch file with topic args (canonical defaults, no ``) - [ ] Config file with parameters - [ ] README.md with documentation -- [ ] Added to layer bringup +- [ ] Included in the stack's entry launch file (`stacks//launch/`) - [ ] Added to mkdocs.yml navigation - [ ] Tested standalone - [ ] Tested in full stack @@ -126,7 +126,7 @@ Study these before implementing similar modules: | Controller | Trajectory Controller | `robot/ros_ws/src/local/c_controls/trajectory_controller` | | World Model | Disparity Expansion | `robot/ros_ws/src/local/world_models/disparity_expansion` | | Global Planner | Random Walk | `robot/ros_ws/src/global/planners/random_walk` | -| Behavior Node | Example Actions | `robot/ros_ws/src/behavior/behavior_tree_example` | +| Behavior / Safety | Drone Safety Monitor | `robot/ros_ws/src/behavior/drone_safety_monitor` | ## Autonomous Debugging Strategy @@ -200,8 +200,8 @@ Full documentation workflow: [update-documentation skill](../../../.agents/skill - ✅ Use launch arguments for all topics ### Integration Issues -- ❌ Not adding module to bringup package -- ❌ Not updating bringup `package.xml` dependencies +- ❌ Not including the module in the stack's entry launch file +- ❌ Putting `` in the module launch file instead of the stack file - ✅ Follow [integrate-module-into-layer](../../../.agents/skills/integrate-module-into-layer) ### Documentation Issues diff --git a/docs/development/beginner/airstack-cli/docker_usage.md b/docs/development/beginner/airstack-cli/docker_usage.md index 2b31f6cb8..ed3319771 100644 --- a/docs/development/beginner/airstack-cli/docker_usage.md +++ b/docs/development/beginner/airstack-cli/docker_usage.md @@ -103,7 +103,7 @@ airstack ready # staged progress, per-gate diagnostics airstack ready --json # machine-readable (last line), exit 0 when ready ``` -The previous `docker compose up` launches robot_bringup in a tmux session. To attach to the session within the docker container, e.g. to inspect output, run `tmux a`. +With `AUTOLAUNCH` enabled (the default), each robot container launches the autonomy stack in a tmux session. To attach to the session within the docker container, e.g. to inspect output, run `tmux a`. The following commands are available within the robot container: @@ -113,10 +113,10 @@ cws # cleans workspace bws # builds workspace bws --packages-select [your_packages] # builds only desired packages sws # sources workspace -ros2 launch robot_bringup robot.launch.xml # top-level launch +ros2 launch autonomy_bringup robot.launch.xml # top-level launch (dispatches the selected stack) ``` -These aliases are defined in `AirStack/robot/.bashrc`. +These aliases are defined in `AirStack/robot/docker/.bashrc`. Each robot has `ROS_DOMAIN_ID` set to its ID number. `ROBOT_NAME` is set to `robot_$ROS_DOMAIN_ID`. @@ -138,7 +138,7 @@ On the GCS `ROS_DOMAIN_ID` is set to 0. The containers mimic the robots' onboard computers on the same network. Therefore we intend to interface with the robots through ssh. -The `gcs` and `docker-robot-*` containers are setup with ssh daemon, so you can ssh into the containers using the IP address. +The `gcs` and robot containers are set up with an ssh daemon, so you can ssh into the containers using the IP address. You can get the IP address of each container by running the following command: diff --git a/docs/development/beginner/airstack-cli/index.md b/docs/development/beginner/airstack-cli/index.md index f6711d017..2c6f0aa13 100644 --- a/docs/development/beginner/airstack-cli/index.md +++ b/docs/development/beginner/airstack-cli/index.md @@ -66,10 +66,10 @@ All commands, grouped by area. Run `airstack help ` for full options. | Command | Description | |---------|-------------| -| `module` | Manage AirStack modules: add\|remove\|list\|sync\|create\|lock\|doctor (RFC #379; see `airstack help module`) | -| `stack` | Manage stack folders: list\|new\|diff (RFC #379 §3; see `airstack help stack`) | -| `fleet` | Manage fleet files: list\|generate (RFC #380 §2; see `airstack help fleet`) | -| `sync` | Sync the checkout from airstack.yaml: modules, external stack repos, fleet validation (RFC #380 §3) | +| `module` | Manage AirStack modules: add\|remove\|list\|sync\|create\|lock\|doctor (see `airstack help module`) | +| `stack` | Manage stack folders: list\|new\|diff (see `airstack help stack`) | +| `fleet` | Manage fleet files: list\|generate (see `airstack help fleet`) | +| `sync` | Sync the checkout from airstack.yaml: modules, external stack repos, fleet validation | See the [Modular AirStack Walkthrough](../../../getting_started/modular_airstack.md) and the guides for [Modules](../../modules.md), [Stacks](../../stacks.md), and [Fleets](../../fleets.md). @@ -77,7 +77,7 @@ See the [Modular AirStack Walkthrough](../../../getting_started/modular_airstack | Command | Description | |---------|-------------| -| `doctor` | Observe-and-report health checks: compose-time gates, `--live` wiring drift, `--snapshot` (RFC #379 §4) | +| `doctor` | Observe-and-report health checks: compose-time gates, `--live` wiring drift, `--snapshot` | | `ready` | Wait until the running stack is flight-ready (containers → sim clock → nodes → PX4); `--json` for scripts | | `version` | Display the current AirStack version | | `help` | Show help information | @@ -87,10 +87,9 @@ See the [Modular AirStack Walkthrough](../../../getting_started/modular_airstack | Command | Description | |---------|-------------| -| `test` | Run tests (options: `--path=PATH`, `--filter=PATTERN`) | +| `test` | Run pytest in the containerized test runner (all args forward to pytest; see `airstack help test`) | | `docs` | Build documentation (options: serve) | -| `lint` | Lint code | -| `format` | Format code | +| `lint` | Static checks: tests/meta contract suite, `bash -n` over the CLI, `py_compile` over `tools/` | ### Remote Development (OSMO) @@ -110,7 +109,7 @@ See the [Modular AirStack Walkthrough](../../../getting_started/modular_airstack | Flag | Description | |------|-------------| -| `--sim isaac\|airsim` | Pick the simulator: swaps in the matching compose profile (`isaac-sim` / `ms-airsim`) and selects the matching URDF | +| `--sim isaac\|airsim\|simple` | Pick the simulator: swaps in the matching compose profile (`isaac-sim` / `ms-airsim`) and selects the matching URDF; `simple` launches the lightweight kinematic simple-sim (no PX4/MAVROS) with the `simple-robot` service in place of `robot-desktop` | | `--robots N` | Number of robots (positive integer): exports `NUM_ROBOTS=N` and, on Isaac Sim, auto-selects the single-/multi-drone launch script to match. Mutually exclusive with `--fleet` (the fleet file defines the robot count) | | `--headless` | Run the simulator without a GUI (sets `ISAAC_SIM_HEADLESS`, `MS_AIRSIM_HEADLESS`, offscreen Qt) | | `--play` / `--no-play` | Start (or don't start) simulation playback automatically (`PLAY_SIM_ON_START`) | diff --git a/docs/development/beginner/development_environment.md b/docs/development/beginner/development_environment.md index 341ea5f98..88a178bc2 100644 --- a/docs/development/beginner/development_environment.md +++ b/docs/development/beginner/development_environment.md @@ -56,14 +56,14 @@ sws # Source workspace (alias for source install/setup.bash) cws # Clean workspace (removes build/install/log) ``` -These are defined in `robot/.bashrc` and available in all robot containers. +These are defined in `robot/docker/.bashrc` and available in all robot containers. ## Development Tips **Quick iteration:** ```bash # Start without auto-launch -AUTOLAUNCH=false airstack up robot-desktop-desktop +AUTOLAUNCH=false airstack up robot-desktop # In another terminal, build and test docker exec airstack-robot-desktop-1 bash -c "bws --packages-select my_package && sws && ros2 launch my_package test.launch.xml" diff --git a/docs/development/beginner/key_concepts.md b/docs/development/beginner/key_concepts.md index 8e5f59dc1..288c5aad1 100644 --- a/docs/development/beginner/key_concepts.md +++ b/docs/development/beginner/key_concepts.md @@ -93,7 +93,7 @@ This modular design means you can: ## Stacks, Modules, and Fleets -Beyond containers, AirStack organizes the *autonomy software itself* into three composable concepts (RFC #379/#380): +Beyond containers, AirStack organizes the *autonomy software itself* into three composable concepts: **Modules** are individual capabilities packaged as thin external repos — a planner, a sensor driver, an Isaac Sim scene library — each carrying a small `module.yaml` manifest. You pull them on demand with `airstack module add --version ` (always pinned to a tag or SHA, never a branch); the CLI validates the manifest, mounts the module into the right containers, and `airstack module list` shows what you have. **Learn more:** [AirStack Modules](../modules.md) diff --git a/docs/development/development_environment.md b/docs/development/development_environment.md index 005d101eb..92caaf584 100644 --- a/docs/development/development_environment.md +++ b/docs/development/development_environment.md @@ -86,7 +86,7 @@ For frequent rebuilds during development: airstack connect airstack-robot-desktop-1 # Inside container -cd /opt/ros_ws +cd /root/AirStack/robot/ros_ws bws --packages-select my_package sws ros2 launch my_package my_launch.xml diff --git a/docs/development/fleets.md b/docs/development/fleets.md index bedc920f4..340a7f3e5 100644 --- a/docs/development/fleets.md +++ b/docs/development/fleets.md @@ -3,14 +3,15 @@ A **fleet file** (`config/fleets/*.yaml`) declares a whole deployment in one readable document: who exists, which body each robot flies (vehicle), which brain it runs (stack), and which ground hosts run each split stack's offboard -half ([RFC #380 §2](https://github.com/castacks/AirStack/discussions/380)). +half. Fleets exist to separate *who exists* from *how each robot flies* — +the stack owns topology, the fleet owns identity and placement. The same file drives simulation (spawn positions, scene) and hardware (identity, placement) — sim vs. real is a deployment mode of one artifact. **Everything here is opt-in.** No `--fleet` flag and no `FLEET_CONFIG_FILE` -env var ⇒ behavior is byte-identical to the legacy path -(`NUM_ROBOTS` + `robot_name_map` + `ISAAC_SIM_SCRIPT_NAME`), which remains -the default and fully supported. +env var ⇒ the replica-based multi-robot configuration +(`NUM_ROBOTS` + `robot_name_map` + `ISAAC_SIM_SCRIPT_NAME`) runs instead — +it remains the default and fully supported. ## The hierarchy @@ -23,7 +24,7 @@ platform class → vehicle type → vehicle unit → robot instance - **Platform class** — code: interface, controller, safety behaviors. `px4_multirotor` is the only platform today; platform *modules* are - RFC #380 Part 2 (future work). + future work. - **Vehicle type** — data: `config/vehicles//vehicle.yaml` (URDF, sensor suite with each sensor's real driver + sim representation declared together, sim asset). See @@ -38,8 +39,8 @@ platform class → vehicle type → vehicle unit → robot instance ## File tour `config/fleets/sim_one_default.yaml` — today's default checkout as a fleet -(one `quad_default` on `full_default`; resolves identically to the legacy -path — a contract test pins the parity): +(one `quad_default` on `full_default`; resolves identically to the +replica-based default — a contract test pins the parity): ```yaml defaults: {vehicle: quad_default, stack: stacks/full_default} @@ -75,7 +76,7 @@ different stack folder — that division keeps fleet files skimmable. A fleet's `stack:` values resolve first as checkout paths (`stacks/...`), then as `/` against external stack repos declared in `airstack.yaml` and fetched by `airstack sync` into gitignored -`stacks/.external//` (RFC #380 §3). +`stacks/.external//`. ## Running a fleet @@ -115,16 +116,17 @@ a fleet is selected. `AIRSTACK_STACK_DIR`, `AIRSTACK_STACK_ENTRY`, `URDF_FILE`, `VEHICLE`, `CALIBRATION_DIR`. Identity comes from the container name / hostname (exact robot key, else the trailing replica index — the same convention as - the legacy map). Pre-set env still wins per variable: an explicit - `ROBOT_NAME` skips resolution entirely (heterogeneous-fleet services set it - explicitly), and non-empty `ROS_DOMAIN_ID` / `AIRSTACK_STACK_DIR` / - `URDF_FILE` keep their values. Resolution failure warns and falls back to - the legacy resolver. -- **Unset/empty** → the legacy `robot_name_map` resolver runs, untouched. + the `robot_name_map` resolver). Pre-set env still wins per variable: an + explicit `ROBOT_NAME` skips resolution entirely (heterogeneous-fleet + services set it explicitly), and non-empty `ROS_DOMAIN_ID` / + `AIRSTACK_STACK_DIR` / `URDF_FILE` keep their values. Resolution failure + warns and falls back to the `robot_name_map` resolver. +- **Unset/empty** → the `robot_name_map` resolver runs, exactly as with no + fleet. `network.domain_policy: auto` (the only implemented policy) assigns robot N -(1-based file order) → domain N — today's rule, byte-compatible with the -legacy resolver for `robot_1..robot_N` fleets. +(1-based file order) → domain N — the same rule the `robot_name_map` +resolver applies for `robot_1..robot_N` fleets. ## Homogeneous vs. heterogeneous @@ -137,7 +139,7 @@ legacy resolver for `robot_1..robot_N` fleets. ## Split placement (`hosts:`) -A **split is a stack shape** ([stacks guide](stacks.md#split-stacks-and-bridgeyaml-rfc-380-2)): +A **split is a stack shape** ([stacks guide](stacks.md#split-stacks-and-bridgeyaml)): multiple launch entry points plus a `bridge.yaml`. The fleet decides *where each half runs*: @@ -154,14 +156,13 @@ ground: - The ground host `gcs` gets a generated service (`gcs-robot_3`) running the **same stack** with `AIRSTACK_STACK_ENTRY=offboard`, `ROBOT_NAME=robot_3` (the tenant it serves), and `ROS_DOMAIN_ID` = the fleet's `gcs_domain` - (default 0) — mirroring the legacy `robot-offboard` service. + (default 0) — the same shape as the `robot-offboard` compose service. - Every `hosts:` role must match an entry-point launch file of the robot's stack, and every named host must exist under `ground:` — both are named validation errors. Doctor's bridge hard-gate (no control-setpoint / trajectory-group names in any `bridge.yaml`) holds unchanged for every split stack a fleet places. -- The removed legacy `AUTONOMY_ROLE` never enters: the entry point *is* the - role. +- There is no separate role variable: the entry point *is* the role. ## Simulation @@ -199,17 +200,17 @@ bare `{version: ...}` module pins against a registry, and deriving the launch-time fleet default from `airstack.yaml` (select fleets explicitly with `--fleet`). -## Migration table (RFC #380 §3) +## Environment variables under a fleet -| Legacy env var | Under a fleet | Status | +| Env var | Under a fleet | Precedence | |---|---|---| | `NUM_ROBOTS` | **Derived** from the fleet's robot count | explicit env still wins (banner) | -| `ROBOT_NAME_MAP_CONFIG_FILE` | Absorbed: identity resolves from the fleet entry | legacy resolver remains the no-fleet default | -| `AUTONOMY_ROLE` | **Removed** — the stack entry point *is* the role, derived from `hosts:` | no-fleet/no-stack default is `stacks/full_default`; a set `AUTONOMY_ROLE` is a preflight error | +| `ROBOT_NAME_MAP_CONFIG_FILE` | Absorbed: identity resolves from the fleet entry | the `robot_name_map` resolver remains the no-fleet default | +| `AUTONOMY_ROLE` | Not a launch input — the stack entry point *is* the role, derived from `hosts:` | no-fleet/no-stack default is `stacks/full_default`; a set `AUTONOMY_ROLE` is a preflight error | | `URDF_FILE` | From the vehicle's `airframe.base_urdf` (pass-through form; xacro generation is future) | explicit env still wins | | `ISAAC_SIM_SCRIPT_NAME` | **Derived**: the generic fleet spawner | explicit env still wins | | `ROBOT_NAME` / `ROS_DOMAIN_ID` | Resolved per robot (`domain_policy: auto` = robot N → domain N) | pre-set env still wins | -| `VERSION` | Unchanged (`release:` is informational until the registry lands) | `.env` stays hand-edited | +| `VERSION` | Unaffected by fleets (`release:` is informational until the registry lands) | `.env` stays hand-edited | ## CLI reference @@ -223,5 +224,5 @@ python3 tools/fleet/resolve_fleet.py config/fleets/.yaml --table # inspect ``` Contract tests: `tests/meta/test_fleet_contract.py` (resolver parity with the -legacy map, generation determinism, split placement, the bridge hard-gate, -precedence, named schema errors). +`robot_name_map` resolver, generation determinism, split placement, the +bridge hard-gate, precedence, named schema errors). diff --git a/docs/development/intermediate/docker-build-profiles.md b/docs/development/intermediate/docker-build-profiles.md index 6fd99df5a..5ad265b47 100644 --- a/docs/development/intermediate/docker-build-profiles.md +++ b/docs/development/intermediate/docker-build-profiles.md @@ -17,7 +17,7 @@ Key inputs: | `PYTHON_VERSION` | Must match the ROS Python path; **quote in YAML** (e.g. `"3.12"`) | | `REAL_ROBOT` | Platform-specific content toggles | -Heavy, algorithm-specific dependencies (e.g. MAC-VO's torch/TensorRT stack) are **not** build-args anymore — they live in each module's `Dockerfile.module` and are layered onto the trunk base image by `airstack module lock --build` (RFC #379 §6). +Heavy, algorithm-specific dependencies (e.g. MAC-VO's torch/TensorRT stack) are **not** build-args — they live in each module's `Dockerfile.module` and are layered onto the trunk base image by `airstack module lock --build` (see [Docker layer composition](../modules.md#docker-layer-composition)). ## Example profiles diff --git a/docs/development/intermediate/documentation.md b/docs/development/intermediate/documentation.md index b01213c63..7b31852b8 100644 --- a/docs/development/intermediate/documentation.md +++ b/docs/development/intermediate/documentation.md @@ -84,7 +84,7 @@ Edit `config/my_planner.yaml`... ## Usage \`\`\`bash -ros2 launch my_planner_bringup my_planner.launch.xml +ros2 launch my_planner my_planner.launch.xml \`\`\` ## Testing @@ -392,7 +392,7 @@ Output in `site/` directory. - **Module**: A ROS 2 package that implements specific functionality - **Layer**: A high-level grouping (interface, sensors, perception, local, global, behavior) -- **Bringup**: Launch package that orchestrates a layer +- **Stack**: A folder under `stacks/` whose entry launch file wires modules together (see [AirStack Stacks](../stacks.md)) - **Topic**: ROS 2 topic for inter-node communication - **Node**: ROS 2 node (running process) diff --git a/docs/development/intermediate/testing/ci_cd.md b/docs/development/intermediate/testing/ci_cd.md index bfaab4d46..232ab07e7 100644 --- a/docs/development/intermediate/testing/ci_cd.md +++ b/docs/development/intermediate/testing/ci_cd.md @@ -27,7 +27,7 @@ to fit CI into your day-to-day development loop. |---|---| | Where do CI jobs run? | Python unit tests: `ubuntu-latest`. Build and simulation tests: a fresh OSMO GPU pod, destroyed afterward. | | What triggers a run? | PR open/update/reopen runs unit + package-build gates; maintainers select simulations with `/pytest`; `workflow_dispatch` is also available. | -| What gets tested? | Automatically: Python units/contracts and ROS package builds/tests. Selectably: Docker builds, liveliness, sensors, and flight policies. (OptiTrack system tests moved to the asm_optitrack module's CI.) | +| What gets tested? | Automatically: Python units/contracts and ROS package builds/tests. Selectably: Docker builds, liveliness, sensors, and flight policies. (OptiTrack system tests live in the asm_optitrack module's CI.) | | How do I see results? | Checks plus a report comment and `test-results-*` artifact (`summary.txt`, `results.xml`, `run_meta.json`, `metrics.json`). | | What fails the build? | Any failed test, or a comparable simulation metric regressing more than 20%. Invalid/incomplete campaigns are labeled, not scored as policy failures. | | Who holds the secrets? | Only the orchestrator host. Workers get a single-use JIT token valid for one registration. | diff --git a/docs/development/intermediate/testing/end_to_end_testing.md b/docs/development/intermediate/testing/end_to_end_testing.md index 203edd31e..4b85802f5 100644 --- a/docs/development/intermediate/testing/end_to_end_testing.md +++ b/docs/development/intermediate/testing/end_to_end_testing.md @@ -8,7 +8,7 @@ AirStack currently has two e2e suites: - **Fixed-trajectory path-tracker benchmark** (`autonomy` mark) — takeoff → execute a fixed pattern (Circle / Figure8 / Racetrack / Line) → land, measuring cross-track error. Documented below. !!! note "Future work: unify the e2e marks" - `takeoff_hover_land` and `autonomy` are separate marks today. As the suite grows they can be consolidated into a single general **e2e** mark/pipeline. Tracked as follow-up — not part of this change. + `takeoff_hover_land` and `autonomy` are separate marks today. As the suite grows they can be consolidated into a single general **e2e** mark/pipeline. --- @@ -407,7 +407,7 @@ Focus on: `cross_track_error_mean_m`, `cross_track_error_max_m`, `path_rmse_m`, ## Manual stack usage (without pytest) -Bring up the stack and take off as described in **[Getting Started](../../../getting_started/index.md)** (`airstack up`, then use the RViz task panel). Once the drone is hovering, dispatch a fixed trajectory directly: +Bring up the stack and take off as described in **[Getting Started](../../../getting_started/index.md)** (`airstack up`, then press `Takeoff` in the Foxglove Robot Tasks panel). Once the drone is hovering, dispatch a fixed trajectory directly: ```bash docker exec -it airstack-robot-desktop-1 bash -c ' diff --git a/docs/development/intermediate/testing/index.md b/docs/development/intermediate/testing/index.md index 8d35551b5..aa1cead9e 100644 --- a/docs/development/intermediate/testing/index.md +++ b/docs/development/intermediate/testing/index.md @@ -87,5 +87,4 @@ airstack test -m "build_packages or autonomy" \ - [Unit Testing](unit_testing.md) — `@pytest.mark.unit`, co-located tests, CI workflow - [Testing frameworks](testing_frameworks.md) — `colcon test`, rostest patterns -- [Integration testing](integration_testing.md) - [CI/CD Pipeline on OSMO](ci_cd.md) — automatic unit/build gates, selectable full-stack GPU campaigns, triggers, and like-for-like metrics reporting diff --git a/docs/development/intermediate/testing/integration_testing.md b/docs/development/intermediate/testing/integration_testing.md deleted file mode 100644 index 00c8e31f4..000000000 --- a/docs/development/intermediate/testing/integration_testing.md +++ /dev/null @@ -1 +0,0 @@ -# Integration Testing \ No newline at end of file diff --git a/docs/development/intermediate/testing/unit_testing.md b/docs/development/intermediate/testing/unit_testing.md index 61b69d0e2..5dcc58ee0 100644 --- a/docs/development/intermediate/testing/unit_testing.md +++ b/docs/development/intermediate/testing/unit_testing.md @@ -82,8 +82,9 @@ AIRSTACK_ROOT=$(pwd) pytest tests/ -m unit -v |---|---|---| | `lidar_point_cloud_filter` | `sensors/lidar_point_cloud_filter/test/test_validation_core.py` | Pure-numpy LiDAR range validation rules | -(The `natnet_ros2` unit tests moved to the -[asm_optitrack module](https://github.com/castacks/asm_optitrack) and run in its CI.) +(Unit tests for module-owned packages — e.g. `natnet_ros2` in the +[asm_optitrack module](https://github.com/castacks/asm_optitrack) — live in +the module repo and run in its CI.) ## Adding a new unit test diff --git a/docs/development/module_ci.md b/docs/development/module_ci.md index 318558cc1..56a412599 100644 --- a/docs/development/module_ci.md +++ b/docs/development/module_ci.md @@ -6,7 +6,7 @@ Trunk owns a reusable GitHub Actions workflow, that checks out `castacks/AirStack` at a pinned ref, checks out the calling module repo next to it, registers the module (`airstack module add` + `airstack module sync`), and runs the **existing system-test suite unchanged** -on a GPU runner ([RFC #379 §5](https://github.com/castacks/AirStack/discussions/379)). +on a GPU runner. A module's CI is therefore a ~12-line caller. ## Wiring a module repo @@ -97,8 +97,8 @@ stays unset; only trunk's `docker-build.yml` writes). The workflow hard-fails when the calling repository is not in the `castacks` org. Org GPU runners (OSMO pool, sim licenses) and org registry secrets never -serve third-party code. External modules will be served by a -**dispatch-triggered test bench** (RFC #379 §5, Phase 4): trunk receives a +serve third-party code. External modules will be served by a planned +**dispatch-triggered test bench**: trunk receives a `repository_dispatch {module_repo, module_ref, airstack_ref}`, runs on its own runners, and posts a check-run back via a GitHub App — secrets and licenses never leave trunk, bench time is gated and rate-limited. Until that lands, @@ -110,7 +110,7 @@ external authors can run the suite on their own runners by overriding System tests double as conformance tests — e.g. `tests/waypoint_checker.py` judges the odometry track regardless of which planner produced it, so passing `waypoint_flight` *is* the behavioral definition of a working global planner. -Guidance by module category (RFC #379 §5): +Guidance by module category: | Module category | Marks | |---|---| @@ -132,7 +132,7 @@ the claim is inspectable; the CI caller is where they actually run. workflow; `sim: msairsim` is the cheap bring-up. - **Release / compatibility claim:** the module's full conformance mark set on GPU — this run is what stamps the badge. -- **Trunk-side nightly canary** (Phase 4) runs registered modules against +- **Trunk-side nightly canary** (planned) runs registered modules against `develop` so breakage surfaces the day it lands. **Badge semantics:** a compat badge reads "module M @ vM passes marks {…} in a diff --git a/docs/development/modules.md b/docs/development/modules.md index a363a1c97..5e33fe95c 100644 --- a/docs/development/modules.md +++ b/docs/development/modules.md @@ -2,11 +2,13 @@ AirStack features can live outside trunk as **modules**: thin repos containing code plus a small `module.yaml` manifest — deps, identity, and test metadata, -never wiring ([RFC #379](https://github.com/castacks/AirStack/discussions/379)). -A checkout pulls the modules it wants with the `airstack module` command group; -trunk never carries module code. +never wiring. Modules exist so that heavy or organization-specific capabilities +(multi-GB ML dependencies, hardware-vendor SDKs, lab-internal algorithms) stay +out of trunk while remaining one command away: a checkout pulls the modules it +wants with the `airstack module` command group, and trunk never carries module +code. -This page covers the Phase P2 machinery: the CLI, the pinning rule, hooks, and +This page covers the module machinery: the CLI, the pinning rule, hooks, and how the overlay places a module into the containers. The manifest format itself is documented in the [module schema README](../../common/module_schema/README.md), and module CI in [Module CI](module_ci.md). Agents scaffolding a new module repo @@ -16,7 +18,7 @@ should follow the `create-module` skill (`.agents/skills/create-module`). | Path | What it is | Committed? | |------|------------|------------| -| `modules.repos` | Which modules this checkout uses: a [vcs2l](https://github.com/ros-infrastructure/vcs2l)-format `repositories:` list (pinned) plus an `x-local-modules:` list for local paths (vcs tools ignore that key) | gitignored in trunk (a *stack* commits its own copy — Phase P3) | +| `modules.repos` | Which modules this checkout uses: a [vcs2l](https://github.com/ros-infrastructure/vcs2l)-format `repositories:` list (pinned) plus an `x-local-modules:` list for local paths (vcs tools ignore that key) | gitignored in trunk (a *stack* commits its own copy — see [Stacks](stacks.md)) | | `modules//` | The synced checkouts (git clones, or symlinks to local paths) | gitignored | | `robot/ros_ws/src/modules/` | Overlay symlink so colcon builds the module's ROS packages | gitignored | | `simulation/isaac-sim/launch_scripts/modules//` | Overlay symlinks exposing a module's Isaac launch scripts | gitignored | @@ -41,8 +43,8 @@ airstack module create --in-tree # scaffold a module boundary in your f ### The pinning rule -`modules.repos` entries are **pinned to tags or commit SHAs — never branches** -(RFC #379 §3). A branch ref rots silently; a pinned `.repos` file *is* a +`modules.repos` entries are **pinned to tags or commit SHAs — never branches**. +A branch ref rots silently; a pinned `.repos` file *is* a tested-together release set. `module add` therefore refuses `--version` values that look like branches (`main`, `develop`, …). Moving a pin is a deliberate act: re-run `module add --version ` (upserts the entry). @@ -147,7 +149,7 @@ AIRSTACK_NO_MODULE_COMPOSE=1 airstack up ## The researcher workflow (fork → module) -Research happens in a fork; modularity is a graduation step (RFC #379 §11). +Research happens in a fork; modularity is a graduation step. Day one, put your work behind a directory boundary: ```bash @@ -172,9 +174,9 @@ classifies your changes against the merge-base with `origin/develop`: files inside `robot/ros_ws/src/modules/` or `modules/` are *contained* (fine); everything else is listed as extraction debt to upstream, carry as a fragment/override, or propose as a missing convention. The report **informs, -never blocks**. Graduation (`module extract` into a fresh template repo) is a -later phase; the `create-module` skill covers authoring the standalone repo by -hand today. +never blocks**. An automated `module extract` command (graduating into a fresh +template repo) is future work; the `create-module` skill covers authoring the +standalone repo by hand today. ## Current limitations (honest v1) @@ -184,9 +186,6 @@ hand today. override-file additions. Override the set with `AIRSTACK_MODULE_ROBOT_SERVICES=svc1,svc2` if you need others. - **`gcs` / `ms-airsim` targets** get no placement yet. -- **Docker composition chain** (per-module dependency layers, `modules.lock`) - and **stack folders** are later phases; `deps:`/`dockerfile:`/`overlay_image:` - manifest fields are recorded but not consumed. - **`airstack_compat`** ranges are validated syntactically but not yet checked against the checkout's `.env` `VERSION` at sync time. - **vcs2l** is installed with `pip3 install --user`; make sure `~/.local/bin` @@ -194,9 +193,9 @@ hand today. ## Docker layer composition -Phase P4 machinery (RFC #379 §6) — it supersedes the "deps recorded but not -consumed" limitation above. Trunk publishes **one signed base image per host -type per version**; modules bring their own dependencies, and +Module dependencies never enter trunk images — that is what keeps the base +images small and the published set finite. Trunk publishes **one signed base +image per host type per version**; modules bring their own dependencies, and `tools/compose_module_layers.py` (run automatically by `airstack module sync`, or on demand via `airstack module lock`) composes them into per-checkout image plans. Permutations are never published. @@ -224,8 +223,7 @@ order 1 → 2 → 3-as-build, grouped per target host (`robot` / `gcs` / docker cannot merge it into a locally built chain. It is used **as-is** only when it is the *sole* docker-relevant module for its host. In any other composition its module must also carry a `dockerfile:` — the fragment is the -source of truth and the overlay is just a cache — otherwise the plan errors, -citing RFC #379 §6. +source of truth and the overlay is just a cache — otherwise the plan errors. ### Zero-module identity rule @@ -279,7 +277,7 @@ from `robot-l4t-stack-base`); override the service list with same host (e.g. `tabulate==0.9.0` vs `tabulate==0.8.0`), naming the fighting modules. Same-spec duplicates and unpinned duplicates are fine. This is one of the two enumerated places where module tooling hard-errors instead of -observing (RFC #379 §4): composing a broken image would be indistinguishable +observing: composing a broken image would be indistinguishable from launching a broken system. ### Trunk publishing is untouched diff --git a/docs/development/stacks.md b/docs/development/stacks.md index 14cbcb971..76d6063ed 100644 --- a/docs/development/stacks.md +++ b/docs/development/stacks.md @@ -1,20 +1,22 @@ # 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). +which modules run and how they are wired together. Stacks exist so wiring has +a **single locus** — one place to read, diff, and pin an entire topology — +instead of connections scattered across per-package launch files. 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)): +Plain files, no schema beyond a required layout: | 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. | +| `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](#split-stacks-and-bridgeyaml) carry one entry file per host role plus `bridge.yaml`. | +| `docker-compose.yaml` | Composes this stack's images from [module layers](modules.md#docker-layer-composition). 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. | @@ -25,22 +27,20 @@ Anatomy is enforced by a unit test: `tests/meta/test_stack_layout_contract.py` | Stack | Topology | |-------|----------| -| [`full_default`](https://github.com/castacks/AirStack/tree/develop/stacks/full_default) | The current full-autonomy topology (GPU `droan_gl` planner) — baseline; machine-proven graph-identical to the removed legacy `AUTONOMY_ROLE=full` dispatch, and what launches when no stack is selected. | -| [`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. Requires the `asm_macvo` module (`airstack module add asm_macvo`). | -| [`lite_default`](https://github.com/castacks/AirStack/tree/develop/stacks/lite_default) | Onboard-lite topology, unsplit — the equivalent of the removed `AUTONOMY_ROLE=onboard` role: interface, sensors, perception, flat Local layer, behavior; **no global, no logging**. | -| [`lite_offload_global`](https://github.com/castacks/AirStack/tree/develop/stacks/lite_offload_global) | The first **split stack** (RFC #380 §2): `onboard.launch.xml` (= lite topology) + `offboard.launch.xml` (global layer only) + `bridge.yaml`. Replaced the removed `onboard`/`offboard` role pair. | - -## Wrap vs. flatten — current status - -The wrap→flatten migration is COMPLETE: every reference stack composes its -graph as flat module-launch includes, the legacy layer bringup launch files -(`local/perception/sensors/global/behavior *.launch.xml`) are deleted, and -the AUTONOMY_ROLE dispatch is gone from `autonomy_bringup` — stacks are the -only dispatch. Two blocks remain wrapped **by design**: `interface.launch.py` -(the safety boundary, until RFC #380 Part 2) and the +| [`full_default`](https://github.com/castacks/AirStack/tree/develop/stacks/full_default) | The full-autonomy topology (GPU `droan_gl` planner) — the baseline, and what launches when no stack is selected. | +| [`full_droan_cpu`](https://github.com/castacks/AirStack/tree/develop/stacks/full_droan_cpu) | CPU DROAN planner + live `disparity_expansion` (for machines without the GPU planner). | +| [`full_macvo`](https://github.com/castacks/AirStack/tree/develop/stacks/full_macvo) | MAC-VO as the planner's disparity source. Requires the `asm_macvo` module (`airstack module add asm_macvo`). | +| [`lite_default`](https://github.com/castacks/AirStack/tree/develop/stacks/lite_default) | Onboard-lite topology, unsplit: interface, sensors, perception, flat Local layer, behavior; **no global, no logging**. | +| [`lite_offload_global`](https://github.com/castacks/AirStack/tree/develop/stacks/lite_offload_global) | A **split stack**: `onboard.launch.xml` (= lite topology) + `offboard.launch.xml` (global layer only) + `bridge.yaml`. | + +## Flat includes, and the deliberate exceptions + +Every reference stack composes its graph as **flat module-launch includes** — +one `` per module, so the entry file reads as the topology. Two +blocks are wrapped **by design**: `interface.launch.py` (the safety boundary, +kept whole until the planned platform-module extraction) and the `interpolate_dds_router` / gossip helpers (their wiring lives in YAML -configs). The lint allowlist (below) is down to that deliberate remainder. +configs). The lint allowlist (below) covers exactly that deliberate remainder. The stack's `wiring.md` — snapshotted from the running system — is the observed truth of the graph. @@ -64,7 +64,8 @@ then includes the stack entry file. With no stack selected anywhere effective config. `--stack :` selects an alternate entry file -(`launch/.launch.xml`) — reserved for split stacks (RFC #380 §2). +(`launch/.launch.xml`) — reserved for +[split stacks](#split-stacks-and-bridgeyaml). Stack launch files need no `colcon build` — they are read from the bind mount; edit and re-launch. @@ -86,13 +87,13 @@ dispatcher. Three reasons the thin `robot.launch.xml` earns its ~50 lines: `robot_state_publisher`/URDF plumbing, the world→map TF — is *platform* infrastructure, not topology. Keeping it out of stack entries preserves the "entry file *is* the wiring diagram" property, and keeps - vehicle-driven URDF generation (RFC #380 §1) a one-file change instead of + vehicle-driven URDF generation (planned) a one-file change instead of an every-stack (and every external stack repo) migration. -3. **It is the seed of the platform module.** RFC #380 Part 2 extracts +3. **It is the seed of the platform module.** Planned work extracts "interface + controller + safety + preamble" as the `px4_multirotor` platform module; this dispatcher is precisely the file that becomes that - platform's bringup. The Directory Atlas (#385) says `autonomy_bringup` - *thins* — it does not disappear. + platform's bringup. `autonomy_bringup` *thins* over time — it does not + disappear. Practically it is also the single point where `AIRSTACK_STACK_DIR`/`_ENTRY` resolution happens, so compose, the fleet resolver, and the CLI converge on @@ -135,7 +136,7 @@ Enforced by `tests/meta/test_launch_single_locus.py` (`unit` mark, runs in CI): in `tests/meta/launch_lint_allowlist.txt` (down to the deliberate remainder: a standalone utility, a vendored driver, one module launch awaiting its canonical rewrite, and the interface safety boundary). -2. The allowlist only shrinks: an entry whose file no longer carries a remap +2. The allowlist only shrinks: an entry whose file carries no remap fails the lint until its line is deleted. 3. Stack launch files must describe every `` they declare. @@ -158,7 +159,7 @@ for the authoring workflow. The full workflow (including split stacks) is the [create-stack skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-stack/SKILL.md). -## Split stacks and `bridge.yaml` (RFC #380 §2) +## Split stacks and `bridge.yaml` **A split is a stack shape, not special machinery.** A split stack folder carries **multiple launch entry points** — one per host role — plus a @@ -178,10 +179,10 @@ stacks/lite_offload_global/ - **Run each half** with the `NAME:ENTRY` form: `airstack up --stack lite_offload_global:onboard` on the vehicle, - `... :offboard` on the ground host. The old coarse `AUTONOMY_ROLE` - trichotomy (removed) became "which entry point does this host run"; a - third machine is just another entry file + bridge section. -- **Or declare the placement in a fleet** (RFC #380 §2): a fleet entry with + `... :offboard` on the ground host. A host's role is simply "which entry + point does this host run"; a third machine is just another entry file + + bridge section. +- **Or declare the placement in a fleet**: a fleet entry with `stack: stacks/lite_offload_global` and `hosts: {offboard: gcs}` derives both halves — the robot's service gets the `onboard` entry, the named ground host's service gets the same stack with @@ -205,9 +206,9 @@ stacks/lite_offload_global/ **onboard-only** (link loss must leave the vehicle able to failsafe): `global_plan` crosses, trajectory commands don't. `gen_dds_router.py --check` — run by `airstack doctor` and by - `tests/meta/test_bridge_contract.py` — exits 1 naming any violation - (RFC #379 §4 / RFC #380 §2). This is one of doctor's **two** enumerated - hard gates; growing that list takes the RFC process. + `tests/meta/test_bridge_contract.py` — exits 1 naming any violation. + This is one of doctor's **two** enumerated hard gates; the gate list + grows only by deliberate project-wide design decision, never ad hoc. - **One `wiring.md` per stack, split or not**: the snapshot groups nodes by host and draws bridge edges as boundary crossings. @@ -228,11 +229,11 @@ trailers** of the two stacks' `wiring.md` files via the same `tests/wiring_snapshot.py` machinery CI uses: nodes/edges/topics added or removed, QoS and type mismatches — topology differences, never XML formatting noise. That, plus shared includable sub-launches, is how -copy-drift between stack folders stays bounded (RFC #379 §3). +copy-drift between stack folders stays bounded. ## `airstack doctor` -Observe-and-report health checks (RFC #379 §4). Doctor never generates or +Observe-and-report health checks. Doctor never generates or edits wiring; in default mode it exits non-zero only on the **two enumerated hard gates**: @@ -240,10 +241,10 @@ hard gates**: airstack doctor # compose-time battery: # 1. module manifests valid (report) # 2. overlay integrity (report) - # 3. HARD GATE: module dep conflicts (RFC #379 §6) + # 3. HARD GATE: module dep conflicts # 4. stack anatomy incl. split=>bridge.yaml (report) # 5. HARD GATE: control/trajectory names in a - # bridge.yaml (RFC #380 §2) + # bridge.yaml airstack doctor --live --stack # capture the RUNNING graph (docker exec per @@ -269,19 +270,18 @@ airstack doctor --snapshot --stack `airstack up` with the stack flag). `airstack module doctor` remains the module-scoped subset (manifests + overlay + `--drift`). -## AUTONOMY_ROLE: removed - -`AUTONOMY_ROLE` was **removed on this branch** — stacks are the only launch -dispatch. `airstack up` hard-errors (preflight) when it sees an explicitly -set `AUTONOMY_ROLE` (env / `.env` / `--env-file`), naming this page. -Migration: - -| You had | Now | Migration | -|---------|-----|-----------| -| No stack, no `AUTONOMY_ROLE` (compose default role `full`) | `full_default` launches by default — machine-proven graph-identical to the old `full` role | Nothing to do (or be explicit: `--stack full_default`) | -| Explicit `AUTONOMY_ROLE=full` | Preflight error | `--stack full_default` (or drop the variable — same stack launches) | -| `local_droan_cpu.launch.xml` variant | **Deleted in P5-E2** — the CPU-DROAN topology lives only in the stack | `--stack full_droan_cpu` | -| `local_macvo_obstacle_avoidance.launch.xml` | **Deleted in P5-E2** (was broken: wrong arg names, stale topic) | `--stack full_macvo` (fixed) | -| `AUTONOMY_ROLE=onboard` (lite, no split) | Preflight error. (On the desktop profile this role was unreachable anyway — `robot-desktop` hardcoded `AUTONOMY_ROLE=full`.) | `--stack lite_default` | -| `AUTONOMY_ROLE=onboard` / `offboard` (split) | Preflight error | `--stack lite_offload_global:onboard` / `:offboard` (+ `bridge.yaml`; generate the router config — the generated config deliberately drops the legacy split's `set_trajectory_mode` crossing, doctor hard gate #2) | -| `--stack X` **and** `AUTONOMY_ROLE` | Preflight error (no silent "stack wins" anymore) | Drop `AUTONOMY_ROLE` | +## `AUTONOMY_ROLE` is not a launch input + +Stacks are the only launch dispatch — there is no role env var. `airstack up` +hard-errors at preflight when it sees an explicitly set `AUTONOMY_ROLE` +(env / `.env` / `--env-file`), naming this page: a role variable would +silently compete with the stack selection, so it is rejected rather than +ignored. Select the topology with `--stack`: + +| You want | Command | +|----------|---------| +| The full-autonomy baseline | Nothing — `full_default` launches by default (or be explicit: `--stack full_default`) | +| CPU DROAN topology | `--stack full_droan_cpu` | +| MAC-VO disparity topology | `--stack full_macvo` (requires the `asm_macvo` module) | +| Onboard-lite, unsplit | `--stack lite_default` | +| Split onboard/offboard | `--stack lite_offload_global:onboard` / `:offboard` (+ generate the router config from `bridge.yaml`) | diff --git a/docs/gcs/docker/index.md b/docs/gcs/docker/index.md index 981c39614..8b344c277 100644 --- a/docs/gcs/docker/index.md +++ b/docs/gcs/docker/index.md @@ -56,8 +56,8 @@ Attach to the session with `airstack connect gcs` (or `tmux attach -t bringup` i | Variable | Description | Default | |----------|-------------|---------| | `AUTOLAUNCH` | Auto-start the GCS bringup in tmux | `false` in the compose file (the `airstack` CLI sets it `true` for full-stack launches) | -| `NUM_ROBOTS` | Legacy robot count: sizes the rendered layout and the per-robot action relays | `1` | -| `FLEET_CONFIG_FILE` | Fleet-first roster (RFC #380): when set, `action_relay` derives robot names/domains from the fleet file instead of `NUM_ROBOTS` | unset | +| `NUM_ROBOTS` | Robot count when no fleet file is set: sizes the rendered layout and the per-robot action relays | `1` | +| `FLEET_CONFIG_FILE` | Fleet roster: when set, `action_relay` derives robot names/domains from the fleet file instead of `NUM_ROBOTS` | unset | | `ROBOT_RELAY_MAP` | `name:domain,...` override for custom robot→domain mappings (wins over both of the above) | unset | | `RECORD_BAGS` | Enable bag recording | unset | | `DISPLAY` | Host X11 display for the Foxglove Studio window | inherited | diff --git a/docs/gcs/foxglove.md b/docs/gcs/foxglove.md index fff842c6c..c519ab411 100644 --- a/docs/gcs/foxglove.md +++ b/docs/gcs/foxglove.md @@ -1,6 +1,6 @@ # GCS Foxglove Visualization -The GCS runs a **Foxglove Studio** browser interface backed by a single ROS 2 node — `foxglove_visualizer_node` — that gathers per-robot data from the cross-domain bridge and republishes it on a small set of GCS-side topics. Foxglove subscribes to those topics and shows the fleet in 3D. +The GCS runs a **Foxglove Studio** interface backed by a single ROS 2 node — `foxglove_visualizer_node` — that gathers per-robot data from the cross-domain bridge and republishes it on a small set of GCS-side topics. Foxglove subscribes to those topics and shows the fleet in 3D. This page describes what the node visualizes today, the topic naming convention, and where to edit when you want to change or add a marker type. For the gossip payload visualization (filtered rays, voxel maps, etc.) see [Coordination Payloads](../robot/autonomy/coordination/payloads.md). diff --git a/docs/gcs/usage/user_interface.md b/docs/gcs/usage/user_interface.md index ce9591b84..110774deb 100644 --- a/docs/gcs/usage/user_interface.md +++ b/docs/gcs/usage/user_interface.md @@ -1,20 +1,23 @@ # User Interface -The GCS control panel shows per-robot status: **Ping**, **Recording**, and **Battery** (voltage and percentage when the robot's MAVROS battery topic is bridged to the GCS). +The operator interface is **Foxglove Studio**, extended with the AirStack panels installed by the GCS container: -The main operator interface is **Foxglove Studio** — see [GCS Foxglove Visualization](../foxglove.md) and [Adding Waypoints and Geofences](../waypoints_and_geofences.md). +- **3D fleet view** — per-robot meshes, trajectories, global plans, and maps in a shared global frame ([GCS Foxglove Visualization](../foxglove.md)) +- **Robot Tasks panel** — send takeoff / land / navigate / trajectory / search / exploration commands per robot +- **Waypoint and Polygon editors** — click-to-place routes and geofence areas ([Adding Waypoints and Geofences](../waypoints_and_geofences.md)) +- **Per-robot tabs** — camera and depth feeds for each robot in the rendered layout ## Debugging tips Launch just the GCS container: ```bash -docker compose up gcs # desktop profile +airstack up gcs # desktop profile docker compose --profile deploy up gcs-real # deployed GCS hardware ``` -Run docker in interactive mode: +Get a shell in the running container: ```bash -docker exec -it gcs /bin/bash +docker exec -it airstack-gcs-1 bash ``` diff --git a/docs/gcs/waypoints_and_geofences.md b/docs/gcs/waypoints_and_geofences.md index 530235df5..3eabcd951 100644 --- a/docs/gcs/waypoints_and_geofences.md +++ b/docs/gcs/waypoints_and_geofences.md @@ -75,7 +75,7 @@ If a save doesn't appear in the dropdown after creating it, click the dropdown a | Symptom | Likely cause | |---|---| -| Clicks don't register | Click capture isn't enabled, or the Publish tool isn't set to **Click position** | +| Clicks don't register | Click capture isn't enabled, or the Publish tool isn't set to **Publish 2D point** | | Clicks register but no marker shows | The 3D panel doesn't have the editor's marker topic enabled — open its Topics list and toggle on `/gcs/waypoints/markers` (or `/gcs/polygon/markers`) | | Saves don't persist | Host volume `~/.airstack` not mounted on the GCS container | | Save name silently overwrites | Both **+ Add** and **Save** overwrite by name — pick a unique name | diff --git a/docs/getting_started/modular_airstack.md b/docs/getting_started/modular_airstack.md index 659c1f382..fac02ceff 100644 --- a/docs/getting_started/modular_airstack.md +++ b/docs/getting_started/modular_airstack.md @@ -1,8 +1,6 @@ # Modular AirStack Walkthrough -The end-to-end journey through Modular AirStack -([RFC #379](https://github.com/castacks/AirStack/discussions/379) / -[RFC #380](https://github.com/castacks/AirStack/discussions/380)) for a +The end-to-end journey through Modular AirStack for a developer new to the project: fly a **reference stack**, read its **wiring**, pull in a **module**, make a **stack of your own**, scale to a **fleet**, and let **doctor** check your work. Every command below is real and current. diff --git a/docs/modules/dfm2_disturbances.md b/docs/modules/dfm2_disturbances.md index 9dca06420..b376deab2 100644 --- a/docs/modules/dfm2_disturbances.md +++ b/docs/modules/dfm2_disturbances.md @@ -10,9 +10,9 @@ |---|---| | Repository | [castacks/asm_dfm2_disturbances](https://github.com/castacks/asm_dfm2_disturbances) | | Type | `isaac_extension` | -| Maintainer | maintainers@theairlab.org | -| License | MIT | -| Registered ref | [`af3daa783248`](https://github.com/castacks/asm_dfm2_disturbances/tree/af3daa783248b07b82165833d419d322ab3137fe) | +| Maintainer | ajong@andrew.cmu.edu | +| License | BSD-3-Clause-Clear | +| Registered ref | [`cf82cdbe5e44`](https://github.com/castacks/asm_dfm2_disturbances/tree/cf82cdbe5e44757e0034a24248ae887c8d91c0b4) | | Declared compat | `>=0.19.0-alpha.18 <0.20.0` | | Registry entry | [modules/dfm2_disturbances.yaml](https://github.com/castacks/airstack-modules-index/blob/main/modules/dfm2_disturbances.yaml) | @@ -21,13 +21,13 @@ From an AirStack checkout ([AirStack Modules guide](../development/modules.md)): ```bash -airstack module add https://github.com/castacks/asm_dfm2_disturbances --version af3daa783248b07b82165833d419d322ab3137fe -airstack up -f .airstack/generated/docker-compose.modules.yaml +airstack module add https://github.com/castacks/asm_dfm2_disturbances --version cf82cdbe5e44757e0034a24248ae887c8d91c0b4 +airstack up ``` `module add` pins the module in `modules.repos` and syncs it into the -gitignored `modules/` overlay; the generated compose file mounts it into -the containers. +gitignored `modules/` overlay; `airstack up` automatically includes the +generated compose override that mounts it into the containers. ## Compatibility: declared vs verified @@ -37,15 +37,15 @@ stamped exclusively by CI runs of the reusable [module-system-tests workflow](../development/module_ci.md) — lives in the registry's [compat/ matrix](https://github.com/castacks/airstack-modules-index/tree/main/compat) ([compat/dfm2_disturbances.yaml](https://github.com/castacks/airstack-modules-index/blob/main/compat/dfm2_disturbances.yaml) once stamped). -A compatibility claim that isn't CI-verified rots (RFC #379 §5): trust the +A compatibility claim that isn't CI-verified rots: trust the matrix, read the declaration as intent. ## Documentation -- [Module README on GitHub @ `af3daa783248`](https://github.com/castacks/asm_dfm2_disturbances/blob/af3daa783248b07b82165833d419d322ab3137fe/README.md) +- [Module README on GitHub @ `cf82cdbe5e44`](https://github.com/castacks/asm_dfm2_disturbances/blob/cf82cdbe5e44757e0034a24248ae887c8d91c0b4/README.md) - *The module repo was not fetched when this page was generated — the* - *links above go to GitHub at the registered ref (RFC #379 §9 failure* - *isolation: an unreachable module repo never fails the docs deploy).* + *links above go to GitHub at the registered ref (failure isolation:* + *an unreachable module repo never fails the docs deploy).* ## Registered stacks using this module @@ -53,4 +53,4 @@ matrix, read the declaration as intent. ## Registry notes -> Pilot module of RFC #379, hand-built before the tooling existed (see the repo's FRICTION_LOG.md). registered_ref is a commit SHA because no release tag exists yet: v0.1.0 is pending the first green module-system-tests.yml CI run. Validated end-to-end locally on 2026-08-20 (extraction campaign) — declared marks liveliness + takeoff_hover_land in the module's test_stack on Isaac Sim. +> registered_ref is a commit SHA because no release tag exists yet: v0.1.0 is pending the first green module-system-tests.yml CI run. Validated end-to-end locally on 2026-08-20 — declared marks liveliness + takeoff_hover_land in the module's test_stack on Isaac Sim. diff --git a/docs/modules/index.md b/docs/modules/index.md index 0c4dd48cd..7a28abc3b 100644 --- a/docs/modules/index.md +++ b/docs/modules/index.md @@ -4,8 +4,8 @@ python3 tools/gen_docs_catalog.py --index (the docs deploy workflows regenerate it against the live registry at build time) --> -The **marketplace catalog** of registered AirStack modules and stacks -([RFC #379 §7/§9](https://github.com/castacks/AirStack/discussions/379)), rendered from the +The **marketplace catalog** of registered AirStack modules and stacks, +rendered from the [airstack-modules-index](https://github.com/castacks/airstack-modules-index) registry — one YAML entry per module or stack, rosdistro-style. Getting listed = a PR to the registry (see the [registry README](https://github.com/castacks/airstack-modules-index#how-to-register-a-module)). @@ -18,17 +18,17 @@ directory and is never hand-edited. | Module | Description | Type | Maintainer | Declared compat | Links | |--------|-------------|------|------------|-----------------|-------| -| [dfm2_disturbances](dfm2_disturbances.md) | Isaac Sim disturbance library (fan/vent force fields, strobe lights, lens flare) | `isaac_extension` | maintainers@theairlab.org | `>=0.19.0-alpha.18 <0.20.0` | [repo](https://github.com/castacks/asm_dfm2_disturbances) | -| [macvo](macvo.md) | MAC-VO learned stereo visual odometry (ICRA 2025 best paper) — macvo_ros2 wrapper around the MAC-VO network, publishing odometry, a covariance-aware point cloud, and the disparity image the local planner can consume | `ros_package` | maintainers@theairlab.org | `>=0.19.0-alpha.18 <0.20.0` | [repo](https://github.com/castacks/asm_macvo) · [full_macvo](../../stacks/full_macvo/README.md) | -| [optitrack](optitrack.md) | OptiTrack NatNet mocap integration — natnet_ros2 client + PX4 external-vision fusion bridges on the robot, and the Motive-compatible NatNet server emulator for Isaac Sim | `ros_package` | maintainers@theairlab.org | `>=0.19.0-alpha.18 <0.20.0` | [repo](https://github.com/castacks/asm_optitrack) | +| [dfm2_disturbances](dfm2_disturbances.md) | Isaac Sim disturbance library (fan/vent force fields, strobe lights, lens flare) | `isaac_extension` | ajong@andrew.cmu.edu | `>=0.19.0-alpha.18 <0.20.0` | [repo](https://github.com/castacks/asm_dfm2_disturbances) | +| [macvo](macvo.md) | MAC-VO learned stereo visual odometry (ICRA 2025 best paper) — macvo_ros2 wrapper around the MAC-VO network, publishing odometry, a covariance-aware point cloud, and the disparity image the local planner can consume | `ros_package` | ajong@andrew.cmu.edu | `>=0.19.0-alpha.18 <0.20.0` | [repo](https://github.com/castacks/asm_macvo) · [full_macvo](../../stacks/full_macvo/README.md) | +| [optitrack](optitrack.md) | OptiTrack NatNet mocap integration — natnet_ros2 client + PX4 external-vision fusion bridges on the robot, and the Motive-compatible NatNet server emulator for Isaac Sim | `ros_package` | ajong@andrew.cmu.edu | `>=0.19.0-alpha.18 <0.20.0` | [repo](https://github.com/castacks/asm_optitrack) | ## Registered stacks A stack is a self-contained topology folder; its pinned `modules.repos` -*is* a tested-together release set ([RFC #379 §3](https://github.com/castacks/AirStack/discussions/379)). +*is* a tested-together release set. The stacks below are the ones REGISTERED in the index; the site nav's **Modules → Reference Stacks** additionally lists every trunk stack -(registration of the remaining stacks is a registry-side follow-up). +(not every trunk stack is registered in the index). | Stack | Description | Declared compat | Wiring | Registry entry | |-------|-------------|-----------------|--------|----------------| @@ -45,7 +45,7 @@ The stacks below are the ones REGISTERED in the index; the site nav's - [AirStack Stacks](../development/stacks.md) — stack anatomy, `stack new|diff`, wiring snapshots, `doctor` - [AirStack Fleets](../development/fleets.md) — fleet files composing stacks - into deployments (RFC #380) + into deployments - [Module CI](../development/module_ci.md) — the reusable system-test workflow module repos call; how compat badges are earned - [Interface Conventions Spec](../robot/autonomy/interface_conventions.md) — diff --git a/docs/modules/macvo.md b/docs/modules/macvo.md index 19e9be1de..59d0977d6 100644 --- a/docs/modules/macvo.md +++ b/docs/modules/macvo.md @@ -10,9 +10,9 @@ |---|---| | Repository | [castacks/asm_macvo](https://github.com/castacks/asm_macvo) | | Type | `ros_package` | -| Maintainer | maintainers@theairlab.org | -| License | MIT | -| Registered ref | [`7d5763936122`](https://github.com/castacks/asm_macvo/tree/7d5763936122ffcbb7173a635c9d5711e25c2414) | +| Maintainer | ajong@andrew.cmu.edu | +| License | BSD-3-Clause-Clear | +| Registered ref | [`431d7faf1f6f`](https://github.com/castacks/asm_macvo/tree/431d7faf1f6fed20d415bb5e5a88d8dcb0d180df) | | Declared compat | `>=0.19.0-alpha.18 <0.20.0` | | Registry entry | [modules/macvo.yaml](https://github.com/castacks/airstack-modules-index/blob/main/modules/macvo.yaml) | @@ -21,13 +21,13 @@ From an AirStack checkout ([AirStack Modules guide](../development/modules.md)): ```bash -airstack module add https://github.com/castacks/asm_macvo --version 7d5763936122ffcbb7173a635c9d5711e25c2414 -airstack up -f .airstack/generated/docker-compose.modules.yaml +airstack module add https://github.com/castacks/asm_macvo --version 431d7faf1f6fed20d415bb5e5a88d8dcb0d180df +airstack up ``` `module add` pins the module in `modules.repos` and syncs it into the -gitignored `modules/` overlay; the generated compose file mounts it into -the containers. +gitignored `modules/` overlay; `airstack up` automatically includes the +generated compose override that mounts it into the containers. ## Compatibility: declared vs verified @@ -37,15 +37,15 @@ stamped exclusively by CI runs of the reusable [module-system-tests workflow](../development/module_ci.md) — lives in the registry's [compat/ matrix](https://github.com/castacks/airstack-modules-index/tree/main/compat) ([compat/macvo.yaml](https://github.com/castacks/airstack-modules-index/blob/main/compat/macvo.yaml) once stamped). -A compatibility claim that isn't CI-verified rots (RFC #379 §5): trust the +A compatibility claim that isn't CI-verified rots: trust the matrix, read the declaration as intent. ## Documentation -- [Module README on GitHub @ `7d5763936122`](https://github.com/castacks/asm_macvo/blob/7d5763936122ffcbb7173a635c9d5711e25c2414/README.md) +- [Module README on GitHub @ `431d7faf1f6f`](https://github.com/castacks/asm_macvo/blob/431d7faf1f6fed20d415bb5e5a88d8dcb0d180df/README.md) - *The module repo was not fetched when this page was generated — the* - *links above go to GitHub at the registered ref (RFC #379 §9 failure* - *isolation: an unreachable module repo never fails the docs deploy).* + *links above go to GitHub at the registered ref (failure isolation:* + *an unreachable module repo never fails the docs deploy).* ## Registered stacks using this module @@ -53,4 +53,4 @@ matrix, read the declaration as intent. ## Registry notes -> registered_ref is a commit SHA because no release tag exists yet: v0.1.0 is pending the first green module-system-tests.yml CI run. RFC #379's dogfood case for Docker dependency tiers 2/3: MAC-VO's heavy deps (TensorRT, torch, model weights) live in the module's Dockerfile.module, out of trunk's Dockerfile.robot. Composed-image CI validation (declared marks build_docker + liveliness against the tier-2 layer chain) is still pending — unlike dfm2_disturbances/optitrack, this module has not yet been validated end-to-end. Consumed by trunk reference stack full_macvo. +> registered_ref is a commit SHA because no release tag exists yet: v0.1.0 is pending the first green module-system-tests.yml CI run. MAC-VO's heavy deps (TensorRT, torch, model weights) live in the module's Dockerfile.module (Docker dependency tier 2), keeping them out of the base robot image. Composed-image CI validation (declared marks build_docker + liveliness against the tier-2 layer chain) is still pending — unlike dfm2_disturbances/optitrack, this module has not yet been validated end-to-end. Consumed by trunk reference stack full_macvo. diff --git a/docs/modules/optitrack.md b/docs/modules/optitrack.md index 69fa0f8bd..da5629593 100644 --- a/docs/modules/optitrack.md +++ b/docs/modules/optitrack.md @@ -10,9 +10,9 @@ |---|---| | Repository | [castacks/asm_optitrack](https://github.com/castacks/asm_optitrack) | | Type | `ros_package` | -| Maintainer | maintainers@theairlab.org | -| License | MIT | -| Registered ref | [`2ae094f4b308`](https://github.com/castacks/asm_optitrack/tree/2ae094f4b308b49127761c0605a17ad4ef4f6e31) | +| Maintainer | ajong@andrew.cmu.edu | +| License | BSD-3-Clause-Clear | +| Registered ref | [`be4e0c141ab8`](https://github.com/castacks/asm_optitrack/tree/be4e0c141ab8fd7248119faa7950004dd56136a3) | | Declared compat | `>=0.19.0-alpha.18 <0.20.0` | | Registry entry | [modules/optitrack.yaml](https://github.com/castacks/airstack-modules-index/blob/main/modules/optitrack.yaml) | @@ -21,13 +21,13 @@ From an AirStack checkout ([AirStack Modules guide](../development/modules.md)): ```bash -airstack module add https://github.com/castacks/asm_optitrack --version 2ae094f4b308b49127761c0605a17ad4ef4f6e31 -airstack up -f .airstack/generated/docker-compose.modules.yaml +airstack module add https://github.com/castacks/asm_optitrack --version be4e0c141ab8fd7248119faa7950004dd56136a3 +airstack up ``` `module add` pins the module in `modules.repos` and syncs it into the -gitignored `modules/` overlay; the generated compose file mounts it into -the containers. +gitignored `modules/` overlay; `airstack up` automatically includes the +generated compose override that mounts it into the containers. ## Compatibility: declared vs verified @@ -37,15 +37,15 @@ stamped exclusively by CI runs of the reusable [module-system-tests workflow](../development/module_ci.md) — lives in the registry's [compat/ matrix](https://github.com/castacks/airstack-modules-index/tree/main/compat) ([compat/optitrack.yaml](https://github.com/castacks/airstack-modules-index/blob/main/compat/optitrack.yaml) once stamped). -A compatibility claim that isn't CI-verified rots (RFC #379 §5): trust the +A compatibility claim that isn't CI-verified rots: trust the matrix, read the declaration as intent. ## Documentation -- [Module README on GitHub @ `2ae094f4b308`](https://github.com/castacks/asm_optitrack/blob/2ae094f4b308b49127761c0605a17ad4ef4f6e31/README.md) +- [Module README on GitHub @ `be4e0c141ab8`](https://github.com/castacks/asm_optitrack/blob/be4e0c141ab8fd7248119faa7950004dd56136a3/README.md) - *The module repo was not fetched when this page was generated — the* - *links above go to GitHub at the registered ref (RFC #379 §9 failure* - *isolation: an unreachable module repo never fails the docs deploy).* + *links above go to GitHub at the registered ref (failure isolation:* + *an unreachable module repo never fails the docs deploy).* ## Registered stacks using this module @@ -53,4 +53,4 @@ matrix, read the declaration as intent. ## Registry notes -> registered_ref is a commit SHA because no release tag exists yet: v0.1.0 is pending the first green module-system-tests.yml CI run. Validated end-to-end locally on 2026-08-20 (extraction campaign) — declared marks integration + liveliness + optitrack (full EV-fusion flight e2e) in the module's test_stack on Isaac Sim. Builds against the proprietary OptiTrack NatNet SDK, fetched host-side by hooks.host_setup (never in git, never in images); CI passes NATNET_ACCEPT_LICENSE=1 via hook_env. +> registered_ref is a commit SHA because no release tag exists yet: v0.1.0 is pending the first green module-system-tests.yml CI run. Validated end-to-end locally on 2026-08-20 — declared marks integration + liveliness + optitrack (full EV-fusion flight e2e) in the module's test_stack on Isaac Sim. Builds against the proprietary OptiTrack NatNet SDK, fetched host-side by hooks.host_setup (never in git, never in images); CI passes NATNET_ACCEPT_LICENSE=1 via hook_env. diff --git a/docs/real_world/HITL/index.md b/docs/real_world/HITL/index.md index bf2adeda2..1215e1df5 100644 --- a/docs/real_world/HITL/index.md +++ b/docs/real_world/HITL/index.md @@ -7,14 +7,14 @@ A desktop computer configured according to [here](../../getting_started/index.md All machines should connect to the same network. In our test, all machines are connected to the same router with ethernet cables. Ensure that all machines are able to `ping` others' IP addresses. ### Run -On the desktop computer, under your Airstack folder, run -``` +On the desktop computer, under your AirStack folder, run +```bash docker compose up isaac-sim ``` -You should see the isaac simulator being launched. +You should see the Isaac simulator being launched. On the Jetson computer, run -``` -docker compose up robot-l4t +```bash +docker compose --profile l4t up robot-l4t ``` Once the scene is played in the Isaac simulator, the rviz GUI on the Jetson should start displaying sensor data, which means the connection is successful. diff --git a/docs/real_world/data_offloading/index.md b/docs/real_world/data_offloading/index.md index dfc63f649..d527ae329 100644 --- a/docs/real_world/data_offloading/index.md +++ b/docs/real_world/data_offloading/index.md @@ -56,7 +56,7 @@ Open a web browser to http://localhost:8091 (or the PORT you set). The default u ```bash cd /opt git clone https://github.com/castacks/storage_tools_device -cd stroage_tools_device +cd storage_tools_device ``` ### Update the config.yaml diff --git a/docs/real_world/installation/index.md b/docs/real_world/installation/index.md index aa2a01c7c..7f485357a 100644 --- a/docs/real_world/installation/index.md +++ b/docs/real_world/installation/index.md @@ -1,29 +1,32 @@ # Installation on ORIN AGX/NX -We have tested installation and running robot container on Jetson ORIN AGX/NX and Ubuntu 22.04. +We have tested installation and running the robot container on Jetson ORIN AGX/NX with Ubuntu 22.04 (L4T / JetPack). ## Setup -Ensure you have docker installed. + +Ensure you have Docker installed (`airstack install` can install it for you). + ### Clone -``` +```bash git clone --recursive -j8 git@github.com:castacks/AirStack.git +cd AirStack ``` -Checkout to the correct branch: -``` -git checkout jkeller/jetson_36.4 -``` + ## Configure -Run `./configure.sh` and follow the instructions in the prompts to do an initial configuration of the repo. +Run `./airstack.sh setup` and follow the prompts to do an initial configuration of the repo (this also adds the `airstack` command to your PATH). Pull the correct image: -``` -docker compose pull robot_l4t + +```bash +docker compose --profile l4t pull robot-l4t ``` ## Run + +```bash +airstack --profile l4t up ``` -docker compose up robot_l4t -``` -You should be able to see the rviz GUI being launched. \ No newline at end of file + +The autonomy stack launches inside a tmux session in the `robot-l4t` container. Verify it with `airstack status` and follow the output with `airstack logs robot-l4t` (or `airstack connect robot-l4t` to attach to the tmux session). diff --git a/docs/release_notes/index.md b/docs/release_notes/index.md new file mode 100644 index 000000000..d577eadde --- /dev/null +++ b/docs/release_notes/index.md @@ -0,0 +1,343 @@ +# Release Notes + +Feature docs deliberately describe only the system as it exists — never how +it got that way — so that every page reads standalone. This page is the one +place where change context lives, organized by version: what changed, from +what, and why. If you last used AirStack on an earlier release, read the +sections between your version and the current one; every other docs page +assumes the current design. + +## 0.20.0 (Unreleased) + +This release restructures AirStack from a monolith into **modules**, +**stacks**, and **fleets**, implementing +[RFC #379 (Modular AirStack)](https://github.com/castacks/AirStack/discussions/379) +scales 1–2 of +[RFC #380 (Heterogeneous AirStack)](https://github.com/castacks/AirStack/discussions/380), +and the stack-folder anatomy of +[RFC #385 (Directory Atlas)](https://github.com/castacks/AirStack/discussions/385). +Feature docs deliberately cite none of these — the design sources live here: + +- **Modules** are thin external repos with a small `module.yaml`, pulled on + demand: `airstack module add --version `. Three capabilities + were extracted from trunk into new repos: + [asm_macvo](https://github.com/castacks/asm_macvo) (with its torch/TensorRT + stack moved out of the base robot image — 17.1 GB → ~6 GB, −65%), + [asm_optitrack](https://github.com/castacks/asm_optitrack) (including the + Isaac Sim NatNet emulator), and + [asm_dfm2_disturbances](https://github.com/castacks/asm_dfm2_disturbances). + The registry lives at + [castacks/airstack-modules-index](https://github.com/castacks/airstack-modules-index). +- **Stacks** (`stacks/`) are self-contained topology folders — pinned + `modules.repos`, plain XML entry launch files, and a CI-observed + `wiring.md` graph baseline. Wiring that used to be spread across per-layer + `*_bringup` packages (`local_bringup`, `onboard_all`, and the + `local*.launch.xml` variant files) now lives in one place: the selected + stack's entry launch file. +- Module provenance: asm_dfm2_disturbances originates from the DFM2 + ("don't fool me twice") AirStack fork, hand-built as the pilot module + before the tooling existed (its FRICTION_LOG.md records every manual + step; the port drops `omni.isaac.dynamic_control` in favor of the PhysX + simulation interface). asm_optitrack was extracted from the trunk + OptiTrack PR series (#359/#374/#375/#376) with git history preserved; its + unit/integration/e2e tests run in the module's own CI. +- **Fleets** (`config/fleets/`) declare who exists, which vehicle, which + stack, and which ground hosts run split-stack halves: + `airstack up --fleet `. + +### Launch-path changes (breaking) + +- **`AUTONOMY_ROLE` is removed.** The env-var role dispatch + (`full`/`onboard`/`offboard`) is gone; a set `AUTONOMY_ROLE` is a preflight + hard error (as is setting it alongside `--stack`, which previously let the + stack silently win). Stacks are the only dispatch: `airstack up --stack + [:]`, defaulting to `full_default`. The per-role launch trees + (`onboard_all/`, `onboard_local_offboard_global/`) are deleted, the + wrap-then-flatten migration is complete (every reference stack composes + flat module-launch includes; the launch-lint allowlist is down to the two + deliberately wrapped blocks: `interface.launch.py` and the DDS-router/ + gossip helpers). Migration map: + + | Before | Now | + |---|---| + | no role set / `AUTONOMY_ROLE=full` | `full_default` (default; machine-proven graph-identical) | + | `full` + `local_droan_cpu.launch.xml` variant | `--stack full_droan_cpu` (variant file deleted; wiring captured from it before deletion) | + | `local_macvo_obstacle_avoidance.launch.xml` variant | `--stack full_macvo` — the variant was broken three ways (unprefixed args silently ignored, stale disparity topic, `launch_macvo` never enabled); the stack fixes all three | + | `AUTONOMY_ROLE=onboard` (no split) | `--stack lite_default` (on the desktop profile the onboard role was unreachable anyway — `robot-desktop` hardcoded `full`) | + | `onboard`/`offboard` split pair | `--stack lite_offload_global:onboard` / `:offboard` | +- The split onboard/offboard deployment is now the `lite_offload_global` + stack (`:onboard` / `:offboard` entries) bridged per its `bridge.yaml`; + the DDS-router config is generated by `tools/gen_dds_router.py` rather + than hand-maintained. The generated bridge deliberately drops the legacy + split's `set_trajectory_mode` crossing (control-mode topics may not cross + a bridge — `airstack doctor` hard gate). +- Module launch files are remap-free and declare prefixed, described + arguments with canonical defaults; generically named arguments (like + `config_file`) were renamed with per-module prefixes because ROS 2 launch + configurations are global across includes. +- Shared DDS-router configs moved out of the per-role `onboard_all/` tree up + to `autonomy_bringup/config/`. +- **OptiTrack activation changed:** trunk's + `overrides/isaac-optitrack-simulation.env` and the `LAUNCH_NATNET` toggle + are gone (a set `LAUNCH_NATNET` draws a preflight warning). Mocap is + brought up by a stack that includes `natnet_ros2` unconditionally — the + asm_optitrack module's `test_stack/` is the reference. +- `SKIP_MACVO` / `SKIP_TENSORRT` build args removed from `Dockerfile.robot` + along with the payload they gated; MAC-VO deps enter an image only via + `airstack module lock --build`. `stacks/full_macvo` includes the module's + own `macvo.launch.xml` (the in-tree `perception/macvo_ros2` copy is + deleted; the module preserves its git history, and fixes the previously + hardcoded `camera_info` subscription to honor its topic parameter). + +### Added + +- `airstack up --stack [:]` stack dispatch with 5 reference + stacks under `stacks/` (`full_default`, `full_droan_cpu`, `full_macvo`, + `lite_default`, `lite_offload_global`), each carrying pinned + `modules.repos` and a CI-observed `wiring.md` graph baseline +- Module CLI — `airstack module add --version ` (branches + refused; local paths allowed), `list|sync|remove|create --in-tree|doctor`, + workspace overlay of module packages, and auto-generated module compose + overrides included by `airstack up`; Docker module layers composed via + `modules.lock` (`airstack module lock --build`) +- Fleet system: `airstack up --fleet ` driven by + `config/fleets/*.yaml` (identity, vehicle from `config/vehicles/`, stack + selection, spawns) with `hosts:` split-stack placement onto ground hosts; + `airstack fleet list|generate` per-robot compose for heterogeneous fleets; + `airstack sync` reconciles `airstack.yaml` (modules, external stack repos, + fleet validation) +- `airstack doctor [--live|--snapshot] [--stack NAME]` — observe-and-report + checks with exactly two hard gates (module dependency-conflict gate; + bridge gate: no control-setpoint / trajectory-group topics may cross a + split-stack bridge); `--live` diffs the RUNNING ROS graph against the + stack's committed `wiring.md` +- `airstack stack list|new |diff
` (diff compares + generated wiring, not launch XML) +- `tests/meta/` contract-test tier (unit mark) pinning the CLI/docs/stack + contracts, plus the `wiring` system-test mark: an observed wiring snapshot + of the running graph drift-checked against the stack's committed + `stacks//wiring.md` +- Split-stack bridging: `stacks/lite_offload_global/bridge.yaml` explicitly + lists every boundary crossing and `tools/gen_dds_router.py` generates the + DDS-router config from it deterministically (`--check` enforces the bridge + hard gate) +- New docs: Modules, Stacks, Fleets, Module CI guides, the generated + Module & Stack Catalog marketplace, and the Modular AirStack Walkthrough +- Intent flags on `airstack up` — `--sim isaac|airsim|simple`, `--robots N`, + `--headless`, `--play`/`--no-play`, `--no-autolaunch`, `--wait`, + `--dry-run` — deriving the coordinated env-var sets as exported leaf + values, with a resolved-config banner and a per-run + `.airstack/runs//effective_config.env` dump; contract-tested +- `airstack ready` (and `airstack up --wait`): staged flight-readiness gates + mirroring the system-test budgets — containers → sim `/clock` → per-robot + sentinel nodes → PX4 MAVROS-connected + `local_position/odom` streaming — + with per-gate diagnostics and `--json` for scripts +- Preflight validation in `airstack up` on resolved configuration (env > + `--env-file` > `.env`): one-simulator guard, `NUM_ROBOTS>1` vs + single-drone Isaac script as a named hard error, missing images listed + with an `image-pull` hint, missing `omni_pass.env` / empty Pegasus + submodule / Docker < 29 surfaced on the host + (`AIRSTACK_SKIP_PREFLIGHT=1` downgrades errors to warnings) +- tmux pane output mirrored to container stdout via shared `.tmux.conf` + hooks, so `docker logs` / `airstack logs` show colcon builds, + `ros2 launch` output, sim loading, and crashes +- simple-sim as a first-class simulator: `airstack up --sim simple` and a + `simple_sim` smoke-test mark (it had been broken since the ROS Jazzy + migration — its container sourced a Humble path — and is fixed) +- Automatic `unit-tests.yml` PR gate on `ubuntu-latest`, plus + `run_meta.json` outcome metadata so reports distinguish completed + simulation campaigns from collection errors, empty selections, timeouts, + and cancellations +- Feature notebook workflow (`use-feature-notebook` skill): gitignored + `notebook/NNN-feature-slug/` entries whose `design_spec.md` and + `results_summary.md` populate feature PR descriptions +- Battery and telemetry display in the GCS control panel (voltage and + percentage per robot when the MAVROS battery topic is bridged) +- `TARGET_ARCH` build arg (default `x86_64`) in `Dockerfile.robot`; + `docker-compose.yaml` passes `TARGET_ARCH: aarch64` to the `voxl` and + `l4t` real-robot image builds +- `ros-${ROS_DISTRO}-mavros-extras` in the robot image (provides the + vision_pose plugin used for external-pose deployments) +- `overrides/l4t-px4-realrobot.env` — site-agnostic deployment override for + a single real PX4 robot on a Jetson (aarch64/l4t) +- `integration` test tier (`tests/integration/`, `integration` mark) with a + shared `robot_autonomy_stack` fixture (robot container, no sim/GPU) +- `waypoint_flight` system test: takeoff → ordered waypoint route via + `NavigateTask` → land, judged on the odometry track by the standalone + `tests/waypoint_checker.py`; the standard acceptance check after + integrating or swapping a planner module + +### Changed + +- `robot-desktop` image slimmed 17.1 GB → ~6 GB (−65%) by moving MACVO's + torch/TensorRT/weights into the `asm_macvo` module Docker layer; a further + dependency purge removed unused apt/pip packages (−152 MB) and `droan_gl`'s + GL dependencies are declared explicitly +- Isaac launch scripts deduplicated onto a shared `pegasus_app.PegasusApp` + base: the scripts become scenario declarations (~40–170 lines each, net + −438 lines) with hooks for NatNet/scene-import extras; behavior verified + by full system-test parity. `ISAAC_SIM_HEADLESS` and + `ISAAC_SIM_LIVESTREAM` work uniformly in every launch script (each was + honored by only half of them before) +- Launch-workflow docs corrected against actual behavior: `ISAAC_SIM_SCENE` + (nonexistent) replaced by `ISAAC_SIM_SCRIPT_NAME`/`ISAAC_SIM_GUI`, + getting-started reflects the paused-by-default sim and Foxglove UI, isaac + docker.md defaults match `.env`, ms-airsim MAVROS ports/FOV/vehicle naming + fixed +- Unit-test documentation matches the co-located layout: C++ gtests run via + `colcon test` under the `build_packages` mark; Python via the root harness + (`conftest.py` applies the `unit` mark by file location) +- Ephemeral CI GPU runners spawn via NVIDIA OSMO as a drop-in replacement + for the earlier OpenStack-Nova backend: the GitHub side (labels, JIT + tokens, fork guard) is unchanged; only the spawn target moved. The OSMO + service-account token plays the old application-credential role, + `osmo workflow exec` replaces SSH-via-floating-IP debugging, and the + runner image prebakes what cloud-init used to install at boot +- Pegasus launch scripts drive lidar through the RTX OmniLidar API + (`add_rtx_lidar_subgraph`) in place of the Ouster graph path, with ROS + topics reconciled (raw cloud on `…/sensors/ouster/point_cloud_raw`, + filtered on `…/sensors/ouster/point_cloud`) +- The `airstack-osmo` SSH config block for OSMO IDE sessions is + `StrictHostKeyChecking no` + `UserKnownHostsFile /dev/null` (replacing + `accept-new`); users with the earlier block should replace it and run + `ssh-keygen -R "[localhost]:2200"` once +- Default system-test `--sim` is `isaacsim`; pass `--sim msairsim` to opt in +- `-m build_packages` CI runs pull `cache_*` images instead of baking sim + images; `docker-build.yml` retags unchanged images on VERSION bumps + (content fingerprint) instead of always rebuilding +- Automatic OSMO validation runs the pull-only `build_packages` gate on + every PR update; GPU simulation campaigns are selected through `/pytest` + or `workflow_dispatch` +- `robot-l4t` compose service knobs are env-overridable (`FCU_URL`, rosbag + path via `BAG_STORAGE_PATH`); `FCU_URL` unquoted so the literal serial + path reaches MAVROS +- `zed-l4t` image: ZED SDK 4.2 → 5.2 with coupled ROS deps (`zed_msgs` + 5.2.1, `point_cloud_transport(_plugins)` 4.x, `backward_ros`) +- Unit tests are defined by `tests/colcon_unit_test_packages.yaml` +- Repo-wide relicense to **BSD 3-Clause Clear** (vendored packages keep + their upstream licenses); `airstack_msgs` stabilized at 1.0.0; every + package.xml carries a real maintainer and description (contract-tested) +- `DOCKER_IMAGE_BUILD_MODE=prebuilt` is a tag discriminator only; a real + prebuilt-workspace image stage is future work +- The repository CHANGELOG.md is removed in favor of this page — all + change records live here, per version +- **Docs policy: standalone snapshots.** Feature docs describe only the + current system; all change-relative language (including RFC citations) + coalesces here. A full docs audit applied the policy and corrected pages + that had drifted from the code, notably: bag recording is NOT + auto-triggered at takeoff (the recorder starts idle; toggle via + `/{robot_name}/bag_record/set_recording_status`); `tracking_point` / + `look_ahead` carry `airstack_msgs/msg/Odometry` (older docs said + `geometry_msgs/PointStamped`) and trajectory topics are + `airstack_msgs/TrajectoryXYZVYaw`; MAVROSInterface targets any + MAVLink-compatible FC (the documented Ascent/Ardupilot specificity does + not exist in code); the RobotInterface command topic is `cmd_pose`; the + DDS-router allowlist table is regenerated from the real config; the + Jetson install flow is `./airstack.sh setup` + the `robot-l4t` service. + Two orphaned pages documenting the behavior-tree framework + (`behavior_tree`, `behavior_executive` — packages removed in an earlier + release, PR #332; only `behavior_tree_msgs` remains) were deleted; the + behavior layer is `drone_safety_monitor` with mission sequencing via + GCS-sent task goals + +### Removed + +An audit removed dead or superseded code wholesale. Anything here is +recoverable from git history, and hardware-specific capabilities return as +out-of-trunk modules: + +- The `AUTONOMY_ROLE` launch dispatch and its per-role launch trees — + stacks are the only launch path +- **MACVO extracted to** [castacks/asm_macvo](https://github.com/castacks/asm_macvo); + **OptiTrack/NatNet extracted to** + [castacks/asm_optitrack](https://github.com/castacks/asm_optitrack) + (client, PX4 external-vision fusion, NatNet emulator + Isaac wrapper, + e2e/integration tests, env overrides) +- `px4_interface` + vendored `px4_msgs` — a native PX4 uXRCE-DDS interface + is tracked as a fresh design in + [#387](https://github.com/castacks/AirStack/issues/387); MAVROS remains + the flight interface +- `waypoint_interface`, `attitude_controller`(+`_msgs`) — dead code +- The RQT/RViz GUI set: `rviz_behavior_tree_panel` (with the `xdot_cpp` + submodule), `rqt_behavior_tree_command`, `rqt_behavior_tree`, `rqt_gcs`, + `rqt_airstack_control_panel` — Foxglove is the GCS surface +- Sensors-layer hardware packages `camera_param_server`, + `gimbal_stabilizer`, `sensor_interfaces` + (`lidar_point_cloud_filter` remains) +- WinTAK / TAK integration (`ros2tak_tools`, CLI plumbing, GCS image + dependencies) — can return as a module +- Isaac Sim `standalone_examples` copies, stale robot-docker helpers, the + `ensemble_planner` skeleton, the Gazebo parallel-bringup tree, and + pre-co-location test scaffolding +- trajectory_library's vestigial rqt selector (catkin-era GUI source, + `plugin.xml`, `setup.py`, launcher script — never installed by its + CMakeLists), the orphaned behavior-tree docs images, and an empty + integration-testing stub page +- `tests/goldens/wiring/` — wiring baselines live per-stack as + `stacks//wiring.md` + +### Fixed + +- `airstack up` guards (one-simulator, URDF pairing) validated `.env` only + and were bypassed by `--env-file`; they now check the resolved + configuration +- `pytest tests/` collects the co-located unit tests before mark filtering + (CI previously collected 97 of 252 items, so the Python unit tests ran + nowhere); empty CI pytest arguments no longer recurse the repository; + non-comparable artifacts are reported instead of false 0% results +- `barebones_pegasus_launch.py` crashed with `NameError: os`; + `isaac-sim-livestream` produced a black stream with multi-drone scripts; + `NATNET_BODY_NAME`/`NATNET_TARGET_NAME` overrides now work +- Isaac Sim image: PX4 `ubuntu.sh` no longer fails dpkg configure on the + NVIDIA base; robot image pins `pytest<8.1` and disables `launch_testing` + for colcon unit tests +- Robot identity: a pre-set `ROBOT_NAME` is honored; the name-map catch-all + maps to `unknown_robot` (valid ROS namespace token) and logs a warning + naming both fixes; inert `ROBOT_NAME`/`ROS_DOMAIN_ID` lines dropped from + `overrides/l4t-px4-realrobot.env` +- l4t robot image: dustynv's `/ros_entrypoint.sh` replaced with a + passthrough so stale prebuilt `fastcdr` libs no longer crash apt-built + nodes like MAVROS; the GeographicLib `egm96-5` geoid is asserted at build + time (MAVROS dies at startup without it) +- Bag recording: `RECORD_BAGS=true` now actually starts the recorder on a + robot; recorder status is bridged in the correct direction so GCS + indicators work; `ros2 bag record --exclude` updated for Jazzy's + `--exclude-regex` (multiple excludes alternated into one regex) +- OptiTrack/NatNet: `NATNET_SERVER_IP` is forwarded to the robot container; + the default tracked body matches the emulator; EKF2 external-vision + parameters are passed as `PX4_PARAM_*` so PX4 actually fuses mocap; + external-vision tuning corrected from real-flight bags (`EKF2_EV_DELAY` + 7.0, `EKF2_EVP_NOISE` 0.05); an unrecognized `connection_type` fails at + startup instead of silently falling back; MODELDEF drone-body count is + cross-checked against `NUM_ROBOTS` after the handshake; the emulator is + installed as a Kit extension so launch scripts can import it + +## 0.19.0 — 2026-08-22 + +The launch-workflow and CI-infrastructure release preceding the modular +transition: + +- Intent flags on `airstack up` (`--sim isaac|airsim`, `--robots N`, + `--headless`, `--play`/`--no-play`, `--no-autolaunch`, `--wait`, + `--dry-run`) deriving coordinated env-var sets, with a resolved-config + banner and per-run `effective_config.env` dump +- `airstack ready` / `airstack up --wait`: staged flight-readiness gates + (containers → sim `/clock` → sentinel nodes → PX4 armable) with `--json` +- Preflight validation on the resolved configuration (one-simulator guard, + robot-count vs Isaac script, missing images/credentials surfaced on host) +- OSMO-backed ephemeral CI GPU runners; automatic `unit-tests.yml` PR gate; + `run_meta.json` outcome metadata for honest metrics comparison +- OptiTrack external-vision configurations for sim (NatNet emulator + PX4 + EKF2 mocap fusion, e2e-tested) and real Jetson robots +- tmux pane output mirrored to `docker logs`; feature-notebook workflow; + Isaac launch scripts deduplicated onto a shared `PegasusApp` base + +## 1.0.0 — 2024-12-19 + +First official public release. + +- Docker image `robot-l4t` for Jetson AGX; automatic Isaac Sim scene + load/play; random walk planner; DROAN trajectory-library local planner; + initial multi-robot GCS visualization +- Isaac Sim 4.1.0 → 4.2.0; unified docker image naming on AirStack's + version diff --git a/docs/robot/autonomy/behavior/behavior_executive.md b/docs/robot/autonomy/behavior/behavior_executive.md deleted file mode 100644 index 61d849279..000000000 --- a/docs/robot/autonomy/behavior/behavior_executive.md +++ /dev/null @@ -1,19 +0,0 @@ -# Behavior Executive - -The behavior executive reads which actions are active from the behavior tree and implements the behavior which these actions should perform and sets the status of the actions to SUCCESS, RUNNING, or FAILURE. It also sets the status of conditions as either SUCCESS or FAILURE. - -A typical way of implementing the behavior for an action is the following in the 20 Hz timer callback: - -``` -if(action->is_active()){ - if(action->active_has_changed()){ - // This is only true when the when the action transitions between active/inactive - // so this block of code will only run once whenever the action goes from being inactive to active. - // You might put a service call here and then call action->set_success() or action->set_failure() - // based on the result returned by the service call. - } - - // Code here will get executed each iteration. - // You might call action->set_running() while you are doing work here. -} -``` \ No newline at end of file diff --git a/docs/robot/autonomy/behavior/behavior_tree.md b/docs/robot/autonomy/behavior/behavior_tree.md deleted file mode 100644 index bd7577539..000000000 --- a/docs/robot/autonomy/behavior/behavior_tree.md +++ /dev/null @@ -1,95 +0,0 @@ -# Behavior Trees - -Defines how a task in terms of conditions and actions which the user -implements. - -Other types of nodes, control flow and decorator nodes, control which -conditions will be checked and which actions will be activated. - -Nodes have statuses of either SUCCESS, RUNNING or FAILURE. - -![](./media/image1.png) - -## Why Behavior Trees? - -Maintainable - Easy to modify - -Scalable - Parts of sub-trees are modular and can be encapsulated - -Reusable - Sub-trees can be reused in different places - -Clear visualization and interpretation - -## Types of Nodes - -- **Execution Nodes** - - Condition Nodes - - Action Nodes -- **Decorator Nodes** - - Not Node -- **Control Flow Nodes** - - Sequence Nodes - - Fallback Nodes - -### Execution Nodes - Condition Nodes - -Condition nodes have a status of either SUCCESS or FAILURE - -![](./media/image2.png) ![](./media/image3.png) - -### Execution Nodes - Action Nodes - -Action nodes can either be active or inactive - -An inactive node's status is not checked by the behavior tree, it is -shown in white - -below - -An active node's status is checked, it can either be SUCCESS (green), -RUNNING (blue) or FAILURE (red) - -![green](./media/image4.png) ![white](./media/image5.png)![blue](./media/image6.png)![red](./media/image7.png) - -### Decorator Nodes - Not Nodes - -The not node must have one condition node has a child and inverts the -status of the child. - -If the child's status is SUCCESS, the not node's status will be FAILURE. - -If the child's status is FAILURE, the not node's status will be SUCCESS. - -![](./media/image8.png) - -### Control Flow Nodes - Fallback Nodes - -These nodes are shown with a ? - -This node returns FAILURE if and only if all of its children return -FAILURE - -If one of its children return RUNNING or SUCCESS, it returns RUNNING or -SUCCESS and no subsequent children's statuses are check - -Below shows a typical example, where an action will only be performed if -all of the preceding conditions are false. In this case a drone will only be -armed if it is not already armed, it is in offboard mode and it is stationary - -![](./media/image8.png) - -### Control Flow Nodes - Sequence Nodes - -These nodes are shown with a "-\>" - -This node returns SUCCESS if and only if all of its children return -SUCCESS - -If one of its children return RUNNING or FAILURE, it returns RUNNING or -FAILURE and no subsequent children's statuses are check - -Below shows a typical example where preceding conditions must be true in -order for an action to be performed. In this case the drone will land if the IMU -times out and it is in offboard mode - -![](./media/image8.png) diff --git a/docs/robot/autonomy/behavior/index.md b/docs/robot/autonomy/behavior/index.md index 1515d5d2a..170d40b04 100644 --- a/docs/robot/autonomy/behavior/index.md +++ b/docs/robot/autonomy/behavior/index.md @@ -3,8 +3,17 @@ The behavior module is responsible for the high-level decision making of the rob ## Launch Behavior modules ship their own canonical launch files and are composed by -the stack entry file (the legacy `behavior_bringup` package was removed with -the AUTONOMY_ROLE dispatch), e.g. +the stack entry file, e.g. `ros2 launch drone_safety_monitor drone_safety_monitor.launch.xml` — see `stacks/full_default/launch/stack.launch.xml` for the composed wiring. +## Modules + +- **`drone_safety_monitor`** — the safety executive: watches the state + estimate for timeouts and issues safety commands. It runs onboard so the + robot can failsafe even if every ground link is lost. + +Task goals (takeoff, land, explore, navigate) are sent by the operator from +the GCS to the [task executors](../tasks.md) in the global and local layers; +see [System Architecture — Task Cascade](../system_architecture.md#task-cascade). + diff --git a/docs/robot/autonomy/behavior/media/image1.png b/docs/robot/autonomy/behavior/media/image1.png deleted file mode 100644 index 135407aed..000000000 Binary files a/docs/robot/autonomy/behavior/media/image1.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image10.png b/docs/robot/autonomy/behavior/media/image10.png deleted file mode 100644 index 81ecbd255..000000000 Binary files a/docs/robot/autonomy/behavior/media/image10.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image2.png b/docs/robot/autonomy/behavior/media/image2.png deleted file mode 100644 index efd5b5942..000000000 Binary files a/docs/robot/autonomy/behavior/media/image2.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image3.png b/docs/robot/autonomy/behavior/media/image3.png deleted file mode 100644 index 6b7f368d3..000000000 Binary files a/docs/robot/autonomy/behavior/media/image3.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image4.png b/docs/robot/autonomy/behavior/media/image4.png deleted file mode 100644 index ba20ef619..000000000 Binary files a/docs/robot/autonomy/behavior/media/image4.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image5.png b/docs/robot/autonomy/behavior/media/image5.png deleted file mode 100644 index 35647b040..000000000 Binary files a/docs/robot/autonomy/behavior/media/image5.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image6.png b/docs/robot/autonomy/behavior/media/image6.png deleted file mode 100644 index 3aefd3b41..000000000 Binary files a/docs/robot/autonomy/behavior/media/image6.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image7.png b/docs/robot/autonomy/behavior/media/image7.png deleted file mode 100644 index ae0061e02..000000000 Binary files a/docs/robot/autonomy/behavior/media/image7.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image8.png b/docs/robot/autonomy/behavior/media/image8.png deleted file mode 100644 index c6a313d6c..000000000 Binary files a/docs/robot/autonomy/behavior/media/image8.png and /dev/null differ diff --git a/docs/robot/autonomy/behavior/media/image9.png b/docs/robot/autonomy/behavior/media/image9.png deleted file mode 100644 index 83265b267..000000000 Binary files a/docs/robot/autonomy/behavior/media/image9.png and /dev/null differ diff --git a/docs/robot/autonomy/dds_router.md b/docs/robot/autonomy/dds_router.md index 085710696..5072cee3c 100644 --- a/docs/robot/autonomy/dds_router.md +++ b/docs/robot/autonomy/dds_router.md @@ -70,12 +70,13 @@ The launch file recognises the same `$(...)` token syntax used in ROS 2 XML laun ``` -!!! note "Renamed launch arguments (RFC #379 §4)" - The arguments were renamed from the generic `config_file` / `args` to the - prefixed `dds_router_config_file` / `dds_router_args` — generic launch - configurations leak across sibling includes in the same launch scope. The - old names still work as **deprecated aliases** (the prefixed name wins - when both are set); update external callers when convenient. +!!! note "Prefixed launch arguments" + The canonical arguments are the prefixed `dds_router_config_file` / + `dds_router_args` — generic names like `config_file` leak across sibling + includes in the same launch scope, because ROS 2 launch configurations + are global. The generic `config_file` / `args` names are accepted as + **deprecated aliases** (the prefixed name wins when both are set); + prefer the prefixed names in all callers. ### 2 — Config inheritance via `extends:` @@ -112,8 +113,7 @@ some_key: !reset **Location:** [`robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml`](../../../robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml) Selected by the `full_*` stacks and `lite_default` (their entry files pass it -to `interpolate_dds_router.launch.py`). Historically this lived at -`onboard_all/config/` under the removed AUTONOMY_ROLE dispatch. +to `interpolate_dds_router.launch.py`). **Participants:** @@ -126,17 +126,24 @@ to `interpolate_dds_router.launch.py`). Historically this lived at | Topic / Service | |---| +| `rt//sensors/ouster/point_cloud` | +| `rt//vdb_mapping/vdb_map_visualization` | +| `rt//sensors/front_stereo/{left,right}/image_rect` + `camera_info` | +| `rt//perception/stereo_image_proc/point_cloud` | | `rt//odometry_conversion/odometry` | -| `rt//interface/mavros/global_position/raw/fix` | -| `rt//behavior/behavior_tree_commands` | -| `rt//behavior/behavior_tree_graphviz` | +| `rt//interface/mavros/global_position/global` | +| `rt//trajectory_controller/trajectory_vis` | +| `rt//global_plan` | | `rq+rr//interface/robot_command` | | `rq+rr//trajectory_controller/set_trajectory_mode` | | `rq+rr//takeoff_landing_planner/set_takeoff_landing_command` | | `rq+rr//behavior/global_plan_toggle` | | `rt//bag_record/bag_recording_status` | | `rt//bag_record/set_recording_status` | -| `rt//fixed_trajectory_generator/fixed_trajectory_command` | + +Gossip peer profiles are deliberately **not** in this allowlist — they are +bridged by the dedicated gossip DDS router on domain 99 (bridging them here +too would cause message amplification). --- @@ -149,10 +156,9 @@ is the authoritative boundary document, and `tools/gen_dds_router.py` generates `.airstack/generated/dds_router.lite_offload_global.yaml` from it (loaded by the stack's `onboard` entry). -The generated config replaced the legacy split's committed -`onboard_local_offboard_global/config/dds_router.yaml` (removed with the -AUTONOMY_ROLE dispatch) — deliberately minus the `set_trajectory_mode` -crossing that config carried: command authority stays onboard +The generated config deliberately contains no `set_trajectory_mode` +crossing: command authority stays onboard — control-mode and +trajectory-group names may never cross a split-stack bridge (`airstack doctor` hard gate #2). --- diff --git a/docs/robot/autonomy/global/index.md b/docs/robot/autonomy/global/index.md index cbd4a423a..0719bfe59 100644 --- a/docs/robot/autonomy/global/index.md +++ b/docs/robot/autonomy/global/index.md @@ -4,11 +4,10 @@ The global packages include global world models and planners. ## Launch -The global layer is composed by the stack entry file (the legacy -`global_bringup` layer launch was removed with the AUTONOMY_ROLE dispatch): -the trunk stacks include `vdb_mapping_ros2.py` (with +The global layer is composed by the stack entry file: the trunk stacks +include `vdb_mapping_ros2.py` (with `global_bringup/config/vdb_params.yaml`) and `random_walk_planner.launch.xml` directly — see `stacks/full_default/launch/stack.launch.xml`. The `global_bringup` package -remains as the owner of the cross-package VDB config files. +owns the cross-package VDB config files. diff --git a/docs/robot/autonomy/global/planning/index.md b/docs/robot/autonomy/global/planning/index.md index 1a0b473ef..2fc678764 100644 --- a/docs/robot/autonomy/global/planning/index.md +++ b/docs/robot/autonomy/global/planning/index.md @@ -96,5 +96,5 @@ The random walk planner replans when the robot is getting close to the goal. The ### Exploration planner -The `exploration` package (`robot/ros_ws/src/global/planners/exploration`) is a frontier-based geometric exploration planner. It is kept as the intended alternative to `random_walk` for planner selection from the RViz Tasks Panel (future). +The `exploration` package (`robot/ros_ws/src/global/planners/exploration`) is a frontier-based geometric exploration planner — the intended alternative to `random_walk` for planner selection from the RViz Tasks Panel (future). diff --git a/docs/robot/autonomy/integration_checklist.md b/docs/robot/autonomy/integration_checklist.md index 5fd986db9..b9c7e24b8 100644 --- a/docs/robot/autonomy/integration_checklist.md +++ b/docs/robot/autonomy/integration_checklist.md @@ -2,7 +2,7 @@ This document provides a comprehensive checklist and guidelines for integrating new modules into the AirStack autonomy stack. -> **Canonical names, types, QoS, and frames now live in the versioned +> **Canonical names, types, QoS, and frames live in the versioned > [Interface Conventions Specification](interface_conventions.md)** (v1.0.0) — > cite that spec for interchange-point contracts; this page remains the > step-by-step integration workflow. @@ -79,10 +79,10 @@ These topics are used across multiple modules and should be used when applicable |-------|------|---------|-------| | `/[robot]/odometry` | nav_msgs/Odometry | Primary state estimate | Perception → All | | `/[robot]/global_plan` | nav_msgs/Path | Global waypoint path | Global → Local | -| `/[robot]/trajectory_controller/trajectory_segment_to_add` | airstack_msgs/TrajectorySegment | Local trajectory commands | Local Planner → Controller | -| `/[robot]/trajectory_controller/trajectory_override` | airstack_msgs/TrajectoryOverride | Direct trajectory override | Behavior → Controller | -| `/[robot]/trajectory_controller/look_ahead` | geometry_msgs/PointStamped | Look-ahead point for planning | Controller → Local Planner | -| `/[robot]/trajectory_controller/tracking_point` | geometry_msgs/PointStamped | Current tracking point | Controller → All | +| `/[robot]/trajectory_controller/trajectory_segment_to_add` | airstack_msgs/TrajectoryXYZVYaw | Local trajectory commands | Local Planner → Controller | +| `/[robot]/trajectory_controller/trajectory_override` | airstack_msgs/TrajectoryXYZVYaw | Direct trajectory override | Task executors → Controller | +| `/[robot]/trajectory_controller/look_ahead` | airstack_msgs/Odometry | Look-ahead point for planning | Controller → Local Planner | +| `/[robot]/trajectory_controller/tracking_point` | airstack_msgs/Odometry | Current tracking point | Controller → All | | `/[robot]/trajectory_controller/trajectory_completion_percentage` | std_msgs/Float32 | Trajectory progress | Controller → Planners | | `/[robot]/interface/mavros/cmd/takeoff` | mavros_msgs/CommandTOL | Takeoff command | Behavior → Interface | | `/[robot]/interface/cmd_vel` | geometry_msgs/Twist | Low-level velocity commands | Controller → Interface | @@ -137,8 +137,8 @@ These topics are used across multiple modules and should be used when applicable - **Outputs:** High-level commands, mode changes - **Topics:** - - `/[robot]/behavior/mission_state` - - `/[robot]/behavior/bt_status` + - `/[robot]/behavior/drone_safety_monitor/state_estimate_timed_out` + - `/[robot]/behavior/drone_safety_monitor/command` ### Task Action Server Naming Convention @@ -154,7 +154,7 @@ Examples: - `/{robot_name}/tasks/navigate` — NavigateTask - `/{robot_name}/tasks/coverage` — CoverageTask -Add the remap in the layer bringup launch file: +Add the remap in the module's launch file: ```xml /launch/*.launch.xml`) - [ ] Launch arguments use `$(env ROBOT_NAME)` for multi-robot support - [ ] Module namespace properly configured -- [ ] Module included in `autonomy_bringup` launch flow ### 5. Dependencies - [ ] All dependencies listed in `package.xml` -- [ ] Bringup package depends on your package - [ ] External dependencies documented in README - [ ] Dependencies available in Docker image (or documented for addition) @@ -550,6 +548,6 @@ docker stats airstack-robot-desktop-1 - [add-task-executor](../../../.agents/skills/add-task-executor) — Implementing a task executor action server - [integrate-module-into-layer](./../../../.agents/skills/integrate-module-into-layer) - — Adding a module to layer bringup + — Integrating a module into a stack - [test-in-simulation](../../../.agents/skills/test-in-simulation) — Testing procedures diff --git a/docs/robot/autonomy/interface/index.md b/docs/robot/autonomy/interface/index.md index e3aab7241..4a4a244ad 100644 --- a/docs/robot/autonomy/interface/index.md +++ b/docs/robot/autonomy/interface/index.md @@ -6,11 +6,11 @@ For example, for drones it converts the control commands from the autonomy stack ==TODO: This is not our diagram, must replace.== ![Interface Diagram](https://404warehouse.net/wp-content/uploads/2016/08/softwareoverview.png?w=800) -The code is located under `AirStack/ros_ws/src/robot/autonomy/interface/`. +The code is located under `robot/ros_ws/src/interface/`. ## Launch -Launch files are under `src/autonomy/interface/interface_bringup/launch`. +Launch files are under `robot/ros_ws/src/interface/interface_bringup/launch`. The main launch command is `ros2 launch interface_bringup interface.launch.py`. @@ -56,13 +56,13 @@ The RobotInterface node subscribes to: - `/$(env ROBOT_NAME)/interface/cmd_roll_pitch_yawrate_thrust` of type `mav_msgs/RollPitchYawrateThrust.msg` - `/$(env ROBOT_NAME)/interface/cmd_torque_thrust` of type `mav_msgs/TorqueThrust.msg` - `/$(env ROBOT_NAME)/interface/cmd_velocity` of type `geometry_msgs/TwistStamped.msg` -- `/$(env ROBOT_NAME)/interface/cmd_position` of type `geometry_msgs/PoseStamped.msg` +- `/$(env ROBOT_NAME)/interface/cmd_pose` of type `geometry_msgs/PoseStamped.msg` -All messages are in the robot's body frame, except `velocity` and `position` which use the frame specified by the message header. +All messages are in the robot's body frame, except `cmd_velocity` and `cmd_pose` which use the frame specified by the message header. ## MAVROSInterface -The available implementation in AirStack is called `MAVROSInterface` implemented in `mavros_interface.cpp`. It simply forwards the control commands to the Ascent flight controller (based on Ardupilot) using MAVROS. +The available implementation in AirStack is called `MAVROSInterface` implemented in `mavros_interface.cpp`. It forwards the control commands over MAVROS to any MAVLink-compatible flight controller (PX4 in simulation). ## Custom Robot Interface @@ -97,14 +97,16 @@ void your_callback_function(){ Should override all `virtual` functions in `robot_interface.hpp`: -- `cmd_attitude_thrust_callback` -- `cmd_rate_thrust_callback` -- `cmd_roll_pitch_yawrate_thrust_callback` -- `cmd_torque_thrust_callback` -- `cmd_velocity_callback` -- `cmd_position_callback` +- `attitude_thrust_callback` +- `rate_thrust_callback` +- `roll_pitch_yawrate_thrust_callback` +- `torque_thrust_callback` +- `velocity_callback` +- `pose_callback` - `request_control` - `arm` - `disarm` - `is_armed` - `has_control` +- `takeoff` +- `land` diff --git a/docs/robot/autonomy/interface_conventions.md b/docs/robot/autonomy/interface_conventions.md index f7dc4440b..80f8dcd35 100644 --- a/docs/robot/autonomy/interface_conventions.md +++ b/docs/robot/autonomy/interface_conventions.md @@ -4,11 +4,10 @@ This is the versioned specification of AirStack's **interchange points** — the narrow waists where modules meet: canonical topic/service/action names, -message types, QoS profiles, TF frames and units, and rate classes -([RFC #379 §4.2](https://github.com/castacks/AirStack/discussions/379)). It -upgrades the topic tables of the -[Module Integration Checklist](integration_checklist.md) into a citable -contract; the checklist remains the step-by-step integration workflow. +message types, QoS profiles, TF frames and units, and rate classes. It +is the citable contract behind the topic tables of the +[Module Integration Checklist](integration_checklist.md); the checklist +remains the step-by-step integration workflow. **Documentation, not enforcement.** This spec is documentation that modules *default to* and conformance tests check — it is never input to any wiring @@ -23,9 +22,8 @@ observed truth, and `airstack doctor --live` diffs reality against it. **Conventions in this table are verified against the observed graph** — types and QoS below come from `stacks/full_default/wiring.md` (the committed -wiring-snapshot of the running reference stack), not from memory. Where a -column reads differently from older docs (e.g. `tracking_point`'s message -type), the observed graph wins. +wiring-snapshot of the running reference stack), not from memory. Where any +other document disagrees with a column here, the observed graph wins. All names are relative to the robot namespace: canonical topic `odometry_conversion/odometry` means `/{robot_name}/odometry_conversion/odometry` @@ -42,7 +40,7 @@ at runtime (`ROBOT_NAME` namespacing is pushed by the launch preamble). `state` (~10–100 Hz), `sensor` (~10–30 Hz), `plan` (~0.1–2 Hz), `event` (on change / on command), `latched` (transient-local state). - **Placement** — `onboard-only` marks interchanges that must never cross a - machine boundary ([RFC #380 §2](https://github.com/castacks/AirStack/discussions/380)): + machine boundary: the **controller** and the **safety executive** stay on the vehicle so link loss leaves it able to failsafe. `doctor` **hard-errors** when `control_setpoint` or trajectory-group names appear in any split stack's @@ -54,7 +52,7 @@ at runtime (`ROBOT_NAME` namespacing is pushed by the launch preamble). ## 1. `sensors/*` — sensor naming convention Sensor topics are namespaced by sensor **id**: `sensors//`. -Sensor ids become first-class in the vehicle manifest (RFC #380 §1), where +Sensor ids are first-class in the vehicle manifest, where each id pairs the real driver with its sim representation; wiring snapshots normalize driver nodes to these ids so sim baselines diff cleanly against hardware bring-ups. @@ -111,7 +109,7 @@ Contrast §5. All names live under the `trajectory_controller/` namespace (served by relative name inside it). **None of these may appear in a `bridge.yaml`** — -doctor hard gate (RFC #379 §4, RFC #380 §2): `global_plan` crosses, +a doctor hard gate: `global_plan` crosses, trajectory commands don't. | Canonical name | Kind | Type | QoS | Rate class | Direction | @@ -119,7 +117,7 @@ trajectory commands don't. | `trajectory_controller/trajectory_override` | topic | `airstack_msgs/msg/TrajectoryXYZVYaw` | RELIABLE | event | any module → controller (replaces current trajectory) | | `trajectory_controller/trajectory_segment_to_add` | topic | `airstack_msgs/msg/TrajectoryXYZVYaw` | RELIABLE | plan | local planner → controller (appends) | | `trajectory_controller/set_trajectory_mode` | service | `airstack_msgs/srv/TrajectoryMode` | (service) | event | task servers → controller | -| `trajectory_controller/tracking_point` | topic | `airstack_msgs/msg/Odometry` | RELIABLE | state | controller → PID/planners (note: **airstack_msgs**, not nav_msgs; older docs saying `PointStamped` are wrong) | +| `trajectory_controller/tracking_point` | topic | `airstack_msgs/msg/Odometry` | RELIABLE | state | controller → PID/planners (note: **airstack_msgs**, not nav_msgs, and not `PointStamped`) | | `trajectory_controller/look_ahead` | topic | `airstack_msgs/msg/Odometry` | RELIABLE | state | controller → local planner | | `trajectory_controller/trajectory_completion_percentage` | topic | `std_msgs/msg/Float32` | RELIABLE | state | controller → task servers | @@ -185,7 +183,7 @@ intents, not control): a split stack lists the crossing actions in its | `behavior/drone_safety_monitor/command` | `std_msgs/msg/String` | RELIABLE | event | **onboard-only** | The safety executive (drone_safety_monitor + the interface's takeover path) -is marked **onboard-only** per RFC #380 §2: link loss must leave the robot +is marked **onboard-only**: link loss must leave the robot able to failsafe without any ground host in the loop. ## 10. `gossip` — multi-robot coordination @@ -223,25 +221,24 @@ them (frame plumbing drifts too). ## Versioning and deprecation This spec is **public API** even though nothing compiles against it — -modules' launch-arg *defaults* and the conformance tests encode it -(RFC #379 §8). Changing a canonical name, type, QoS profile, or frame +modules' launch-arg *defaults* and the conformance tests encode it. +Changing a canonical name, type, QoS profile, or frame convention requires: 1. a **semver-major** bump of this spec, 2. a **coexistence window** (old and new names both served/accepted), -3. a short **RFC in the registry repo** (`rfcs/` — the deprecation registry; - until the registry repo exists, RFCs live as GitHub Discussions like - [#379](https://github.com/castacks/AirStack/discussions/379) / - [#380](https://github.com/castacks/AirStack/discussions/380)). +3. a short **written proposal in the registry repo** (`rfcs/` — the + deprecation registry; until the registry repo exists, proposals live as + GitHub Discussions on the AirStack repo). Additions (new interchange points) are semver-minor and are discovered through drift reports: three forks patching the same tap point = a missing -convention (RFC #379 §11). The `doctor` hard-gate list (dep conflicts; -control/trajectory names in `bridge.yaml`) grows only through the same RFC -process. +convention. The `doctor` hard-gate list (dep conflicts; +control/trajectory names in `bridge.yaml`) grows only through the same +proposal process. ## Change log | Spec | Date | Change | |---|---|---| -| v1.0.0 | 2026-08-20 | Initial versioned spec, recorded from `full_default`'s observed wiring (P5-E3, RFC #379 §4.2). Known v2 candidates: plain `odometry` as the canonical state topic; a structured `global_map` interchange. | +| v1.0.0 | 2026-08-20 | Initial versioned spec, recorded from `full_default`'s observed wiring. Known v2 candidates: plain `odometry` as the canonical state topic; a structured `global_map` interchange. | diff --git a/docs/robot/autonomy/local/controls/index.md b/docs/robot/autonomy/local/controls/index.md index 1ffe7da95..8aab04daf 100644 --- a/docs/robot/autonomy/local/controls/index.md +++ b/docs/robot/autonomy/local/controls/index.md @@ -1,7 +1,10 @@ # Controls -Controls dictate the actuation of the robot. They are responsible for taking in sensor data and producing control commands. +Controls dictate the actuation of the robot: they close the loop between the planned trajectory and the robot's actual state, and publish control commands to the topics defined by the [Robot Interface](../../interface/index.md). -The controller should publish control commands directly to topics defined by the [Robot Interface](../../interface/index.md). +AirStack splits control into two nodes: -Currently the AirStack uses a custom controller called "Trajectory Controller". +- [**Trajectory Controller**](../../../../../robot/ros_ws/src/local/controls/trajectory_controller/README.md) (`trajectory_controller`) — a pure-pursuit trajectory manager that advances a **tracking point** and **look-ahead point** along the current trajectory (it is not itself a feedback controller) +- **PID Controller** (`pid_controller`) — a cascaded position/velocity PID that drives the drone toward the tracking point and publishes roll/pitch/yaw-rate/thrust commands to the interface + +Both are perpetual nodes and run onboard only — control never crosses a machine boundary (see the [Interface Conventions Specification](../../interface_conventions.md)). diff --git a/docs/robot/autonomy/local/index.md b/docs/robot/autonomy/local/index.md index bfb91dc57..050db46ef 100644 --- a/docs/robot/autonomy/local/index.md +++ b/docs/robot/autonomy/local/index.md @@ -3,8 +3,7 @@ The local module includes packages that are specific to the local autonomy of th ## Launch Local modules ship their own canonical launch files and are composed flat by -the stack entry file (the legacy `local_bringup` package was removed with -the AUTONOMY_ROLE dispatch): `takeoff_landing_planner`, the trajectory +the stack entry file: `takeoff_landing_planner`, the trajectory controller, `droan_gl`, and the PID controller are included directly — see `stacks/full_default/launch/stack.launch.xml` for the composed wiring. diff --git a/docs/robot/autonomy/local/planning/index.md b/docs/robot/autonomy/local/planning/index.md index a59789807..96face1c3 100644 --- a/docs/robot/autonomy/local/planning/index.md +++ b/docs/robot/autonomy/local/planning/index.md @@ -1,7 +1,12 @@ # Local Planning -Part of the local planner is the Waypoint Manager. +Local planners turn the coarse global plan into short, collision-free trajectory segments, reacting to obstacles the global map is too slow or too coarse to capture. They plan from the trajectory controller's look-ahead point and feed segments to it continuously. -The Waypoint Manager subscribes to the global waypoints and the drone's current position and publishes the next waypoint to the local planner. +AirStack's baseline local planner is DROAN, in two implementations: -We plan for this baseline to be DROAN. \ No newline at end of file +- [**DROAN GL**](../../../../../robot/ros_ws/src/local/planners/droan_gl/README.md) (`droan_gl`) — GPU-accelerated, true-sphere disparity expansion via OpenGL shaders; the default in the `full_default` stack +- [**DROAN Local Planner**](../../../../../robot/ros_ws/src/local/planners/droan_local_planner/README.md) (`droan_local_planner`) — the CPU implementation, selected by the `full_droan_cpu` stack + +Both are task executors serving `NavigateTask` at `/{robot_name}/tasks/navigate`. + +Specialized maneuvers are handled by the [Takeoff Landing Planner](../../../../../robot/ros_ws/src/local/planners/takeoff_landing_planner/README.md), and candidate trajectories come from the [Trajectory Library](../../../../../robot/ros_ws/src/local/planners/trajectory_library/README.md). diff --git a/docs/robot/autonomy/local/world_model/index.md b/docs/robot/autonomy/local/world_model/index.md index 5051a0efd..8429e8285 100644 --- a/docs/robot/autonomy/local/world_model/index.md +++ b/docs/robot/autonomy/local/world_model/index.md @@ -1 +1,9 @@ -# Local World Model \ No newline at end of file +# Local World Model + +Local world models give the local planner a fast, short-range obstacle representation built directly from sensor data — cheaper and lower-latency than the global map, at the cost of limited spatial extent. AirStack's local world model is disparity-based: + +- [**Disparity Expansion**](../../../../../robot/ros_ws/src/local/world_models/disparity_expansion/README.md) — C-space expansion of stereo disparity images by the robot radius +- [**Disparity Graph**](../../../../../robot/ros_ws/src/local/world_models/disparity_graph/README.md) — rolling window of expanded-disparity keyframes with their camera poses +- [**Disparity Graph Cost Map**](../../../../../robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md) — cost-map plugin answering collision-cost queries for the DROAN local planner + +The GPU planner `droan_gl` performs the expansion and graph internally on the GPU; the CPU pipeline uses these packages as separate nodes. diff --git a/docs/robot/autonomy/perception/index.md b/docs/robot/autonomy/perception/index.md index 194a91270..a0aed9b52 100644 --- a/docs/robot/autonomy/perception/index.md +++ b/docs/robot/autonomy/perception/index.md @@ -12,10 +12,9 @@ Perception forms the foundation of the autonomy stack by: ## Launch -Launch files are located under `robot/ros_ws/src/perception/perception_bringup/launch/` -(module launch files; the legacy `perception.launch.xml` layer wrapper was -removed with the AUTONOMY_ROLE dispatch). The stack entry files include them -directly: +Module launch files are located under +`robot/ros_ws/src/perception/perception_bringup/launch/`. The stack entry +files include them directly: ```bash ros2 launch perception_bringup stereo_image_proc.launch.xml ros2 launch perception_bringup topic_keepalive.launch.xml @@ -37,7 +36,7 @@ State estimation and related perception packages live under `robot/ros_ws/src/pe ### External pose (motion capture) -- [**OptiTrack (asm_optitrack module)**](../../optitrack.md) — NatNet mocap support (rigid-body poses from a Motive PC, PX4 external-vision fusion bridges) moved out of the trunk into the [asm_optitrack module](https://github.com/castacks/asm_optitrack). Add it with `airstack module add https://github.com/castacks/asm_optitrack --version `; see [AirStack Modules](../../../development/modules.md). +- [**OptiTrack (asm_optitrack module)**](../../optitrack.md) — NatNet mocap support (rigid-body poses from a Motive PC, PX4 external-vision fusion bridges), provided by the [asm_optitrack module](https://github.com/castacks/asm_optitrack). Add it with `airstack module add https://github.com/castacks/asm_optitrack --version `; see [AirStack Modules](../../../development/modules.md). ## Configuration diff --git a/docs/robot/autonomy/sensors/index.md b/docs/robot/autonomy/sensors/index.md index 6a9a7b5ef..66c12c74f 100644 --- a/docs/robot/autonomy/sensors/index.md +++ b/docs/robot/autonomy/sensors/index.md @@ -13,9 +13,8 @@ The sensors layer is responsible for: ## Launch Sensor modules ship their own canonical launch files and are composed by the -stack entry file under the `sensors` namespace (the legacy `sensors_bringup` -layer launch was removed with the AUTONOMY_ROLE dispatch). E.g. the trunk -stacks include: +stack entry file under the `sensors` namespace. E.g. the trunk stacks +include: ```bash ros2 launch lidar_point_cloud_filter lidar_point_cloud_filter.launch.xml @@ -41,7 +40,7 @@ Topic strings are parameterized with `$(env ROBOT_NAME)` in YAML; override `inpu - [**LiDAR point cloud filter**](#lidar-point-cloud-filter) (`lidar_point_cloud_filter`) — near-range sphere filter for `PointCloud2` - [**Gimbal (simulation)**](gimbal.md) — gimbal extension usage in simulation (documentation only; no robot-side package) -## LiDAR point cloud filter (`lidar_point_cloud_filter`){#lidar-point-cloud-filter} +## LiDAR point cloud filter **Package:** `robot/ros_ws/src/sensors/lidar_point_cloud_filter` diff --git a/docs/robot/autonomy/system_architecture.md b/docs/robot/autonomy/system_architecture.md index 7133f601e..ecdcfcfe0 100644 --- a/docs/robot/autonomy/system_architecture.md +++ b/docs/robot/autonomy/system_architecture.md @@ -43,7 +43,7 @@ They receive data over topics and publish results immediately — there is no external activation step. Most of the autonomy stack consists of perpetual nodes. -Examples: state estimator, VDB mapper, disparity expander, trajectory controller, behavior tree tick loop. +Examples: state estimator, VDB mapper, disparity expander, trajectory controller, safety monitor. ### Task Executors @@ -71,21 +71,22 @@ All task action servers are remapped to `/{robot_name}/tasks/{task_name}` by con ### Task Cascade -High-level tasks (sent by the behavior layer) cascade down through the stack: +High-level task goals (sent by the operator from the GCS — the Foxglove +robot-commands panel or the RViz Tasks Panel) cascade down through the stack: ```mermaid graph TD - BE[behavior_executive] -->|ExplorationTask| RW[random_walk_planner] + GCS[GCS operator] -->|ExplorationTask| RW[random_walk_planner] RW -->|NavigateTask| DG[droan_gl] DG -->|trajectory_segment_to_add| TC[trajectory_controller] - style BE fill:#cce5ff + style GCS fill:#cce5ff style TC fill:#cce5ff style RW fill:#d4edda style DG fill:#d4edda ``` -*Blue = perpetual node, green = task executor.* +*Blue = perpetual node / external client, green = task executor.* The global-layer task executor (e.g. `random_walk_planner`) decides *where* to go and delegates the actual flying to the local-layer task @@ -277,18 +278,18 @@ graph TB subgraph "Controllers" TrajControl[Trajectory Controller] - AttControl[Attitude Controller] + PIDControl[PID Controller] DROAN --> TrajControl TakeoffLanding --> TrajControl - TrajControl --> AttControl + TrajControl --> PIDControl end Perception -->|Odometry| DROAN Perception -->|Disparity| Disparity Global -->|Global Plan| DROAN - AttControl -->|Commands| Interface + PIDControl -->|Commands| Interface ``` **Key Modules:** @@ -307,7 +308,8 @@ graph TB - **Controllers:** - - `trajectory_controller`: Trajectory tracking + - `trajectory_controller`: Pure-pursuit tracking point / look-ahead management + - `pid_controller`: Cascaded PID producing attitude/thrust commands **Topics:** @@ -337,7 +339,7 @@ graph TB subgraph "Global Planners" RandomWalk[Random Walk Explorer] - Ensemble[Ensemble Planner] + Exploration[Exploration Planner] end Perception -->|Pose| VDBMap @@ -345,12 +347,12 @@ graph TB VDBMap --> Occupancy Occupancy --> RandomWalk - Occupancy --> Ensemble - Behavior -->|Goals| RandomWalk - Behavior -->|Goals| Ensemble + Occupancy --> Exploration + GCS[GCS Task Goals] --> RandomWalk + GCS --> Exploration RandomWalk --> GlobalPlan[Global Plan] - Ensemble --> GlobalPlan + Exploration --> GlobalPlan GlobalPlan --> Local ``` @@ -362,66 +364,55 @@ graph TB - **Planners:** - - `random_walk`: Random exploration planner + - `random_walk`: Random exploration planner (ExplorationTask executor) + - `exploration`: Frontier-based exploration planner (optional alternative) **Topics:** - **Subscribed:** - `/[robot]/odometry` - - `/[robot]/sensors/*/pointcloud` - - `/[robot]/behavior/mission_goal` + - `/[robot]/vdb_mapping/vdb_map_visualization` - **Published:** - `/[robot]/global_plan` - - `/[robot]/global/map` - - `/[robot]/global/occupancy` + - `/[robot]/vdb_mapping/vdb_map_pointcloud` ### Behavior Layer -**Purpose:** High-level mission execution and decision making. +**Purpose:** Onboard safety supervision. Mission-level sequencing is driven +by the operator from the GCS through [task executors](tasks.md); the +behavior layer's job is the part that must never depend on a ground link — +watching the robot's health and forcing a safe reaction when something +breaks. ```mermaid graph TB - Mission[Mission Manager] -->|Goals| BT[Behavior Tree] - BT -->|Evaluate| Conditions{Conditions} - Conditions -->|True| Actions[Actions] - Conditions -->|False| Fallback[Fallback] + Odom[State Estimate] --> SM[drone_safety_monitor] + SM -->|state_estimate_timed_out| Planners[Planners / Task Executors] + SM -->|safety command| Interface - Actions -->|Global Goals| Global - Actions -->|Local Commands| Local - Actions -->|Mode Changes| Interface - - subgraph "Behavior Tree Components" - BT - Conditions - Actions - Fallback - end - - GCS[Ground Control Station] -->|Commands| Mission - Autonomy[Autonomy State] --> BT + GCS[Ground Control Station] -->|Task goals| Tasks[Task Executors] + Tasks --> Global + Tasks --> Local ``` **Key Modules:** -- `behavior_tree`: Behavior tree framework -- `behavior_executive`: Mission execution engine +- `drone_safety_monitor`: Watches the state estimate for timeouts and issues + safety commands (onboard-only) **Topics:** - **Subscribed:** - `/[robot]/odometry` - - `/[robot]/interface/mavros/state` - - `/[robot]/trajectory_controller/trajectory_completion_percentage` - **Published:** - - `/[robot]/global/goal` - - `/[robot]/trajectory_controller/trajectory_override` - - `/[robot]/behavior/mission_state` + - `/[robot]/behavior/drone_safety_monitor/state_estimate_timed_out` + - `/[robot]/behavior/drone_safety_monitor/command` ## Complete Data Flow @@ -571,9 +562,10 @@ Specify input and output topics: ### 3. Configure Launch Integration -Add module to layer bringup with: +Give the module its own canonical launch file and include it from the stack +entry file (`stacks//launch/*.launch.xml`) with: -- Topic remapping +- Topic remapping (via launch arguments) - Namespace configuration - Parameter loading - Conditional launching (if needed) diff --git a/docs/robot/autonomy/tasks.md b/docs/robot/autonomy/tasks.md index 20345ea92..75d15aecb 100644 --- a/docs/robot/autonomy/tasks.md +++ b/docs/robot/autonomy/tasks.md @@ -8,16 +8,16 @@ See [System Architecture — Node Types](system_architecture.md#node-types-perpe ## Task Cascade -Behavior sends high-level task goals to global-layer task executors, which in turn delegate navigation to local-layer task executors: +The operator sends high-level task goals from the GCS (Foxglove robot-commands panel or RViz Tasks Panel) to global-layer task executors, which in turn delegate navigation to local-layer task executors: ```mermaid graph TD - BE[behavior_executive] -->|ExplorationTask| RW[random_walk_planner] + GCS[GCS operator] -->|ExplorationTask| RW[random_walk_planner] RW -->|NavigateTask| DG[droan_gl / droan_local_planner] DG -->|TrajectoryXYZVYaw| TC[trajectory_controller] ``` -All task action servers are remapped to `/{robot_name}/tasks/{task_name}` in the bringup launch files. +All task action servers are remapped to `/{robot_name}/tasks/{task_name}` in their module launch files. ## Task Action Types @@ -45,7 +45,7 @@ geometry_msgs/Point current_position **File:** `action/FixedTrajectoryTask.action` **Action server:** `/{robot_name}/tasks/fixed_trajectory` -**Implemented by:** *(not yet implemented)* +**Implemented by:** `trajectory_controller` package (`fixed_trajectory_task` node) Follow a pre-defined trajectory specified by shape type and parameters. With `loop: true`, the trajectory repeats until the task is canceled. @@ -272,7 +272,7 @@ See the [add-task-executor](../../../.agents/skills/add-task-executor) skill for 2. Create a new package under `robot/ros_ws/src/global/planners/` (or `local/planners/` if it is a navigation executor) 3. Implement the four action server callbacks: `handle_goal`, `handle_cancel`, `handle_accepted`, `execute` 4. In `execute()`, delegate navigation to `/{robot_name}/tasks/navigate` (NavigateTask) via an action client -5. Add a remap in the layer bringup launch file: `` +5. Add a remap in your module's launch file (included by the stack entry file): `` 6. Document the node with a **Task Executor** section in its `README.md` Reference implementation: [random_walk_planner](../../../robot/ros_ws/src/global/planners/random_walk/README.md) diff --git a/docs/robot/autonomy_modes.md b/docs/robot/autonomy_modes.md index 5854d3ced..bd93ccd6f 100644 --- a/docs/robot/autonomy_modes.md +++ b/docs/robot/autonomy_modes.md @@ -3,8 +3,8 @@ AirStack uses **stacks** ([docs/development/stacks.md](../development/stacks.md)) to control which autonomy modules launch inside each container. Each compose service carries a default stack — no environment variables need to be set by -hand. (The legacy `AUTONOMY_ROLE` role system was removed; a set -`AUTONOMY_ROLE` is a preflight error.) +hand. (Stacks are the only dispatch mechanism; a set `AUTONOMY_ROLE` +environment variable is a preflight error.) | Stack | What runs | |---|---| @@ -33,7 +33,7 @@ Profiles are split into **deployment** and **simulator** categories. | `desktop` | Dev desktop | `robot-desktop` + `gcs` | `full_default` | | `desktop_split` | Dev desktop | `robot-desktop-onboard` + `robot-offboard` + `gcs` | `lite_default` + `lite_offload_global:offboard` | | `l4t` | Jetson | `robot-l4t` + `zed-l4t` | `full_default` | -| `l4t_lite` | Jetson | `robot-l4t-onboard` + `zed-l4t` | `lite_default` | +| `l4t_lite` | Jetson | `robot-l4t-onboard` | `lite_default` | | `voxl` (alias `voxl_onboard`) | VOXL2 | `robot-voxl-onboard` | `lite_default` (compute-constrained) | | `offboard` | Ground station | `robot-offboard` ×N + `gcs-real` | `lite_offload_global:offboard` | diff --git a/docs/robot/configuration/index.md b/docs/robot/configuration/index.md index fcf056dea..8a00eea96 100644 --- a/docs/robot/configuration/index.md +++ b/docs/robot/configuration/index.md @@ -16,24 +16,26 @@ Robot configuration includes: ### Environment Variables -Key environment variables configured in `robot/docker/.env`: +Key environment variables configured in the top-level `.env` file: ```bash -# Robot Identity -ROBOT_NAME=robot1 -ROS_DOMAIN_ID=0 - # Launch Configuration AUTOLAUNCH=true -# Stack selection (stacks are the only dispatch; unset = full_default) +# Stack selection (stacks are the only dispatch; unset = full_default). +# Usually set via `airstack up --stack ` rather than by hand. AIRSTACK_STACK_DIR=/root/AirStack/stacks/full_default -# Sensor Configuration -ENABLE_CAMERA=true -ENABLE_LIDAR=false -CAMERA_TOPIC=/camera/image_raw +# Multi-robot +NUM_ROBOTS=1 + +# Robot identity mapping (name → ROBOT_NAME + ROS_DOMAIN_ID) +ROBOT_NAME_MAP_CONFIG_FILE=default_robot_name_map.yaml ``` +`ROBOT_NAME` and `ROS_DOMAIN_ID` are **not** set in `.env` — each container +resolves them at startup from `ROBOT_NAME_SOURCE` and the mapping config; see +[Robot Identity](../docker/robot_identity.md). + ### ROS 2 Parameters Module-specific parameters in YAML files: diff --git a/docs/robot/docker/index.md b/docs/robot/docker/index.md index 46d70b3de..781b38aeb 100644 --- a/docs/robot/docker/index.md +++ b/docs/robot/docker/index.md @@ -15,16 +15,19 @@ robot/docker/ All robot services inherit from the shared `robot_base` service defined in `robot-base-docker-compose.yaml`. Platform-specific services then extend `robot_base` and override only what differs for that target. -``` +```text robot_base (robot-base-docker-compose.yaml) │ -├── robot-desktop (profile: desktop) x86-64 desktop / simulation -│ └── simple-robot (profile: simple) desktop + simple sim override -│ └── robot-test (profile: test) desktop + colcon test override +├── robot-desktop (profile: desktop) x86-64 desktop / simulation +│ ├── robot-desktop-onboard (profile: desktop_split) desktop, lite stack (simulated onboard computer) +│ ├── simple-robot (profile: simple) desktop + simple sim override +│ └── robot-test (profile: test) desktop + colcon test override │ -├── robot-voxl (profile: voxl) ModalAI VOXL platform -├── robot-l4t (profile: l4t) NVIDIA Jetson (Linux for Tegra) -└── zed-l4t (profile: l4t) ZED camera driver on Jetson +├── robot-offboard (profiles: desktop_split, offboard) offboard/global half on a ground host +├── robot-voxl-onboard (profiles: voxl, voxl_onboard) ModalAI VOXL platform (lite stack) +├── robot-l4t (profile: l4t) NVIDIA Jetson (Linux for Tegra) +├── robot-l4t-onboard (profile: l4t_lite) Jetson, lite stack (global offloaded) +└── zed-l4t (profile: l4t) ZED camera driver on Jetson ``` ## Base Service (`robot_base`) @@ -65,11 +68,11 @@ Runs with `airstack up --profile simple`. Extends `desktop` with `SIM_TYPE=simpl ### `voxl` — ModalAI VOXL -Runs with `airstack up --profile voxl`. Use this profile when deploying on a ModalAI VOXL flight computer. +Runs with `airstack up --profile voxl` (service `robot-voxl-onboard`). Use this profile when deploying on a ModalAI VOXL flight computer. - **Image:** `...:v_robot-voxl_` - **Base image:** `ubuntu:22.04` (no CUDA; VOXL has its own compute stack) -- **Skipped components:** OpenVDB (MAC-VO and TensorRT no longer ship in any trunk robot image — they arrive via the `asm_macvo` module's `Dockerfile.module`) +- **Skipped components:** OpenVDB (MAC-VO and TensorRT are not part of any trunk robot image — they arrive via the `asm_macvo` module's `Dockerfile.module`) - **Network:** `host` (relies on the physical network for DDS discovery) - **Robot identity:** derived from the device hostname → `ROBOT_NAME_SOURCE=hostname` diff --git a/docs/robot/docker/robot_identity.md b/docs/robot/docker/robot_identity.md index dde74696f..d2efbd243 100644 --- a/docs/robot/docker/robot_identity.md +++ b/docs/robot/docker/robot_identity.md @@ -46,7 +46,7 @@ Each rule has a `pattern` (Python `re.fullmatch` regex), a `robot` template, and ```yaml mappings: - - pattern: '.*robot-.*(\ d+)' + - pattern: '.*robot-\D*(\d+)' robot: 'robot_{1}' domain_id: '{1}' @@ -63,7 +63,7 @@ To customize the mapping for your deployment, create a new YAML file in `robot/d Used by the **`desktop`** and **`simple`** profiles. -In simulation, Docker Compose names containers after the service, appending a replica number (e.g. `airstack-robot-desktop-1`, `airstack-robot-2`). The `.bashrc` resolves the container's hostname back to its Docker name: +In simulation, Docker Compose names containers after the service, appending a replica number (e.g. `airstack-robot-desktop-1`, `airstack-robot-desktop-2`). The `.bashrc` resolves the container's hostname back to its Docker name: ```bash name_to_map=$(host $(host $(hostname) | awk '{print $NF}') | awk '{print $NF}' | awk -F . '{print $1}') @@ -75,7 +75,7 @@ Because simulation robots get their identity from the container name (which is c ```bash NUM_ROBOTS=3 docker compose --profile desktop up -# → containers: airstack-robot-desktop-1, airstack-robot-2, airstack-robot-3 +# → containers: airstack-robot-desktop-1, airstack-robot-desktop-2, airstack-robot-desktop-3 # → ROBOT_NAME: robot_1, robot_2, robot_3 # → ROS_DOMAIN_ID: 1, 2, 3 ``` @@ -114,7 +114,7 @@ export FCU_URL="/dev/ttyTHS4:115200" `ROBOT_NAME=unknown_robot`, `ROS_DOMAIN_ID=0` with no error and a clean boot. The symptoms surface later — topics under `/unknown_robot`, per-robot config lookups keyed on `ROBOT_NAME` finding no profile, and containers pinned to another domain - (`zed-l4t` hardcodes `ROS_DOMAIN_ID=1`) no longer seeing the stack. + (`zed-l4t` hardcodes `ROS_DOMAIN_ID=1`) unable to see the stack. Make sure every physical robot has a hostname that matches a rule before deployment. Run the following to set the hostname on a device: diff --git a/docs/robot/index.md b/docs/robot/index.md index 723cbc599..cdbbb3225 100644 --- a/docs/robot/index.md +++ b/docs/robot/index.md @@ -57,9 +57,9 @@ The robot autonomy stack is launched via Docker Compose. The configuration is in ### Launch Command Hierarchy The Docker `command:` attribute launches the top-level ROS 2 launch file, -which runs a shared preamble and then the selected **stack** entry file -(stacks are the only dispatch — the legacy AUTONOMY_ROLE layer cascade was -removed; see [Stacks](../development/stacks.md)): +which runs a shared preamble and then the selected **stack** entry file — +the stack is the only dispatch mechanism (see +[Stacks](../development/stacks.md)): ``` robot.launch.xml # Entry point (autonomy_bringup): @@ -106,8 +106,8 @@ Standard ROS 2 topics used across the autonomy stack: |-------|------|-------------| | `/$ROBOT_NAME/odometry` | [nav_msgs/Odometry](https://docs.ros.org/en/rolling/p/nav_msgs/interfaces/msg/Odometry.html) | Best estimate of robot state | | `/$ROBOT_NAME/global_plan` | [nav_msgs/Path](https://docs.ros.org/en/rolling/p/nav_msgs/interfaces/msg/Path.html) | Target global trajectory | -| `/$ROBOT_NAME/trajectory_controller/trajectory_override` | airstack_msgs/TrajectoryOverride | Direct trajectory commands | -| `/$ROBOT_NAME/trajectory_controller/look_ahead` | geometry_msgs/PointStamped | Look-ahead point for planning | +| `/$ROBOT_NAME/trajectory_controller/trajectory_override` | airstack_msgs/TrajectoryXYZVYaw | Direct trajectory commands | +| `/$ROBOT_NAME/trajectory_controller/look_ahead` | airstack_msgs/Odometry | Look-ahead point for planning | **See also:** diff --git a/docs/robot/logging/data_offloading.md b/docs/robot/logging/data_offloading.md index bb9f006e0..ac09ea510 100644 --- a/docs/robot/logging/data_offloading.md +++ b/docs/robot/logging/data_offloading.md @@ -151,7 +151,7 @@ After successful offload, free space: ```bash # Remove successfully transferred bags (already done if using --remove-source-files) # Or delete bags older than 7 days after verification -find /opt/airstack/bags -name "*.db3" -mtime +7 -delete +find /opt/airstack/bags -name "*.mcap" -mtime +7 -delete ``` ### Storage Quotas @@ -171,7 +171,7 @@ Compress before transfer: ```bash # Compress bags cd /opt/airstack/bags -tar -czf bags_$(date +%Y%m%d_%H%M%S).tar.gz *.db3 +tar -czf bags_$(date +%Y%m%d_%H%M%S).tar.gz *.mcap # Transfer compressed archive rsync -avz --progress bags_*.tar.gz user@groundstation:/data/robot1/ @@ -262,7 +262,7 @@ SOURCE="/data/robot1/bags" ARCHIVE="/archive/robot1" DAYS_OLD=30 -find ${SOURCE} -name "*.db3" -mtime +${DAYS_OLD} -exec tar -czf {}.tar.gz {} \; -delete +find ${SOURCE} -name "*.mcap" -mtime +${DAYS_OLD} -exec tar -czf {}.tar.gz {} \; -delete mv ${SOURCE}/*.tar.gz ${ARCHIVE}/ ``` diff --git a/docs/robot/logging/index.md b/docs/robot/logging/index.md index bbf3a2c91..9443c26ec 100644 --- a/docs/robot/logging/index.md +++ b/docs/robot/logging/index.md @@ -2,14 +2,29 @@ ## Bag Recording -AirStack provides automated bag recording capabilities for capturing ROS2 topic data during flights. The main node for bag recording is located at [common/ros_packages/logging/bag_recorder_pid](../../../common/ros_packages/logging/bag_recorder_pid/README.md). For detailed configuration options and implementation details, please consult the README in that directory. +AirStack provides automated bag recording capabilities for capturing ROS 2 topic data during flights. The main node for bag recording is located at [common/ros_packages/logging/bag_recorder_pid](../../../common/ros_packages/logging/bag_recorder_pid/README.md). For detailed configuration options and implementation details, please consult the README in that directory. ### Enabling Bag Recording -To enable bag recording, prepend `RECORD_BAGS=true` to the airstack up command: +To start the recorder node, prepend `RECORD_BAGS=true` to the airstack up command: ```bash RECORD_BAGS=true airstack up robot-desktop ``` -The BehaviorTree will automatically trigger topic recording once the drone takes off. Recorded bags will appear in the `./robot/bags` directory. \ No newline at end of file +The stack entry file includes `logging_bringup/launch/logging.launch.xml`, which starts the `bag_record` node only when `RECORD_BAGS=true`. The topic set to record is selected with `LOG_CONFIG` (a filename in `logging_bringup/config/`, default `log.yaml`). + +The recorder starts **idle**. Toggle recording at runtime by publishing to its control topic (bridged to the GCS by the DDS router): + +```bash +# Start recording +ros2 topic pub --once /$ROBOT_NAME/bag_record/set_recording_status std_msgs/msg/Bool "{data: true}" + +# Stop recording +ros2 topic pub --once /$ROBOT_NAME/bag_record/set_recording_status std_msgs/msg/Bool "{data: false}" + +# Watch recording status (published at 2 Hz) +ros2 topic echo /$ROBOT_NAME/bag_record/bag_recording_status +``` + +Recorded bags (MCAP format) are written to `/bags` inside the container, which is the mounted `./robot/bags` directory on the host. diff --git a/docs/robot/logging/rosbags.md b/docs/robot/logging/rosbags.md index 1e3ebe21d..73e4c135a 100644 --- a/docs/robot/logging/rosbags.md +++ b/docs/robot/logging/rosbags.md @@ -27,43 +27,40 @@ ros2 bag record -a Record with storage limit: ```bash -ros2 bag record -a --max-bag-size 1000 # 1GB per file +ros2 bag record -a --max-bag-size 1000000000 # ~1 GB per file (bytes) ``` -### Automatic Recording +### Managed Recording -AirStack can automatically record bags using the [bag_recorder_pid](../../../common/ros_packages/logging/bag_recorder_pid/README.md) package. - -Configure in the robot launch files to automatically start recording when the autonomy stack launches. +AirStack manages recording with the [bag_recorder_pid](../../../common/ros_packages/logging/bag_recorder_pid/README.md) package: launch the stack with `RECORD_BAGS=true` to start the recorder node, then toggle recording via its `bag_record/set_recording_status` topic. See the [Logging overview](index.md) for the full workflow. ## Configuration ### Topic Selection -Choose topics based on mission objectives: +Choose topics based on mission objectives. The recorder's config file (selected with `LOG_CONFIG`, in `logging_bringup/config/`) groups topics into named **sections**; relative topic names are prefixed with the robot namespace: **Minimal set** (state and commands): ```yaml -topics: - - /{robot_name}/odometry - - /{robot_name}/global_plan - - /{robot_name}/trajectory_controller/trajectory_segment_to_add -``` - -**Standard set** (add sensor data): -```yaml -topics: - - /{robot_name}/odometry - - /{robot_name}/global_plan - - /{robot_name}/camera/image_raw/compressed - - /{robot_name}/depth/image_raw - - /{robot_name}/imu/data +sections: + state: + mcap_qos: mcap_qos.yaml + args: [] + topics: + - odometry_conversion/odometry + - global_plan + - trajectory_controller/trajectory_segment_to_add ``` -**Full set** (everything for debugging): +**Full set** (everything for debugging — record all topics except an exclude list): ```yaml -topics: - - ".*" # Record all topics +sections: + everything: + mcap_qos: mcap_qos.yaml + args: [] + exclude: + - /tf + - /tf_static ``` ### Storage Management @@ -78,12 +75,12 @@ On resource-constrained platforms (Jetson, VOXL): ## Storage Locations ### Development (Docker) -- Bags stored in mounted volume: `robot/bags/` +- Bags stored in mounted volume: `robot/bags/` (mounted at `/bags` in the container) - Persists across container restarts ### Hardware Deployment -- Default location: `/opt/airstack/bags/` or local SSD -- Configure via environment variable: `ROSBAG_DIR` +- Jetson (`l4t` profile): `/media/airlab/Storage/airstack_collection` on the device is mounted at `/bags` +- The recorder's target directory is its `output_dir` parameter (set in `logging.launch.xml`, default `/bags`) ## Playback and Analysis @@ -113,9 +110,9 @@ ros2 bag info path/to/bagfile Example output: ``` -Files: rosbag2_2024_03_17-14_30_00.db3 +Files: state_20260317_143000/state_20260317_143000_0.mcap Bag size: 1.2 GB -Storage id: sqlite3 +Storage id: mcap Duration: 300.5s Start: Mar 17 2024 14:30:00.123 End: Mar 17 2024 14:35:00.623 @@ -128,9 +125,16 @@ Topic information: ### Extract Specific Topics -Convert to a new bag with only specific topics: +Convert to a new bag with only specific topics using `ros2 bag convert` with an output spec: ```bash -ros2 bag filter input_bag -o output_bag --topics /robot1/odometry /robot1/camera/image_raw +ros2 bag convert -i input_bag -o out_spec.yaml +``` + +```yaml +# out_spec.yaml +output_bags: + - uri: output_bag + topics: [/robot_1/odometry_conversion/odometry, /robot_1/global_plan] ``` ## Common Workflows diff --git a/docs/robot/optitrack.md b/docs/robot/optitrack.md index 11637a47b..3c4d52c9b 100644 --- a/docs/robot/optitrack.md +++ b/docs/robot/optitrack.md @@ -1,6 +1,6 @@ -# OptiTrack (moved to the asm_optitrack module) +# OptiTrack (asm_optitrack module) -OptiTrack support — the `natnet_ros2` client, the PX4 external-vision fusion bridges, and the NatNet server emulator for Isaac Sim — no longer lives in the AirStack trunk. It was extracted into the standalone [asm_optitrack module](https://github.com/castacks/asm_optitrack). To use it, add the module to your checkout: +OptiTrack support — the `natnet_ros2` client, the PX4 external-vision fusion bridges, and the NatNet server emulator for Isaac Sim — ships as the standalone [asm_optitrack module](https://github.com/castacks/asm_optitrack) rather than in the AirStack trunk. To use it, add the module to your checkout: ```bash airstack module add https://github.com/castacks/asm_optitrack --version diff --git a/docs/robot/static_transforms/index.md b/docs/robot/static_transforms/index.md index 4040b7336..4748c2e3a 100644 --- a/docs/robot/static_transforms/index.md +++ b/docs/robot/static_transforms/index.md @@ -1,3 +1,6 @@ +# Static Transforms + +Static transforms pin down the fixed geometric relationships between frames — where sensors sit on the body, and how each robot's map frame relates to the world — so that every module can transform data into a common frame without per-module configuration. ## Frame Conventions diff --git a/docs/simulation/index.md b/docs/simulation/index.md index 49cf50e5f..6a48d2864 100644 --- a/docs/simulation/index.md +++ b/docs/simulation/index.md @@ -1,6 +1,8 @@ # Simulation -AirStack provides high-fidelity simulation environments for developing and testing autonomous systems before deploying to hardware. Simulation enables rapid iteration, safe testing of edge cases, and multi-robot scenarios. +AirStack provides simulation environments for developing and testing autonomous systems before deploying to hardware. Simulation enables rapid iteration, safe testing of edge cases, and multi-robot scenarios. + +Three simulators are supported because no single one covers every development need: **Isaac Sim** (primary) for high-fidelity rendering, physics, and full sensor suites; **Microsoft AirSim (legacy)** for native PX4-in-the-loop testing with pre-built Unreal Engine scenes and no Omniverse dependency; and **Simple Sim** for fast, lightweight iteration on planning and perception code without PX4 or a heavyweight GPU workload. Pick the lightest simulator that exercises what you're working on. ## Directory Structure @@ -12,10 +14,10 @@ simulation/ │ ├── docker/ # Isaac Sim containerization │ │ ├── docker-compose.yaml # Main launch configuration │ │ └── Dockerfile.isaac-ros # Image definition -│ ├── assets/ # 3D models and props -│ ├── config/ # Simulation configurations +│ ├── assets/ # Scenes, 3D models and props │ ├── extensions/ # Custom Isaac Sim extensions -│ └── launch_scripts/ # Python launch scripts +│ ├── launch_scripts/ # Python launch scripts +│ └── utils/ # Shared helpers ├── ms-airsim/ │ ├── docker/ # Microsoft AirSim (legacy) containerization │ │ ├── docker-compose.yaml # Launch configuration @@ -85,13 +87,13 @@ An open-source drone simulator built on Unreal Engine with native PX4 SITL integ - Depth-based obstacle avoidance testing (DROAN) - Environments from the Unreal Engine ecosystem -**Launch:** `airstack up --env-file overrides/ms-airsim.env` +**Launch:** `airstack up --sim airsim` **Location:** `simulation/ms-airsim/` ### Simple Sim (Lightweight) -A lightweight kinematic simulator (single ROS 2 node, no PX4/MAVROS — it mocks the MAVROS interface directly) for fast iteration when full Isaac Sim fidelity isn't needed. Single robot only. Actively used by core maintainer John Keller. See [Simple Sim](simple_sim/index.md). +A lightweight kinematic simulator (single ROS 2 node, no PX4/MAVROS — it mocks the MAVROS interface directly) for fast iteration when full Isaac Sim fidelity isn't needed. Single robot only. See [Simple Sim](simple_sim/index.md). **Use cases:** @@ -167,10 +169,7 @@ ISAAC_SIM_SCRIPT_NAME=my_custom_scene.py airstack up --sim isaac airstack up --sim isaac --play ``` -**Pre-built scenes:** Located in `scenes/` directory - -- `two_drone_fire_new.usd` - Fire academy scenario -- `two_drone_RetroNeighborhood.usd` - Urban neighborhood +**Pre-built scenes:** Located in `simulation/isaac-sim/assets/scenes/` (e.g. `simple_pegasus.scene.usd`); standalone launch scripts in `simulation/isaac-sim/launch_scripts/` build scenes programmatically. **Learn more:** [Docker Workflow](../development/beginner/airstack-cli/docker_usage.md#docker-compose-variable-overrides) diff --git a/docs/simulation/isaac_sim/ascent_sitl_extension.md b/docs/simulation/isaac_sim/ascent_sitl_extension.md index 7eb63ef09..1f759b85e 100644 --- a/docs/simulation/isaac_sim/ascent_sitl_extension.md +++ b/docs/simulation/isaac_sim/ascent_sitl_extension.md @@ -2,11 +2,13 @@ The AirStack extension for IsaacSim does two main things. It creates an Ascent Omnigraph Node which runs the Ascent SITL and updates the position of a drone model in IsaacSim based on the SITL. It also creates a panel for listing, attaching to, and killing tmux sessions. -## Ascent OmniGraph Node (Deprecated as of November 2025) +## Ascent OmniGraph Node (Deprecated) + +The Ascent OmniGraph node is **deprecated** — PX4 via the [Pegasus extension](pegasus_scene_setup.md) is the supported vehicle-dynamics path. The Ascent OmniGraph node takes as input a domain id, node namespace and drone prim. It runs the Ascent SITL, mavproxy, and mavros and takes care of keeping the SITL time synced with IsaacSim's time. Mavros is run using the inputted domain id and node namespace. The drone prim's position is set based off of the position of the drone in the SITL. The drone prim doesn't do collision and will pass through objects in the IsaacSim world. -The way the SITL is synced with IsaacSim is by running the SITL in gdb with a breakpoint on the functin that advances the SITL time. Every time this function is called, our code is run by injecting a library using the LD_PRELOAD trick. Our code runs a client socket that talks to a server socket running in the AirStack IsaacSim extension which tells it how long to sleep based off the current SITL and IsaacSim time. +The way the SITL is synced with IsaacSim is by running the SITL in gdb with a breakpoint on the function that advances the SITL time. Every time this function is called, our code is run by injecting a library using the LD_PRELOAD trick. Our code runs a client socket that talks to a server socket running in the AirStack IsaacSim extension which tells it how long to sleep based off the current SITL and IsaacSim time. The Ascent OmniGraph node is shown below: @@ -14,7 +16,7 @@ The Ascent OmniGraph node is shown below: ## TMUX Panel -This is a panel for listing, attaching to, and killing any running TMUX sessions. The Ascent SITL, mavproxy, and mavros are run in a TMUX sesion, so this is mainly for debugging those and probably doesn't need to be interacted with by most users. A list of TMUX sessions is displayed in the panel. It doesn't auto refresh so you have to manually click the refresh button to display any changes in the list of sessions. For each session, there is an `Attach` button and a `Kill` button. The `Attach` button will bring up an `xterm` window with the TMUX session. The `Kill` button will kill the TMUX session. +This is a panel for listing, attaching to, and killing any running TMUX sessions. The Ascent SITL, mavproxy, and mavros are run in a TMUX session, so this is mainly for debugging those and probably doesn't need to be interacted with by most users. A list of TMUX sessions is displayed in the panel. It doesn't auto refresh so you have to manually click the refresh button to display any changes in the list of sessions. For each session, there is an `Attach` button and a `Kill` button. The `Attach` button will bring up an `xterm` window with the TMUX session. The `Kill` button will kill the TMUX session. The TMUX panel is shown below: diff --git a/docs/simulation/isaac_sim/docker.md b/docs/simulation/isaac_sim/docker.md index 0badb1e54..e71ff52c8 100644 --- a/docs/simulation/isaac_sim/docker.md +++ b/docs/simulation/isaac_sim/docker.md @@ -121,21 +121,21 @@ Key variables for Isaac Sim configuration: | `AUTOLAUNCH` | Auto-start Isaac Sim on container launch | `true` | | `ISAAC_SIM_USE_STANDALONE` | `true`: run `ISAAC_SIM_SCRIPT_NAME`; `false`: open the `ISAAC_SIM_GUI` USD | `true` | | `ISAAC_SIM_SCRIPT_NAME` | Standalone launch script in `simulation/isaac-sim/launch_scripts/` | `example_one_px4_pegasus_launch_script.py` | -| `ISAAC_SIM_GUI` | Path to a USD scene file (used only when `ISAAC_SIM_USE_STANDALONE=false`) | `simple_pegasus.scene.usd` | +| `ISAAC_SIM_GUI` | Path to a USD scene file (used only when `ISAAC_SIM_USE_STANDALONE=false`) | `simulation/isaac-sim/assets/scenes/simple_pegasus.scene.usd` | | `PLAY_SIM_ON_START` | Auto-play simulation on start (`airstack up --play/--no-play`) | `false` | | `ISAAC_SIM_HEADLESS` | Run without a window (`airstack up --headless`) | unset (`false`) | -| `PX4_PHYSICS_HZ` | Physics step rate for PX4 SITL — also sets PX4 `IMU_INTEG_RATE` | `250` | -| `PX4_RENDERING_HZ` | Rendering frame rate for PX4 profiles (independent of physics) | `60` | +| `PX4_PHYSICS_HZ` | Physics step rate for PX4 SITL — also sets PX4 `IMU_INTEG_RATE` | `100` | +| `PX4_RENDERING_HZ` | Rendering frame rate for PX4 profiles (independent of physics) | `30` | | `ARDUPILOT_PHYSICS_HZ` | Physics step rate for ArduPilot SITL | `800` | | `ARDUPILOT_RENDERING_HZ` | Rendering frame rate for ArduPilot profiles | `120` | -`PX4_PHYSICS_HZ` and `PX4_RENDERING_HZ` are set in the isaac-sim compose file (defaults 100/60 there; the Pegasus code default is 250). AirStack runs PX4 at **100 Hz** for near-real-time performance. See [Pegasus Scene Setup → Physics Rate](pegasus_scene_setup.md) for valid values and the full configuration flow. +`PX4_PHYSICS_HZ` and `PX4_RENDERING_HZ` default to 100/30 in the isaac-sim compose file (the Pegasus code default is 250 Hz physics). AirStack runs PX4 at **100 Hz** for near-real-time performance. See [Pegasus Scene Setup → Physics Rate](pegasus_scene_setup.md) for valid values and the full configuration flow. **Example overrides:** ```bash -# Launch without GUI (headless) -ISAAC_SIM_GUI=false airstack up isaac-sim +# Launch without a window (headless) +airstack up --sim isaac --headless # Don't auto-play simulation PLAY_SIM_ON_START=false airstack up isaac-sim @@ -316,7 +316,7 @@ Access via web browser. 1. **Edit scene files** on host (they're mounted): ``` - simulation/isaac-sim/scenes/my_scene.usd + simulation/isaac-sim/assets/scenes/my_scene.usd ``` 2. **Reload in Isaac Sim:** diff --git a/docs/simulation/isaac_sim/pegasus_scene_setup.md b/docs/simulation/isaac_sim/pegasus_scene_setup.md index fa1f068e6..ab0929b40 100644 --- a/docs/simulation/isaac_sim/pegasus_scene_setup.md +++ b/docs/simulation/isaac_sim/pegasus_scene_setup.md @@ -20,8 +20,6 @@ Through this approach, AirStack leverages Pegasus to create a flexible, reusable ## Launch Configuration -Launch Configuration - At the top level of the AirStack simulation environment, a `.env` file controls how Pegasus and Isaac Sim are launched: ```bash ISAAC_SIM_GUI="omniverse://airlab-nucleus.andrew.cmu.edu/Library/Assets/Pegasus/iris_with_sensors.pegasus.robot.usd" @@ -112,7 +110,7 @@ Scripts must live in `simulation/isaac-sim/launch_scripts/`. Set `ISAAC_SIM_SCRI ## RTX OmniLidar and near range (`min_range`) — known limitation {#rtx-lidar-near-range} -AirStack’s Pegasus fork (Isaac Sim **5.1+**) wires **RTX OmniLidar** through OmniGraph helpers such as `add_rtx_lidar_subgraph` in `pegasus.simulator.ogn.api.spawn_rtx_lidar` (used from `simulation/isaac-sim/launch_scripts/example_one_px4_pegasus_launch_script.py`, `example_multi_px4_pegasus_launch_script.py`, etc.). Recent work in this repo switched those scripts from the legacy Ouster graph path to this **RTX** API and reconciled ROS topic names (e.g. raw cloud on `…/sensors/ouster/point_cloud_raw`, filtered consumer topic `…/sensors/ouster/point_cloud`). +AirStack’s Pegasus fork (Isaac Sim **5.1+**) wires **RTX OmniLidar** through OmniGraph helpers such as `add_rtx_lidar_subgraph` in `pegasus.simulator.ogn.api.spawn_rtx_lidar` (used from `simulation/isaac-sim/launch_scripts/example_one_px4_pegasus_launch_script.py`, `example_multi_px4_pegasus_launch_script.py`, etc.). The lidar publishes the raw cloud on `…/sensors/ouster/point_cloud_raw`; consumers read the filtered topic `…/sensors/ouster/point_cloud`. ### `min_range` → `nearRangeM` in simulation diff --git a/docs/simulation/isaac_sim/spawning_drones.md b/docs/simulation/isaac_sim/spawning_drones.md index 212f2a73a..3deb43820 100644 --- a/docs/simulation/isaac_sim/spawning_drones.md +++ b/docs/simulation/isaac_sim/spawning_drones.md @@ -8,7 +8,7 @@ All launch scripts under `simulation/isaac-sim/launch_scripts/` are thin scenari | `example_one_px4_pegasus_launch_script.py` | One PX4 drone with the standard sensor stack (ZED stereo + Ouster lidar) in the default environment. | | `example_multi_px4_pegasus_launch_script.py` | `NUM_ROBOTS` drones spawned in a row (`row_spawn_configs`). Each drone gets its own ROS domain id (`1..N`). Lidar gated on `ENABLE_LIDAR`. | | `example_multi_drone_scene_import.py` | Explicit `DRONE_CONFIGS` in an **imported scene** (USD from a Nucleus server) with per-drone GPS homes. Use this as the starting point for any custom scene. | -| `fleet_spawn.py` | Generic fleet spawner (RFC #380): spawn positions, per-robot sensor toggles, and the scene come from the fleet file named by `FLEET_CONFIG_FILE`. Selected automatically by `airstack up --fleet ` — not meant to be copied as a template. | +| `fleet_spawn.py` | Generic fleet spawner: spawn positions, per-robot sensor toggles, and the scene come from the fleet file named by `FLEET_CONFIG_FILE`. Selected automatically by `airstack up --fleet ` — not meant to be copied as a template. | ## Writing a launch script with `PegasusApp` @@ -74,7 +74,7 @@ DRONE_CONFIGS = [ | `domain_id` | ROS domain id and (by default) PX4 vehicle id — MAVLink port is `14540 + vehicle_id`. The robot container with `ROS_DOMAIN_ID=1` will see this drone. | | `x_m`, `y_m`, `z_m` | World-frame spawn position in meters. Convention: `+X = East`, `+Y = North`, `+Z = Up`. | | `orient` | Spawn orientation quaternion `[x, y, z, w]` (default identity). | -| `prim`, `node_name` | Override the drone's root prim / OmniGraph node name (single-drone scenes use the historical `/World/base_link` / `PX4Multirotor`). | +| `prim`, `node_name` | Override the drone's root prim / OmniGraph node name (single-drone scenes default to `/World/base_link` / `PX4Multirotor`). | | `camera`, `lidar`, `lidar_min_range`, `camera_offset` | Per-drone sensor overrides of the app-level settings. | To add another drone, append an entry with a fresh `domain_id` and a non-overlapping spawn position, and launch the matching number of robot containers (`airstack up --sim isaac --robots N` keeps `NUM_ROBOTS` and the launch script consistent). diff --git a/docs/simulation/ms-airsim/docker.md b/docs/simulation/ms-airsim/docker.md index 16a515663..ac70b100a 100644 --- a/docs/simulation/ms-airsim/docker.md +++ b/docs/simulation/ms-airsim/docker.md @@ -42,7 +42,10 @@ The Microsoft AirSim (legacy) service is defined in `simulation/ms-airsim/docker Microsoft AirSim (legacy) is gated behind a Docker Compose profile: ```bash -# Start alongside the robot stack +# Start alongside the robot stack (intent flag: selects profiles + matching URDF) +airstack up --sim airsim + +# Equivalent explicit-profile form airstack up --profile ms-airsim --profile desktop # Build the image first @@ -301,7 +304,7 @@ AIRSIM_CAM_FOV=120 airstack up --profile ms-airsim - Ensure AirSim binary started successfully (check tmux window 0) - The entrypoint retries until the API is ready; check for connection errors in the container logs -- Verify `ms_airsim_ip` in `bridge.yaml` matches where AirSim is running (default: `127.0.0.1` — same container) +- Verify the bridge node's `ms_airsim_ip` parameter matches where AirSim is running (default: `127.0.0.1` — same container) **PX4 SITL won't connect:** @@ -317,7 +320,7 @@ AIRSIM_CAM_FOV=120 airstack up --profile ms-airsim **No depth images on ROS 2 topics:** -- Verify the camera names in `settings.json` match those in `bridge.yaml` +- Verify the camera names in the generated `settings.json` are `front_left` / `front_right` — the names the bridge node requests images by - Check bridge node output for connection errors (tmux bridge window) - Echo the topic: `ros2 topic echo /robot_1/sensors/front_stereo/depth --once` diff --git a/docs/simulation/ms-airsim/index.md b/docs/simulation/ms-airsim/index.md index dbeb6b8d2..c2906f8e1 100644 --- a/docs/simulation/ms-airsim/index.md +++ b/docs/simulation/ms-airsim/index.md @@ -40,9 +40,11 @@ Scenes are pulled from the [AirSim Linux releases](https://github.com/microsoft/ ### 2. Launch Microsoft AirSim (legacy) + Robot ```bash -airstack up --env-file overrides/ms-airsim.env +airstack up --sim airsim ``` +(Equivalently: `airstack up --env-file overrides/ms-airsim.env`, which sets the same compose profiles and URDF.) + To build the images first: ```bash @@ -60,11 +62,6 @@ To attach to the tmux session: airstack connect ms-airsim ``` -A video is below: - - - - ## Architecture ``` @@ -107,10 +104,10 @@ Key settings: | `ClockType` | `SteppableClock` | Lockstep with PX4 | | `VehicleType` | `PX4Multirotor` | PX4 SITL vehicle | | `TcpPort` | `4560 + i` | PX4 lockstep connection (per robot `i`) | -| `ControlPortLocal` | `24540 + i` | AirSim MAVLink proxy local port (moved off `14540+i` so it doesn't intercept PX4 ↔ MAVROS traffic) | +| `ControlPortLocal` | `24540 + i` | AirSim MAVLink proxy local port (deliberately offset from `14540+i` so the proxy doesn't intercept PX4 ↔ MAVROS traffic) | | `ControlPortRemote` | `24580 + i` | AirSim MAVLink proxy remote port | -`settings.json` is generated at container start from [`settings.json.j2`](https://github.com/.../simulation/ms-airsim/config/settings.json.j2) via [`generate_settings.py`](https://github.com/.../simulation/ms-airsim/config/generate_settings.py), which expands per-robot port offsets, spawn positions, and camera parameters. +`settings.json` is generated at container start from [`settings.json.j2`](https://github.com/castacks/AirStack/blob/main/simulation/ms-airsim/config/settings.json.j2) via [`generate_settings.py`](https://github.com/castacks/AirStack/blob/main/simulation/ms-airsim/config/generate_settings.py), which expands per-robot port offsets, spawn positions, and camera parameters. ### Cameras @@ -158,7 +155,7 @@ Declared (with these defaults) in `simulation/ms-airsim/ros_ws/src/ms_airsim_ros ### Project status -Microsoft archived AirSim. For a maintained successor, see [Project AirSim](https://github.com/iamaisim/ProjectAirSim) (UE5, new API — integration planned as a future AirStack feature). +Microsoft archived AirSim, which is why AirStack labels it "legacy": it remains a stable, supported simulation backend here, but the upstream project receives no new features. For a maintained successor, see [Project AirSim](https://github.com/iamaisim/ProjectAirSim) (UE5, new API). ## Troubleshooting @@ -169,7 +166,7 @@ Microsoft archived AirSim. For a maintained successor, see [Project AirSim](http **No depth images:** -- Verify the camera name in `settings.json` matches `bridge.yaml` +- Verify the camera names in the generated `settings.json` are `front_left` / `front_right` — the names the bridge node requests images by - Check AirSim console for rendering errors **MAVROS won't connect:** diff --git a/docs/tutorials/airstack_on_osmo.md b/docs/tutorials/airstack_on_osmo.md index e9cfa8974..5709bb330 100644 --- a/docs/tutorials/airstack_on_osmo.md +++ b/docs/tutorials/airstack_on_osmo.md @@ -1,6 +1,6 @@ # AirStack on OSMO — Recommended Remote Development Workflow -This is AirStack's recommended day-to-day development path going forward. +This is AirStack's recommended day-to-day development path. You submit one OSMO workflow that spins up a GPU pod running the full three-container AirStack stack (Isaac Sim, robot-desktop, GCS), attach VS Code or Cursor to it over Remote-SSH, and stream Isaac Sim and the GCS @@ -26,7 +26,7 @@ Why this is the recommended path: > **Still want local development on a Linux+GPU desktop?** It works and > can be faster for tight inner loops — see > [Getting Started](../getting_started/index.md). It just isn't the -> recommended default anymore. +> recommended default. ## Who is this for? @@ -221,17 +221,17 @@ EOF The `localhost:2200` is what we'll port-forward to in step 4. -> **Already added the old block?** If your `~/.ssh/config` still has -> `StrictHostKeyChecking accept-new` for `airstack-osmo` from an earlier -> setup, replace it with the three lines above. As a one-time cleanup of -> the stale fingerprint left behind by previous pods, also run: +> **`~/.ssh/config` already has a different `airstack-osmo` block?** If +> your config carries `StrictHostKeyChecking accept-new` for +> `airstack-osmo`, replace it with the three lines above, and clean out +> any saved fingerprint for the port-forward once: > > ```bash > ssh-keygen -R "[localhost]:2200" > ``` > -> `airstack osmo:ide` does this scrub for you on every run, so you only -> need it once when migrating. +> `airstack osmo:ide` does this scrub for you on every run, so at most you +> need it once. > **Smoke-test the agent forward** once the pod is up: SSH in and run > `ssh-add -l` — you should see your local key listed. If you see "The diff --git a/mkdocs.yml b/mkdocs.yml index 4f9727f66..1b800434d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,6 +5,9 @@ site_name: AirStack site_dir: ../site site_url: "https://docs.theairlab.org/docs/" # Trailing slash is recommended exclude_docs: | + # docs/README.md is the GitHub-facing readme for the docs/ folder; the site + # page for that URL is docs/index.md, and same-dir would otherwise collide. + docs/README.md **/ros_ws/build **/docker/Foxglove **/ros_ws/install @@ -207,6 +210,7 @@ nav: - docs/real_world/HITL/index.md - Data Offloading: - docs/real_world/data_offloading/index.md + - Release Notes: docs/release_notes/index.md - About: docs/about.md plugins: - search diff --git a/robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml b/robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml index b2219d533..1143b8b84 100644 --- a/robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml +++ b/robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml @@ -12,12 +12,9 @@ # all topics are bidirectional by default. See https://eprosima-dds-router.readthedocs.io/ for more details # Shared robot <-> GCS DDS Router allowlist (autonomy_bringup/config/). -# History: lived at onboard_all/config/ under the removed AUTONOMY_ROLE -# dispatch; stacks (RFC #379) now select it directly — the full_* stacks and -# lite_default point their interpolate_dds_router include here. (The legacy -# onboard_local_offboard_global extension of this file merged to an identical -# allowlist, so lite_default uses this base unchanged; the lite_offload_global -# split stack instead GENERATES its router config from its bridge.yaml.) +# The full_* stacks and lite_default point their interpolate_dds_router +# include here; the lite_offload_global split stack instead GENERATES its +# router config from its bridge.yaml (tools/gen_dds_router.py). participants: - name: "robot" kind: "local" diff --git a/robot/ros_ws/src/global/planners/exploration/README.md b/robot/ros_ws/src/global/planners/exploration/README.md index 2a18367e1..f510e4cef 100644 --- a/robot/ros_ws/src/global/planners/exploration/README.md +++ b/robot/ros_ws/src/global/planners/exploration/README.md @@ -1,6 +1,6 @@ # Exploration Planner -The Exploration Planner is an optional global planner for autonomous flight. It combines the maintaining of an openvdb based voxel occupancy grid map, extract frontier from the map, and generates trajectories that enables the drone to explore previously undiscovered areas. With VDB mapping integrated, you can turn off the vdb_mapping node in the launch xml, and the planner will generate and publish multiple linked straight-line trajectories as a shortened RRT path to a selected viewpoint with collision check. +The Exploration Planner is an optional global planner for autonomous flight. It maintains an OpenVDB-based voxel occupancy grid map, extracts frontiers from the map, and generates trajectories that let the drone explore unvisited areas. Because it maintains its own map, you can turn off the `vdb_mapping` node in the launch XML; the planner will generate and publish multiple linked straight-line trajectories as a shortened, collision-checked RRT path to a selected viewpoint. ## Functionality @@ -11,7 +11,7 @@ include) with: ``: -Then when running the robot stack, after taking off, click the `Global Plan` and the exploration planner will work in the place of previous random walk planner. The working process is: +Then when running the robot stack, after taking off, toggle `Global Plan` (the `~/global_plan_toggle` service) and the exploration planner runs in place of the random walk planner. The working process is: 1. Create and maintain a voxel grid map with odometry and laser scan, to visualize, check topic `"~/vdb_viz"`, where `~` is the namespace. 2. Extract frontier and select viewpoints for exploration. @@ -19,9 +19,9 @@ Then when running the robot stack, after taking off, click the `Global Plan` and 4. Continuously monitor the robot's progress along the published path. 5. Once the robot completes the current path, a new exploration path will be generated. -This loop continues, enabling the drone to keep explore in the entire space. +This loop continues, enabling the drone to keep exploring the entire space. -We're still cleaning old params of random walk planner, based on which this exploration is developed. We'll update parameter documentation later. +The planner is derived from the random walk planner and shares several of its parameters; parameter documentation is still being expanded. ## Parameters |
Parameter
| Description diff --git a/robot/ros_ws/src/global/planners/random_walk/README.md b/robot/ros_ws/src/global/planners/random_walk/README.md index 91cda56d5..50ab2f986 100644 --- a/robot/ros_ws/src/global/planners/random_walk/README.md +++ b/robot/ros_ws/src/global/planners/random_walk/README.md @@ -7,7 +7,7 @@ The blue line is the global plan generated by the random walk. The yellow line s ## Functionality -Upon activation by the behavior tree, the Random Walk Planner will: +Upon activation by an `ExplorationTask` goal, the Random Walk Planner will: 1. Generate a specified number of straight-line path segments. 2. Continuously monitor the robot's progress along the published path. @@ -40,13 +40,13 @@ This node is a **task executor**: it runs as a ROS 2 action server and is activa Random walk delegates navigation to the local planner via a second action: ``` -behavior_executive → ExplorationTask → random_walk_planner - ↓ - NavigateTask (/{robot_name}/tasks/navigate) - ↓ - droan_gl (local planner) - ↓ - trajectory_controller +GCS operator → ExplorationTask → random_walk_planner + ↓ + NavigateTask (/{robot_name}/tasks/navigate) + ↓ + droan_gl (local planner) + ↓ + trajectory_controller ``` ### Goal parameters diff --git a/robot/ros_ws/src/local/planners/droan_local_planner/README.md b/robot/ros_ws/src/local/planners/droan_local_planner/README.md index 16a8e68fd..15842f7fe 100644 --- a/robot/ros_ws/src/local/planners/droan_local_planner/README.md +++ b/robot/ros_ws/src/local/planners/droan_local_planner/README.md @@ -16,7 +16,7 @@ The cost map is built by three supporting packages: 2. **disparity_graph** — maintains a rolling graph of (pose, obstacle cloud) observations, discarding old entries as the drone moves 3. **disparity_graph_cost_map** — queries the disparity graph to assign a collision cost to any 3D point in space -The cost map plugin is configurable via the `cost_map` parameter (default: `disparity_graph_cost_map::DisparityGraphCostMap`). +The cost map plugin is configurable via the `cost_map` parameter; the shipped `config/droan.yaml` sets `disparity_graph_cost_map::DisparityGraphCostMap` (the code's declared default is `PointCloudMapRepresentation`). ### 2. Trajectory Library diff --git a/robot/ros_ws/src/local/planners/trajectory_library/plugin.xml b/robot/ros_ws/src/local/planners/trajectory_library/plugin.xml deleted file mode 100644 index ba4923252..000000000 --- a/robot/ros_ws/src/local/planners/trajectory_library/plugin.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - An interface for choosing fixed trajectories. - - - - - - - system-help - Fixed Trajectory Selector. - - - diff --git a/robot/ros_ws/src/local/planners/trajectory_library/scripts/rqt_fixed_trajectory_selector b/robot/ros_ws/src/local/planners/trajectory_library/scripts/rqt_fixed_trajectory_selector deleted file mode 100644 index 4d4951103..000000000 --- a/robot/ros_ws/src/local/planners/trajectory_library/scripts/rqt_fixed_trajectory_selector +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python - -import sys - -from rqt_fixed_trajectory_selector.rqt_fixed_trajectory_selector import FixedTrajectorySelectorPlugin -from rqt_gui.main import Main - -plugin = 'rqt_fixed_trajectory_selector' -main = Main(filename=plugin) -sys.exit(main.main(standalone=plugin)) diff --git a/robot/ros_ws/src/local/planners/trajectory_library/setup.py b/robot/ros_ws/src/local/planners/trajectory_library/setup.py deleted file mode 100644 index d8e789de9..000000000 --- a/robot/ros_ws/src/local/planners/trajectory_library/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from distutils.core import setup -from catkin_pkg.python_setup import generate_distutils_setup - -d = generate_distutils_setup( - packages=['rqt_fixed_trajectory_selector'], - package_dir={'': 'src'}, -) - -setup(**d) diff --git a/robot/ros_ws/src/local/planners/trajectory_library/src/rqt_fixed_trajectory_selector/__init__.py b/robot/ros_ws/src/local/planners/trajectory_library/src/rqt_fixed_trajectory_selector/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/robot/ros_ws/src/local/planners/trajectory_library/src/rqt_fixed_trajectory_selector/rqt_fixed_trajectory_selector.py b/robot/ros_ws/src/local/planners/trajectory_library/src/rqt_fixed_trajectory_selector/rqt_fixed_trajectory_selector.py deleted file mode 100644 index a8fd1a8f5..000000000 --- a/robot/ros_ws/src/local/planners/trajectory_library/src/rqt_fixed_trajectory_selector/rqt_fixed_trajectory_selector.py +++ /dev/null @@ -1,204 +0,0 @@ -import os -import time -import rospy -import rospkg -from std_msgs.msg import String, Bool -from behavior_tree_msgs.msg import Status -import numpy as np -import yaml -import collections - -from qt_gui.plugin import Plugin -import python_qt_binding.QtWidgets as qt -import python_qt_binding.QtCore as core -import python_qt_binding.QtGui as gui - -from python_qt_binding import QT_BINDING, QT_BINDING_VERSION - -from python_qt_binding.QtCore import Slot, Qt, qVersion, qWarning, Signal -from python_qt_binding.QtGui import QColor -from python_qt_binding.QtWidgets import QWidget, QVBoxLayout, QSizePolicy - -from airstack_msgs.msg import FixedTrajectory -from diagnostic_msgs.msg import KeyValue - -class FixedTrajectorySelectorPlugin(Plugin): - def __init__(self, context): - super(FixedTrajectorySelectorPlugin, self).__init__(context) - self.setObjectName('FixedTrajectorySelectorPlugin') - - self.config_filename = '' - - self.button_dct = {} - self.attribute_settings = collections.OrderedDict() - - self.timer = rospy.Timer(rospy.Duration(1./10.), self.timer_callback) - - self.fixed_trajectory_pub = rospy.Publisher('fixed_trajectory_command', FixedTrajectory, queue_size=1) - self.global_plan_fixed_trajectory_pub = rospy.Publisher('global_plan_fixed_trajectory', FixedTrajectory, queue_size=1) - - # main layout - self.widget = QWidget() - self.vbox = qt.QVBoxLayout() - self.widget.setLayout(self.vbox) - context.add_widget(self.widget) - - # config widget - self.config_widget = qt.QWidget() - self.config_widget.setStyleSheet('QWidget{margin-left:-1px;}') - self.config_layout = qt.QHBoxLayout() - self.config_widget.setLayout(self.config_layout) - self.config_widget.setFixedHeight(50) - - self.config_button = qt.QPushButton('Open Config...') - self.config_button.clicked.connect(self.select_config_file) - self.config_layout.addWidget(self.config_button) - - self.config_label = qt.QLabel('config filename: ') - self.config_layout.addWidget(self.config_label) - self.vbox.addWidget(self.config_widget) - - # trajectory widget - self.trajectory_widget = qt.QWidget() - self.trajectory_layout = qt.QVBoxLayout() - self.trajectory_widget.setLayout(self.trajectory_layout) - self.vbox.addWidget(self.trajectory_widget) - - self.tab_widget = qt.QTabWidget() - self.trajectory_layout.addWidget(self.tab_widget) - - # button widget - self.button_widget = qt.QWidget() - self.button_layout = qt.QHBoxLayout() - self.button_widget.setLayout(self.button_layout) - self.vbox.addWidget(self.button_widget) - - self.publish_button = qt.QPushButton('Publish') - self.publish_button.clicked.connect(self.publish_trajectory) - self.button_layout.addWidget(self.publish_button) - - self.trajectory_type_label = qt.QLabel('Type: ') - self.button_layout.addWidget(self.trajectory_type_label) - - self.trajectory_type_combo_box = qt.QComboBox() - self.trajectory_type_combo_box.addItem('Fixed Trajectory') - self.trajectory_type_combo_box.addItem('Global Plan') - self.button_layout.addWidget(self.trajectory_type_combo_box) - - def publish_trajectory(self): - trajectory_type = self.trajectory_type_combo_box.currentText() - trajectory_name = self.tab_widget.tabText(self.tab_widget.currentIndex()) - msg = FixedTrajectory() - msg.type = trajectory_name - for attribute, value in self.attribute_settings[trajectory_name].iteritems(): - key_value = KeyValue() - key_value.key = attribute - key_value.value = value - msg.attributes.append(key_value) - if trajectory_type == 'Fixed Trajectory': - self.fixed_trajectory_pub.publish(msg) - elif trajectory_type == 'Global Plan': - self.global_plan_fixed_trajectory_pub.publish(msg) - - - def select_config_file(self): - starting_path = os.path.join(rospkg.RosPack().get_path('trajectory_library'), 'config') - filename = qt.QFileDialog.getOpenFileName(self.widget, 'Open Config File', starting_path, "Config Files (*.yaml)")[0] - self.set_config(filename) - - def set_config(self, filename): - if filename != '': - self.config_filename = filename - if self.config_filename != None: - self.config_label.setText('config filename: ' + self.config_filename) - self.init_buttons(filename) - - def init_buttons(self, filename): - y = yaml.load(open(filename, 'r').read()) - print(y) - - def get_attribute_changed_function(trajectory_name, attribute_name): - def attribute_changed(text): - if trajectory_name not in self.attribute_settings: - self.attribute_settings[trajectory_name] = {} - self.attribute_settings[trajectory_name][attribute_name] = text - return attribute_changed - - def get_publish_function(trajectory_name): - def publish_function(): - msg = FixedTrajectory() - msg.type = trajectory_name - for attribute, value in self.attribute_settings[trajectory_name].iteritems(): - key_value = KeyValue() - key_value.key = attribute - key_value.value = value - msg.attributes.append(key_value) - self.fixed_trajectory_pub.publish(msg) - return publish_function - - - for trajectory in y['trajectories']: - trajectory_name = trajectory.keys()[0] - attributes = trajectory[trajectory_name]['attributes'] - - trajectory_tab = qt.QWidget() - trajectory_layout = qt.QVBoxLayout() - trajectory_tab.setLayout(trajectory_layout) - - for attribute in attributes: - attribute_widget = qt.QWidget() - attribute_layout = qt.QHBoxLayout() - attribute_widget.setLayout(attribute_layout) - - attribute_label = qt.QLabel() - attribute_label.setText(attribute) - attribute_layout.addWidget(attribute_label) - - attribute_default = '0' - if attribute == 'frame_id': - attribute_default = 'world' - if trajectory_name in self.attribute_settings.keys(): - if attribute in self.attribute_settings[trajectory_name].keys(): - attribute_default = self.attribute_settings[trajectory_name][attribute] - - attribute_edit = qt.QLineEdit() - attribute_edit.textChanged.connect(get_attribute_changed_function(trajectory_name, - attribute)) - attribute_edit.setText(attribute_default) - - attribute_layout.addWidget(attribute_edit) - - trajectory_layout.addWidget(attribute_widget) - - #publish_button = qt.QPushButton('Publish') - #publish_button.clicked.connect(get_publish_function(trajectory_name)) - #trajectory_layout.addWidget(publish_button) - - self.tab_widget.addTab(trajectory_tab, trajectory_name) - - - - def timer_callback(self, msg): - bool_msg = Bool() - for key in self.button_dct.keys(): - bool_msg.data = self.button_dct[key]['data'] - self.button_dct[key]['publisher'].publish(bool_msg) - - def shutdown_plugin(self): - pass - - def save_settings(self, plugin_settings, instance_settings): - instance_settings.set_value('config_filename', self.config_filename) - instance_settings.set_value('attribute_settings', self.attribute_settings) - - def restore_settings(self, plugin_settings, instance_settings): - attribute_settings = instance_settings.value('attribute_settings') - if attribute_settings != None: - self.attribute_settings = attribute_settings - self.set_config(instance_settings.value('config_filename')) - - #def trigger_configuration(self): - # Comment in to signal that the plugin has a way to configure - # This will enable a setting button (gear icon) in each dock widget title bar - # Usually used to open a modal configuration dialog - diff --git a/robot/ros_ws/src/local/world_models/disparity_expansion/README.md b/robot/ros_ws/src/local/world_models/disparity_expansion/README.md index 8ddb1380a..1d58f9dd1 100644 --- a/robot/ros_ws/src/local/world_models/disparity_expansion/README.md +++ b/robot/ros_ws/src/local/world_models/disparity_expansion/README.md @@ -1,4 +1,4 @@ -# README # +# Disparity Expansion This package generates a world representation using disparity images. This enables planning in image space by applying C-space expansion in 2.5D disparity images. diff --git a/robot/ros_ws/src/local/world_models/disparity_graph/README.md b/robot/ros_ws/src/local/world_models/disparity_graph/README.md index fd2784da1..93b1529ae 100644 --- a/robot/ros_ws/src/local/world_models/disparity_graph/README.md +++ b/robot/ros_ws/src/local/world_models/disparity_graph/README.md @@ -1,6 +1,9 @@ # Disparity Graph +Maintains a sliding graph of keyframe disparity images and their camera poses for occupancy queries by disparity-based cost maps. A single disparity frame only covers the current camera view; keeping a rolling window of past (pose, expanded-disparity) keyframes gives the local planner spatial memory of obstacles the camera is no longer looking at. + +Used by [`disparity_graph_cost_map`](../disparity_graph_cost_map/README.md), which serves collision costs to the [DROAN local planner](../../planners/droan_local_planner/README.md). Expanded disparity inputs come from [`disparity_expansion`](../disparity_expansion/README.md). Contact: Andrew Jong -Docs TODO. Help appreciated. \ No newline at end of file +Docs TODO. Help appreciated. diff --git a/robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md b/robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md index dd477e3b4..e6a2df986 100644 --- a/robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md +++ b/robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md @@ -1,6 +1,7 @@ # Disparity Graph Cost Map +A cost map plugin backed by a [disparity graph](../disparity_graph/README.md): it answers "what is the collision cost of this 3D point?" by projecting the query point into the expanded disparity keyframes stored in the graph. The [DROAN local planner](../../planners/droan_local_planner/README.md) loads it via its `cost_map` parameter (`disparity_graph_cost_map::DisparityGraphCostMap`) to score candidate trajectories. Contact: Andrew Jong -Docs TODO. Help appreciated. \ No newline at end of file +Docs TODO. Help appreciated. diff --git a/simulation/isaac-sim/docker/docker-compose.yaml b/simulation/isaac-sim/docker/docker-compose.yaml index e36597196..68559b050 100644 --- a/simulation/isaac-sim/docker/docker-compose.yaml +++ b/simulation/isaac-sim/docker/docker-compose.yaml @@ -51,7 +51,7 @@ services: - PLAY_SIM_ON_START=${PLAY_SIM_ON_START} - NUM_ROBOTS=${NUM_ROBOTS:-1} - ENABLE_LIDAR=${ENABLE_LIDAR:-false} - # Fleet spawner input (RFC #380): fleet_spawn.py maps the robot-container + # Fleet spawner input: fleet_spawn.py maps the robot-container # path onto this container's /isaac-sim/AirStack checkout mount. - FLEET_CONFIG_FILE=${FLEET_CONFIG_FILE:-} - ISAAC_SIM_HEADLESS=${ISAAC_SIM_HEADLESS:-false} diff --git a/stacks/full_default/README.md b/stacks/full_default/README.md index 61cac43e5..ea19a447a 100644 --- a/stacks/full_default/README.md +++ b/stacks/full_default/README.md @@ -1,30 +1,29 @@ # `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. +The full-autonomy topology, as a self-contained stack folder. This is the +stack most users start from, the default when no stack is selected, and the +baseline other stacks are copied from. ## What it launches The entry point `launch/stack.launch.xml` composes **every layer as a flat -set of module-launch includes** (Local flattened in E2; sensors, perception, -global, and behavior in E3): LiDAR near-range filter, stereo +set of module-launch includes**: LiDAR near-range filter, stereo disparity/point-cloud pair, topic keepalive, takeoff/land task server, fixed-trajectory task server, GPU DROAN planner, trajectory controller, PID controller, VDB mapping, random-walk global planner, and the drone safety monitor — each module launch file declares its topic endpoints as args with -canonical defaults, so bare includes mean canonical wiring. Plus the two -role-`full` extras (DDS-router domain bridge to the GCS, gossip coordination -layer). Two blocks stay wrapped by design: `interface.launch.py` (the safety -boundary; flattens with RFC #380 Part 2's platform modules) and -`logging.launch.xml` (already a single self-contained module). +canonical defaults, so bare includes mean canonical wiring. Two cross-domain +extras run alongside: the DDS-router domain bridge to the GCS and the gossip +coordination layer. Two blocks stay wrapped by design: `interface.launch.py` +(the safety boundary) and `logging.launch.xml` (already a single +self-contained module). -## Equivalence +## Baseline -`airstack up --stack full_default` produces a ROS graph identical to the -removed legacy `AUTONOMY_ROLE=full` dispatch — **machine-proven** by the -wiring-snapshot diff (this folder's `wiring.md` is the committed baseline). -This stack is also the default: with no stack selected, `robot.launch.xml` -launches it. Verify with the wiring snapshot test: +This stack is the default: with no stack selected, `robot.launch.xml` +launches it. Its committed [wiring.md](wiring.md) is the observed-graph +baseline that other full stacks are compared against. Verify with the wiring +snapshot test: ```bash airstack test -m wiring --stack full_default --sim isaacsim --num-robots 1 @@ -38,7 +37,7 @@ airstack ready ``` The shared per-robot preamble (ROBOT_NAME namespace, `use_sim_time`, -`robot_state_publisher`, world→map static TF) still runs in +`robot_state_publisher`, world→map static TF) runs in `autonomy_bringup/launch/robot.launch.xml`, which dispatches to this stack when `AIRSTACK_STACK_DIR` is set. @@ -46,8 +45,7 @@ The shared per-robot preamble (ROBOT_NAME namespace, `use_sim_time`, - The interface layer is a wrapped include (`interface.launch.py`) — its MAVROS wiring is not visible in `stack.launch.xml`; read `wiring.md` for - the observed graph. It flattens with the platform-module refactor - (RFC #380 Part 2). + the observed graph. - `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. diff --git a/stacks/full_default/docker-compose.yaml b/stacks/full_default/docker-compose.yaml index 45e512d3b..b3c94abe6 100644 --- a/stacks/full_default/docker-compose.yaml +++ b/stacks/full_default/docker-compose.yaml @@ -1,4 +1,4 @@ -# Per-stack image composition (RFC #379 S6) arrives with this stack's first +# Per-stack image composition 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`. diff --git a/stacks/full_default/launch/stack.launch.xml b/stacks/full_default/launch/stack.launch.xml index f8e3e6327..104d464a9 100644 --- a/stacks/full_default/launch/stack.launch.xml +++ b/stacks/full_default/launch/stack.launch.xml @@ -1,5 +1,5 @@ + — never place this module offboard. --> + interface (cmd_roll_pitch_yawrate_thrust). ONBOARD-ONLY. --> + on safety events. The safety executive is ONBOARD-ONLY by design: it must keep working when the bridge drops. --> + on safety events. The safety executive is ONBOARD-ONLY by design: it must keep working when the bridge drops. -->