diff --git a/.agents/skills/update-documentation/SKILL.md b/.agents/skills/update-documentation/SKILL.md index 1cf13cfbd..4370112c6 100644 --- a/.agents/skills/update-documentation/SKILL.md +++ b/.agents/skills/update-documentation/SKILL.md @@ -351,15 +351,12 @@ Find the appropriate section based on your module type: ```yaml nav: - - Robot: - - Autonomy Modules: + - Reference: + - Autonomy Packages: - Local: - - Planning: - # Existing modules - - Trajectory Library: - - robot/ros_ws/src/local/planners/trajectory_library/README.md - - DROAN Local Planner: - - robot/ros_ws/src/local/planners/droan_local_planner/README.md + # Existing modules + - Trajectory Library: robot/ros_ws/src/local/planners/trajectory_library/README.md + - DROAN Local Planner: robot/ros_ws/src/local/planners/droan_local_planner/README.md # Add your module HERE - Your Module Name: - robot/ros_ws/src/local/planners/your_package/README.md diff --git a/.agents/skills/write-mkdocs-documentation/SKILL.md b/.agents/skills/write-mkdocs-documentation/SKILL.md index 9433f234c..ef110ef98 100644 --- a/.agents/skills/write-mkdocs-documentation/SKILL.md +++ b/.agents/skills/write-mkdocs-documentation/SKILL.md @@ -240,20 +240,25 @@ nav: ### Organization Pattern +The site nav is organized by the [Diátaxis](https://diataxis.fr) quadrants — top-level tabs are document *kinds*, with product areas grouped inside each tab: + ``` -docs/ -├── getting_started/ # New user onboarding -├── development/ # Developer guides -│ ├── beginner/ # Entry-level tutorials -│ ├── intermediate/ # Testing, best practices -│ └── advanced/ # Deep dives -├── robot/ # Robot-specific docs -│ ├── autonomy/ # Autonomy stack integration -│ └── configuration/ # Configuration guides -└── simulation/ # Simulation setup +nav tabs (mkdocs.yml) +├── Tutorials # learning-oriented lessons (getting_started/, guided walkthroughs) +├── How-to Guides # task recipes (dev env, docker, simulation, GCS, field, testing, contributing) +├── Reference # look-up material (CLI, configuration schemas, interface spec, containers, packages) +├── Concepts # explanation (architecture, modular design, identity, sim platforms) +├── Release Notes +└── About + +File locations stay stable (docs/development/, docs/robot/, docs/simulation/, …); +the nav labels — not directories — carry the taxonomy. New pages: pick the +quadrant first (see the decision tree in docs/development/intermediate/documentation.md), +then add the file under the closest existing directory and nav-link it in the +matching tab. robot/ros_ws/src/ -├── local/planners/my_planner/README.md # Module docs +├── local/planners/my_planner/README.md # Module docs (all-in-one; exempt from the one-quadrant rule) └── perception/state_est/README.md # Module docs ``` diff --git a/.env b/.env index a561ae2c5..f88541d12 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.14" +VERSION="0.20.0-alpha.15" # 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/AGENTS.md b/AGENTS.md index b391d74ee..14302b322 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,8 +77,8 @@ Modules communicate via ROS 2 topics. Common standard topics: |--------------|------|---------| | `/{robot_name}/odometry` | nav_msgs/Odometry | Robot state estimation | | `/{robot_name}/global_plan` | nav_msgs/Path | Global waypoint path | -| `/{robot_name}/trajectory_controller/trajectory_override` | airstack_msgs/TrajectoryOverride | Direct trajectory commands | -| `/{robot_name}/trajectory_controller/trajectory_segment_to_add` | airstack_msgs/TrajectorySegment | Planned trajectory segment | +| `/{robot_name}/trajectory_controller/trajectory_override` | airstack_msgs/TrajectoryXYZVYaw | Direct trajectory commands | +| `/{robot_name}/trajectory_controller/trajectory_segment_to_add` | airstack_msgs/TrajectoryXYZVYaw | Planned trajectory segment | | `/{robot_name}/trajectory_controller/look_ahead` | geometry_msgs/PointStamped | Look-ahead point for planning | **Note:** Topics are remapped in bringup launch files to connect modules. Input/output topics should be configurable via launch arguments. @@ -152,7 +152,7 @@ Study these well-structured modules as examples for different types: |------------|------------------|----------| | **Local Planner** | DROAN Local Planner | `robot/ros_ws/src/local/planners/droan_local_planner` | | **Local World Model** | Disparity Expansion | `robot/ros_ws/src/local/world_models/disparity_expansion` | -| **Controller** | Trajectory Controller | `robot/ros_ws/src/local/c_controls/trajectory_controller` | +| **Controller** | Trajectory Controller | `robot/ros_ws/src/local/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** | Drone Safety Monitor | `robot/ros_ws/src/behavior/drone_safety_monitor` | @@ -359,14 +359,14 @@ Create `README.md` in the package directory with: Add the module README to the navigation structure: ```yaml nav: - - Robot: - - Autonomy Modules: + - Reference: + - Autonomy Packages: - Local: - - Planning: - - Your Module: - - robot/ros_ws/src/local/planners/your_package/README.md + - Your Module: robot/ros_ws/src/local/planners/your_package/README.md ``` +The nav is organized by [Diátaxis](https://diataxis.fr) tabs (Tutorials / How-to Guides / Reference / Concepts); package READMEs live under Reference → Autonomy Packages. Pick the quadrant for any new system-level page with the decision tree in [docs/development/intermediate/documentation.md](docs/development/intermediate/documentation.md). + The `same-dir` plugin allows linking to README files outside the `docs/` directory. ### 3. System-Level Documentation (if needed) diff --git a/common/ros_packages/msgs/airstack_msgs/README.md b/common/ros_packages/msgs/airstack_msgs/README.md new file mode 100644 index 000000000..c42a8ba74 --- /dev/null +++ b/common/ros_packages/msgs/airstack_msgs/README.md @@ -0,0 +1,181 @@ +# airstack_msgs + +`airstack_msgs` is AirStack's core interface package: the ROS 2 message and service definitions that modules exchange at the stack's interchange points — trajectories and waypoints for the trajectory controller, an extended odometry type, and the command services for the robot interface, trajectory controller, and takeoff/landing planner. The package defines the *types*; which canonical topics and services carry them is specified authoritatively by the [Interface Conventions Specification](../../../../docs/robot/autonomy/interface_conventions.md) (types and QoS there are verified against the observed wiring of the reference stack). + +A number of definitions in this package have **no consumer in the AirStack trunk** — they were added for search-and-track mission planning work and are kept for downstream/module use. The tables below say so explicitly rather than inventing semantics; do not build new integrations on them without checking their real users first. + +## At a glance + +| Definition | Kind | Status in trunk | +|---|---|---| +| [`Odometry`](msg/Odometry.msg) | msg | **Active** — `trajectory_controller/tracking_point`, `look_ahead` (spec [§5](../../../../docs/robot/autonomy/interface_conventions.md#5-trajectory-group--the-trajectory-controllers-contract--onboard-only)) | +| [`TrajectoryXYZVYaw`](msg/TrajectoryXYZVYaw.msg) | msg | **Active** — the trajectory-controller command surface (spec §5) | +| [`WaypointXYZVYaw`](msg/WaypointXYZVYaw.msg) | msg | **Active** — element of `TrajectoryXYZVYaw` | +| [`FixedTrajectory`](msg/FixedTrajectory.msg) | msg | **Active** — goal payload of `task_msgs/action/FixedTrajectoryTask` | +| [`KeepOutZone`](msg/KeepOutZone.msg) | msg | Defined; no trunk publisher/subscriber (referenced only inside `PlanRequest`/`SearchMissionRequest`) | +| [`PlanRequest`](msg/PlanRequest.msg) | msg | Defined; no trunk publisher/subscriber | +| [`SearchMissionRequest`](msg/SearchMissionRequest.msg) | msg | Defined; no trunk publisher/subscriber | +| [`SearchPrior`](msg/SearchPrior.msg) | msg | Defined; no trunk publisher/subscriber (referenced only inside `PlanRequest`/`SearchMissionRequest`) | +| [`TaskAssignment`](msg/TaskAssignment.msg) | msg | Defined; no trunk publisher/subscriber | +| [`query/TextQueryResponse`](msg/query/TextQueryResponse.msg) | msg | Defined; no trunk consumer (intended for semantic-query modules) | +| [`RobotCommand`](srv/RobotCommand.srv) | srv | **Active** — served by `robot_interface` at `interface/robot_command` (spec §7) | +| [`TakeoffLandingCommand`](srv/TakeoffLandingCommand.srv) | srv | **Active** — served by `takeoff_landing_planner` at `takeoff_landing_planner/set_takeoff_landing_command`; the GCS-facing takeoff/land command (spec §8, related-service note) | +| [`TrajectoryMode`](srv/TrajectoryMode.srv) | srv | **Active** — served by `trajectory_controller` at `trajectory_controller/set_trajectory_mode` (spec §5) | +| [`PlanToWaypoint`](srv/PlanToWaypoint.srv) | srv | Defined but **not built** — absent from the `rosidl_generate_interfaces` list in [CMakeLists.txt](CMakeLists.txt); no trunk consumer | + +## Messages + +### Odometry + +Extended odometry: `nav_msgs/Odometry` minus the covariances, plus feed-forward acceleration and jerk. This is the type the trajectory controller publishes on `trajectory_controller/tracking_point` and `trajectory_controller/look_ahead` (note: **airstack_msgs**, not nav_msgs — a classic type mismatch when wiring new modules; see spec §5). Consumers include the PID controller, both DROAN planners, the takeoff/landing planner, the random-walk global planner, and the drone safety monitor. + +| Field | Type | Meaning | +|---|---|---| +| `header` | `std_msgs/Header` | Stamp + frame of `pose` | +| `child_frame_id` | `string` | Frame of `twist` (body frame) | +| `pose` | `geometry_msgs/Pose` | Position and orientation | +| `twist` | `geometry_msgs/Twist` | Linear/angular velocity | +| `acceleration` | `geometry_msgs/Vector3` | Linear acceleration (feed-forward term for the controller) | +| `jerk` | `geometry_msgs/Vector3` | Linear jerk (feed-forward term for the controller) | + +### TrajectoryXYZVYaw and WaypointXYZVYaw + +The trajectory command interchange (spec §5, **onboard-only**): planners send these to the trajectory controller on `trajectory_controller/trajectory_override` (replaces the current trajectory) and `trajectory_controller/trajectory_segment_to_add` (appends). Producers in the trunk: `droan_local_planner`, `droan_gl`, `takeoff_landing_planner`, and the `fixed_trajectory_task` server; the [trajectory_library](../../../../robot/ros_ws/src/local/planners/trajectory_library/README.md) package converts between this type and its internal `Trajectory` class. + +`TrajectoryXYZVYaw`: + +| Field | Type | Meaning | +|---|---|---| +| `header` | `std_msgs/Header` | Stamp + frame the waypoints are expressed in | +| `waypoints` | `WaypointXYZVYaw[]` | Ordered waypoint list | + +`WaypointXYZVYaw` ("XYZ, Velocity, Yaw"): + +| Field | Type | Meaning | +|---|---|---| +| `position` | `geometry_msgs/Point` | Waypoint position (m, trajectory frame) | +| `velocity` | `float64` | Speed *magnitude* at this waypoint (m/s) — direction is inferred from the segment direction (see `trajectory_library`'s `Trajectory` constructor) | +| `yaw` | `float64` | Heading (rad, about +Z) | +| `acceleration` | `geometry_msgs/Vector3` | Feed-forward acceleration (often left zero) | +| `jerk` | `geometry_msgs/Vector3` | Feed-forward jerk (often left zero) | + +### FixedTrajectory + +A parametric trajectory specification: a shape `type` plus free-form key/value `attributes`. It is the goal payload of `task_msgs/action/FixedTrajectoryTask` (`tasks/fixed_trajectory`, served by the `fixed_trajectory_task` node in the `trajectory_controller` package). Types accepted by the trunk server: `Figure8`, `Circle`, `Racetrack`, `Line`, `Point`, `Lawnmower`; attributes are shape parameters such as `frame_id`, `velocity`, `radius`, `length`, `width`, `height` (see `robot/ros_ws/src/local/controls/trajectory_controller/src/fixed_trajectory_task.cpp` for each shape's accepted keys). Goals are sent from the GCS `action_relay` and the RViz Tasks Panel. + +| Field | Type | Meaning | +|---|---|---| +| `type` | `string` | Trajectory shape name (e.g. `Figure8`) | +| `attributes` | `diagnostic_msgs/KeyValue[]` | Shape parameters as string key/value pairs | + +### KeepOutZone + +A vertical cylinder to avoid: center `(x, y)`, a `z` band, and a radius. **Defined; no trunk publisher/subscriber** — it appears only as a field of `PlanRequest` and `SearchMissionRequest` below. + +| Field | Type | Meaning | +|---|---|---| +| `header` | `std_msgs/Header` | Stamp + frame | +| `x`, `y` | `float64` | Cylinder center (m) | +| `z_min`, `z_max` | `float64` | Vertical extent (m) | +| `radius` | `float64` | Cylinder radius (m) | + +### PlanRequest + +A request for a search/coverage plan: start state, wind, planning budget, speed objective, search bounds, priors, and keep-out zones. **Defined; no trunk publisher/subscriber.** Field meanings below come from the comments in the `.msg` file. + +| Field | Type | Meaning (from file comments) | +|---|---|---| +| `header` | `std_msgs/Header` | — | +| `start_pose` | `geometry_msgs/Pose` | Plan from this position and orientation | +| `wind_speed` | `geometry_msgs/Vector3` | Wind speed in m/s | +| `max_planning_time` | `float32` | Seconds | +| `maximum_range` | `float32` | Budget | +| `desired_speed` | `float32` | Desired flight speed in m/s | +| `search_bounds` | `geometry_msgs/Polygon` | Constraint/objective region | +| `search_priors` | `SearchPrior[]` | Prior information | +| `keep_out_zones` | `KeepOutZone[]` | Prior information | +| `clear_tree` | `bool` | Replan flag | +| `scenario` | `uint32` | "To match up with the correct plan" | + +### SearchMissionRequest + +A search mission: bounds, priors, and keep-out zones (a `PlanRequest` without the per-plan budget/state fields). **Defined; no trunk publisher/subscriber.** + +| Field | Type | Meaning | +|---|---|---| +| `header` | `std_msgs/Header` | — | +| `search_bounds` | `geometry_msgs/Polygon` | Mission area | +| `search_priors` | `SearchPrior[]` | Prior information | +| `keep_out_zones` | `KeepOutZone[]` | Zones to avoid | + +### SearchPrior + +Prior probability information over a region for search planning. **Defined; no trunk publisher/subscriber** (referenced only inside `PlanRequest`/`SearchMissionRequest`). + +| Field | Type | Meaning (from file comments) | +|---|---|---| +| `header` | `std_msgs/Header` | — | +| `grid_prior_type` | `uint8` | One of `POLYGON_PRIOR=1` (must be a convex polygon), `LINE_SEG_PRIOR=2`, `POINT_PRIOR=3` | +| `points_list` | `geometry_msgs/Polygon` | Polygon, line segment, or list of points | +| `value` | `float32[]` | Initial value for the region | +| `priority` | `float32[]` | Higher = more important; empty ⇒ 1.0 | +| `sensor_model_id` | `uint8[]` | Sensor model type per region; default 0 | + +### TaskAssignment + +Assigns a search or track task (with an embedded `PlanRequest`) to a robot. **Defined; no trunk publisher/subscriber.** + +| Field | Type | Meaning | +|---|---|---| +| `header` | `std_msgs/Header` | — | +| `assigned_task_type` | `uint8` | `SEARCH=1` or `TRACK=2` | +| `assigned_task_number` | `uint32` | Per the file comment: "index of the target to track?" | +| `plan_request` | `PlanRequest` | The plan to execute | + +### query/TextQueryResponse + +Response to a text query against a robot semantic map ("robot semantic query" per the file comment): a tag plus a GPS geofence for where it applies. **Defined and built; no trunk consumer** — intended for semantic-search modules. + +| Field | Type | Meaning | +|---|---|---| +| `header` | `std_msgs/Header` | Timestamp and frame_id | +| `tag_name` | `string` | The tag associated with the query or response | +| `geofence` | `sensor_msgs/NavSatFix[]` | GPS fixes outlining the geofence | + +## Services + +### RobotCommand + +Low-level vehicle commands into the interface layer. Served by `robot_interface` at `interface/robot_command` (spec §7); called by the takeoff/landing task executors to request control, arm, and command takeoff/land through MAVROS/PX4. + +| Request | Response | +|---|---| +| `uint8 command` — one of `REQUEST_CONTROL=0`, `ARM=1`, `DISARM=2`, `TAKEOFF=3`, `LAND=4`, `SET_LOW_THRUST_MODE=5`, `UNSET_LOW_THRUST_MODE=6` | `bool success` | + +### TakeoffLandingCommand + +The GCS-facing takeoff/land command, served by `takeoff_landing_planner` at `takeoff_landing_planner/set_takeoff_landing_command` (spec §8, related-service note). The planner runs the full sequence (request control → arm → generate takeoff/landing trajectory → hand it to the trajectory controller) on behalf of the caller. + +| Request | Response | +|---|---| +| `uint8 command` — one of `TAKEOFF=0`, `LAND=1`, `NONE=2` | `bool accepted` | + +### TrajectoryMode + +Sets the trajectory controller's tracking mode; served at `trajectory_controller/set_trajectory_mode` (spec §5). Task servers and the safety monitor call it to pause, hold pose, track, append segments, or rewind. See the [Trajectory Controller README](../../../../robot/ros_ws/src/local/controls/trajectory_controller/README.md) for what each mode does. + +| Request | Response | +|---|---| +| `int32 mode` — one of `PAUSE=0`, `ROBOT_POSE=1`, `TRACK=2`, `ADD_SEGMENT=3`, `REWIND=4` | `bool success` | + +### PlanToWaypoint + +Requests a plan to a goal pose. **Defined in `srv/` but not listed in `CMakeLists.txt`'s `rosidl_generate_interfaces`, so it is not generated or usable**; no trunk consumer. + +| Request | Response | +|---|---| +| `geometry_msgs/Pose goal_pose`, `uint8 command` | `bool success` | + +## Sibling interface packages + +Two sibling packages under `common/ros_packages/msgs/` complete AirStack's shared interfaces: [`task_msgs`](../task_msgs/) defines the eight task action types (`TakeoffTask`, `LandTask`, `NavigateTask`, `FixedTrajectoryTask`, `ExplorationTask`, `SemanticSearchTask`, `CoverageTask`, `ChatTask`) served at `tasks/` — see [Task Executors](../../../../docs/robot/autonomy/tasks.md) and spec §8; and [`behavior_tree_msgs`](../behavior_tree_msgs/) carries the behavior-tree engine's runtime plumbing — node `Status` (FAILURE/RUNNING/SUCCESS) and `Active` signals, `BehaviorTreeCommand(s)` for setting condition states, and `GraphVizXdot(Compressed)` for streaming the rendered tree to GUIs. diff --git a/docs/README.md b/docs/README.md index b4d60cf94..e66c84748 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,13 +27,13 @@ AirStack is a comprehensive, modular autonomy stack for embodied AI and robotics ## 📋 System Requirements - **Docker**: With NVIDIA Container Toolkit support -- **NVIDIA GPU**: RTX 3070 or better (for local Isaac Sim) -- **Storage**: At least 25GB free space for Docker images -- **OS**: Ubuntu 22.04 recommended +- **NVIDIA GPU**: RTX 3070 minimum, RTX 4080 or better recommended (for local Isaac Sim) +- **Storage**: Docker images take ~25GB; 100GB free disk space recommended +- **OS**: Ubuntu 22.04 or 24.04 ## 🔧 Quick Start -Follow the instructions at https://docs.theairlab.org/latest/docs/getting_started/ to set up AirStack on your machine, then take the [Modular AirStack Walkthrough](getting_started/modular_airstack.md). +Follow the instructions in [Getting Started](./getting_started/index.md) to set up AirStack on your machine, then take the [Modular AirStack Walkthrough](getting_started/modular_airstack.md). ## 🏗️ System Architecture diff --git a/docs/about.md b/docs/about.md index dbb58c335..eda95583b 100644 --- a/docs/about.md +++ b/docs/about.md @@ -61,8 +61,8 @@ See: [System Architecture](robot/autonomy/system_architecture.md) - Gazebo (planned) ### Hardware -- NVIDIA Jetson (Orin, Xavier NX, TX2) -- ModalAI VOXL (VOXL 2, VOXL Flight) +- NVIDIA Jetson (Orin AGX/NX) +- ModalAI VOXL 2 (Docker profile exists; docs in progress) - x86-64 desktop/laptop (development) ### Software @@ -85,7 +85,7 @@ See: [System Architecture](robot/autonomy/system_architecture.md) Currently, AirStack is in alpha and limited to AirLab members and collaborators. We plan to release it publicly once it reaches a stable state. !!! question "What hardware do I need?" - **For development**: Ubuntu 22.04 machine with NVIDIA GPU (RTX 3070+ recommended), 16GB+ RAM, and 100GB+ free storage. + **For development**: Ubuntu 22.04 or 24.04 machine with NVIDIA GPU (RTX 3070 minimum, RTX 4080 or better recommended), 16GB+ RAM, and 100GB free storage recommended (Docker images take ~25GB). **For deployment**: NVIDIA Jetson or ModalAI VOXL onboard computer. @@ -124,7 +124,7 @@ See: [System Architecture](robot/autonomy/system_architecture.md) !!! question "How do I debug a module?" Build with debug symbols, use ROS 2 tools (`ros2 topic echo`, `ros2 node info`), and leverage the debug-module skill from the AI Agent Guide. - See: [Development Environment](development/development_environment.md) + See: [Development Environment](development/beginner/development_environment.md) !!! question "Where should I add my custom module?" Place it in the appropriate layer under `robot/ros_ws/src////`. For example, a new planner goes in `robot/ros_ws/src/local/planners/my_planner/`. diff --git a/docs/development/adding_a_vehicle.md b/docs/development/adding_a_vehicle.md new file mode 100644 index 000000000..2caf9a69f --- /dev/null +++ b/docs/development/adding_a_vehicle.md @@ -0,0 +1,92 @@ +# Adding a Vehicle Type, Unit, or Platform + +This guide shows how to add a new airframe to AirStack at each level of the vehicle hierarchy ([fleets guide](fleets.md#the-hierarchy)): a **platform class** is code (interface, controller — `px4_multirotor` is the only one today), a **vehicle type** is data (`config/vehicles//vehicle.yaml`), and a **vehicle unit** is one serial number's calibration overlay. A fleet entry binds all three to a robot instance. The three asks are very different sizes: a new type is a YAML file, a new unit is a gitignored directory, a new compute platform is a Docker build chain. + +The canonical schema reference is [`config/vehicles/README.md`](../../config/vehicles/README.md) — this guide doesn't restate it. + +## A. Adding a new vehicle type + +The common case: a different airframe or sensor suite on the existing `px4_multirotor` platform. + +1. **Copy the reference type.** `quad_default` (the Pegasus Iris with stereo camera + 3D lidar) is the only in-tree type and the template: + + ```bash + cp -r config/vehicles/quad_default config/vehicles/my_quad + ``` + +2. **Edit `config/vehicles/my_quad/vehicle.yaml`** per the [schema README](../../config/vehicles/README.md): set `name:`, keep `platform: px4_multirotor` (the only platform today), point `airframe.base_urdf:` at your URDF (package-relative, exactly what `URDF_FILE` carries — the file must exist under the robot description packages), and declare each sensor as one `{type, id, frame, driver, sim}` entry. `sim_asset:` names the sim asset — but note vehicles are **pass-through in sim today**: [`fleet_spawn.py`](https://github.com/castacks/AirStack/blob/develop/simulation/isaac-sim/launch_scripts/fleet_spawn.py) spawns the Pegasus Iris asset for every `px4_multirotor` regardless. What *does* take effect per vehicle is the sensor list: any `lidar*`-typed entry enables the RTX lidar subgraph for that robot, any `stereo_cam` entry the camera subgraph (the per-vehicle `ENABLE_LIDAR` equivalent). + +3. **Reference it from a fleet file.** Copy `config/fleets/sim_one_default.yaml` and change the vehicle binding: + + ```yaml + defaults: {vehicle: my_quad, stack: stacks/full_default} + robots: + robot_1: {spawn: [0, 0, 0.07]} + sim: {scene: default} + network: {domain_policy: auto, gossip_domain: 99} + ``` + +4. **Validate before launching.** The resolver names schema errors (unknown vehicle, missing `airframe.base_urdf`, unknown keys): + + ```bash + python3 tools/fleet/resolve_fleet.py config/fleets/my_fleet.yaml --validate # "OK: 1 robot(s), homogeneous fleet" + python3 tools/fleet/resolve_fleet.py config/fleets/my_fleet.yaml --table # ROBOT/DOMAIN/VEHICLE/STACK/ENTRY/HOSTS/SPAWN + airstack fleet list + ``` + +5. **Launch and verify in the container.** `airstack up --fleet my_fleet --sim isaac`, then `airstack ready`. Inside the robot container, [`tools/fleet/resolve_fleet.py`](https://github.com/castacks/AirStack/blob/develop/tools/fleet/resolve_fleet.py) has resolved the whole entry from `FLEET_CONFIG_FILE` (called by `robot/docker/.bashrc`); confirm the exports: + + ```bash + docker exec airstack-robot-desktop-1 bash -c 'echo $VEHICLE $URDF_FILE' + # my_quad robot_descriptions/.../my_urdf.urdf + ``` + +Caveats worth knowing: an explicitly set env var wins over the resolver per variable (`URDF_FILE`, `ROBOT_NAME`, …), and `airstack up --sim airsim` keeps exporting ms-airsim's reduced stereo-only URDF regardless of the manifest. Vehicle types beyond trunk are intended to arrive as data modules (`type: data`, RFC #379) rather than commits to `config/vehicles/`. + +## B. Adding a new unit of an existing type + +A **unit** is one physical serial number whose calibration drifts and gets re-measured — it never touches the shared type. Full story: [`config/local/README.md`](../../config/local/README.md). + +1. **Create the calibration overlay** on the machine that flies (or resolves) that airframe — `config/local/` is gitignored, per-machine: + + ```bash + mkdir -p config/local/calibration/SN-0042 + # drop the intrinsics/extrinsics files your drivers consume — no enforced layout yet + ``` + +2. **Bind it in the fleet entry:** + + ```yaml + robots: + robot_1: {vehicle: quad_default, unit: SN-0042} + ``` + +3. **Consume it via `CALIBRATION_DIR`.** The resolver exports `CALIBRATION_DIR=/root/AirStack/config/local/calibration/SN-0042` into the robot container (`config/` is bind-mounted read-only at `/root/AirStack/config`); point driver configs at `$CALIBRATION_DIR`. It is empty when the fleet entry declares no `unit:`. Verify: + + ```bash + python3 tools/fleet/resolve_fleet.py config/fleets/my_fleet.yaml --robot robot_1 | grep CALIBRATION_DIR + ``` + +Recalibrating in the field writes into the unit directory — never into `config/vehicles//`. + +## C. Adding a new compute platform + +This is a much bigger lift than A or B: a platform is a **Docker compose profile + build-arg chain**, and there are exactly two non-desktop precedents — Jetson L4T (`l4t` profile) and ModalAI VOXL 2 (`voxl` profile), both in [`robot/docker/docker-compose.yaml`](https://github.com/castacks/AirStack/blob/develop/robot/docker/docker-compose.yaml). Read [Docker build profiles](intermediate/docker-build-profiles.md) and the [`docker-build-profiles` skill](../../.agents/skills/docker-build-profiles/SKILL.md) first; current status of every platform is in [Supported Platforms](../real_world/supported_platforms.md). + +1. **Add a service block** in `robot/docker/docker-compose.yaml` under its own profile, extending `robot_base` from `robot-base-docker-compose.yaml`. Everything builds from the single `Dockerfile.robot`; the variant is selected by `build.args`: `BASE_IMAGE`, `ROS_DISTRO: jazzy`, `REAL_ROBOT: true`, `TARGET_ARCH: aarch64` (both hardware precedents), and a **quoted** `PYTHON_VERSION` when needed (`"3.12"` — unquoted YAML parses it as float `3.1`). + +2. **Study the precedent closest to your board.** L4T is the full worked example: an intermediate `robot-l4t-stack-base` image (`Dockerfile.l4t-stack-base` on `dustynv/ros:jazzy-ros-base-r36.4.0-cu128-24.04`) feeds `Dockerfile.robot` as `BASE_IMAGE`, both with `network: host` under `build:` (a Jetson build workaround), plus `runtime: nvidia`, `network_mode: host`, and a companion `zed-l4t` driver service. VOXL is the minimal CUDA-less variant: `BASE_IMAGE: ubuntu:24.04`, `deploy: !reset {}` to drop the GPU reservation, and a compute-constrained default of `AIRSTACK_STACK_DIR=.../stacks/lite_default`. + +3. **Set the runtime environment** the way both precedents do: `ROBOT_NAME_SOURCE=hostname` (real robots resolve identity from the host, not the container name), `LAUNCH_PACKAGE=autonomy_bringup` (no RViz), an overridable `AIRSTACK_STACK_DIR` default, and a tmux `autolaunch ... sim:=false` command. + +4. **Give the image both cache tags.** Every service with a `build:` section lists the versioned image *and* the floating `${CACHE_TAG:-cache}_...` tag in `tags:` and `cache_from:` — skip this and CI builds of your service are always cold (see the cache section in [`AGENTS.md`](https://github.com/castacks/AirStack/blob/develop/AGENTS.md)). + +5. **Build and verify:** + + ```bash + airstack images build --profile myboard robot-myboard # l4t builds its stack-base first this way + airstack up robot-myboard --no-autolaunch + docker exec airstack-robot-myboard-1 bash -c "bws && sws && ros2 node list" + ``` + +Be honest about what you get: **CI covers only the desktop x86 simulation path** ([Supported Platforms — what CI covers](../real_world/supported_platforms.md#what-ci-does-and-does-not-cover)). No CI job builds or runs `l4t`, `voxl`, or your new profile — its status is whatever you verify on hardware. Add a row to the [platform matrix](../real_world/supported_platforms.md) stating exactly that. diff --git a/docs/development/advanced/ai_agent_guide.md b/docs/development/advanced/ai_agent_guide.md index 282787cf0..2d33d5593 100644 --- a/docs/development/advanced/ai_agent_guide.md +++ b/docs/development/advanced/ai_agent_guide.md @@ -88,7 +88,7 @@ docker exec airstack-robot-desktop-1 bash -c "ros2 bag record -a -o /tmp/test" /[robot_name]/[layer]/[module]/[data_type] ``` -Example: `/drone1/local_planner/droan/trajectory` +Example: `/drone1/trajectory_controller/trajectory_segment_to_add` ### Standard Topics - `/[robot]/odometry` - State estimate @@ -123,7 +123,7 @@ Study these before implementing similar modules: | Module Type | Reference | Location | |------------|-----------|----------| | Local Planner | DROAN | `robot/ros_ws/src/local/planners/droan_local_planner` | -| Controller | Trajectory Controller | `robot/ros_ws/src/local/c_controls/trajectory_controller` | +| Controller | Trajectory Controller | `robot/ros_ws/src/local/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 / Safety | Drone Safety Monitor | `robot/ros_ws/src/behavior/drone_safety_monitor` | diff --git a/docs/development/airstack-cli/index.md b/docs/development/airstack-cli/index.md deleted file mode 100644 index d39ff52e9..000000000 --- a/docs/development/airstack-cli/index.md +++ /dev/null @@ -1,130 +0,0 @@ -# AirStack CLI Tool - -The AirStack CLI tool (`airstack.sh`) provides a unified interface for common development tasks in the AirStack project, including setup, installation, and container management. It simplifies the development workflow and ensures consistency across different environments. - -## Overview - -The AirStack CLI tool is designed to be: - -- **Modular**: Commands are organized into modules that can be easily extended -- **Consistent**: Provides a unified interface for all AirStack-related tasks -- **Helpful**: Includes detailed help text and error messages -- **Extensible**: New commands can be added without modifying the core script - -At its core, `airstack.sh` is simply a light wrapper around [docker compose](https://docs.docker.com/compose/), providing additional functionality and convenience for AirStack development. - -## Basic Usage - -```bash -./airstack.sh [options] -``` - -To see all available commands: - -```bash -./airstack.sh commands -``` - -To get help for a specific command: - -```bash -./airstack.sh help -``` - -## Adding to Shell Profile - -For convenience, you can add the AirStack CLI to your shell profile to make it available from any directory: - -```bash -./airstack.sh setup -# now you can use `airstack` instead of `./airstack.sh` -airstack commands -``` - -This will add the necessary aliases to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.) so you can use the `airstack` command from any directory. - -## Core Commands - -The AirStack CLI includes several built-in commands: - -| Command | Description | -|---------|-------------| -| `install` | Install dependencies (Docker Engine, Docker Compose, etc.) | -| `setup` | Configure AirStack settings and add to shell profile | -| `up` | Start services using Docker Compose | -| `down` | Stop services | -| `connect` | Connect to a running container (supports partial name matching) | -| `status` | Show status of all containers | -| `logs` | View logs for a container (supports partial name matching) | -| `help` | Show help information | - -### Installation - -The `install` command sets up the necessary dependencies for AirStack development: - -```bash -airstack install [options] -``` - -Options: - -- `--force`: Force reinstallation of components -- `--no-docker`: Skip Docker installation - -### Setup - -The `setup` command configures your environment for AirStack development: - -```bash -airstack setup [options] -``` - -Options: - -- `--no-shell`: Skip adding to shell profile -- `--no-config`: Skip configuration tasks - -### Container Management - -The AirStack CLI provides several commands for managing Docker containers: - -```bash -# Start services -airstack up [service...] -airstack up robot-desktop # start only the robot service -airstack up isaac-sim # start only the Isaac Sim service -airstack up gcs # start only the Ground Control Station service -airstack up docs # start only the documentation service - -# Stop services -airstack down [service...] - -# Show container status -airstack status - -# Connect to a container shell, supports partial name matching -airstack connect - -# View container logs -airstack logs -``` - -## Module-Specific Commands - -In addition to the core commands, the AirStack CLI includes several module-specific commands: - -### Configuration Commands - -```bash -# Run all configuration tasks -airstack config - -# Configure Isaac Sim -airstack config isaac-sim - -# Configure Nucleus -airstack config nucleus - -# Configure Git hooks -airstack config git-hooks -``` diff --git a/docs/development/beginner/airstack-cli/docker_usage.md b/docs/development/beginner/airstack-cli/docker_usage.md index bb7d52d69..f1e76507f 100644 --- a/docs/development/beginner/airstack-cli/docker_usage.md +++ b/docs/development/beginner/airstack-cli/docker_usage.md @@ -60,7 +60,7 @@ airstack connect isaac-sim # or equivalently: docker exec -it isaac-sim bash Within the isaac-sim Docker container, the alias `runapp` launches Isaac Sim. The `--path` argument can be passed with a path to a `.usd` file to load a scene. -It can also be run in headless mode with `./runheadless.native.sh` to stream to [Omniverse Streaming Client](https://docs.omniverse.nvidia.com/streaming-client/latest/user-manual.html) or `./runheadless.webrtc.sh` to [stream to a web browser](https://docs.omniverse.nvidia.com/extensions/latest/ext_livestream/webrtc.html). +It can also be run in headless mode (`airstack up --sim isaac --headless`) and accessed remotely via WebRTC streaming — see [Isaac Sim Docker → Accessing Isaac Sim](../../../simulation/isaac_sim/docker.md) for the current access methods. (The Omniverse Streaming Client and `runheadless.native.sh` have been discontinued upstream.) The container also has the isaacsim ROS2 package within that can be launched with `ros2 launch isaacsim run_isaacsim.launch.py`. @@ -82,18 +82,16 @@ airstack connect robot-2 # to connect to robot 2 ### Launch flags and readiness -`airstack up` accepts intent flags that derive the coordinated env-var sets for you (they override `.env` for that run without editing it): +`airstack up` accepts intent flags that derive the coordinated env-var sets for you (they override `.env` for that run without editing it). For example: ```bash -airstack up --sim isaac|airsim # pick the simulator: compose profile + matching URDF (+ Isaac script) -airstack up --robots N # NUM_ROBOTS + single/multi Isaac launch script -airstack up --headless # no sim window (ISAAC_SIM_HEADLESS / MS_AIRSIM_HEADLESS) -airstack up --play / --no-play # PLAY_SIM_ON_START override -airstack up --no-autolaunch # idle containers, no tmux launch (development) -airstack up --wait # block until the stack is flight-ready -airstack up --dry-run # print + validate the resolved config; start nothing +airstack up --sim isaac --robots 2 # simulator profile + matching URDF + multi-drone script +airstack up --no-autolaunch # idle containers, no tmux launch (development) +airstack up --headless --wait # no sim window; block until flight-ready ``` +The full flags table (`--play`/`--no-play`, `--dry-run`, `--stack`, `--fleet`, `--scene`, ...) is in the [CLI reference](index.md#airstack-up-flags). + Every `up` prints the resolved launch config and saves it to `.airstack/runs//effective_config.env`. Preflight validates the resolved values (one simulator profile, URDF pairing, robot-count/script consistency, missing images by name) before compose runs; `AIRSTACK_SKIP_PREFLIGHT=1` downgrades errors to warnings. `airstack up` returns as soon as containers start — workspaces may still be building and the sim loading. To wait for actual flight-readiness (containers → sim `/clock` → per-robot autonomy nodes → PX4 connected + EKF armable): @@ -146,27 +144,17 @@ You can get the IP address of each container by running the following command: docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [CONTAINER-NAME] ``` -Then ssh in, for example: +Then ssh in, for example (containers get addresses on the `172.31.0.0/24` bridge network; use the `docker inspect` command above to find the actual IP): ```bash -ssh root@172.18.0.6 +ssh root@172.31.0.5 ``` The ssh password is `airstack`. ## Automated Testing -To perform automated tests for the configured packages, please use the `autotest` service which -extends the `robot` service with testing specific commands. Presently only `takeoff_landing_planner` -is configured to be tested. - -```bash -# On your development PC, do: - -docker compose up robot-test -``` - -This command will spin up a `robot` container, build the ROS2 workspace, source the workspace and run all the configured tests for the provided packages using `colcon test`. Excessive output log from the build process is presently piped away to preserve readability. +Automated testing is handled by the pytest-based test harness: run `airstack test -m ` (e.g. `airstack test -m unit -v`) in a containerized runner. See the [Testing docs](../../intermediate/testing/index.md) and [`tests/README.md`](../../../../tests/README.md) for the full mark reference and options. ## Docker Compose Variable Overrides diff --git a/docs/development/beginner/airstack-cli/index.md b/docs/development/beginner/airstack-cli/index.md index 90874d2d2..6c6aaa42a 100644 --- a/docs/development/beginner/airstack-cli/index.md +++ b/docs/development/beginner/airstack-cli/index.md @@ -125,8 +125,9 @@ One command group — run `airstack help osmo` for the full reference. | `--dry-run` | Validate the resolved configuration and preflight checks, then exit without starting services | | `--stack NAME[:ENTRY]` | Launch a stack folder: `stacks/NAME/launch/ENTRY.launch.xml` (default entry `stack`). Stacks are the only launch dispatch; no `--stack` launches the trunk reference stack `full_default`. See [Stacks](../../stacks.md) | | `--fleet NAME` | Launch a fleet (`config/fleets/NAME.yaml`): exports `FLEET_CONFIG_FILE`, derives `NUM_ROBOTS`, selects the Isaac fleet spawner, and (for heterogeneous fleets) includes the generated per-robot services. See [Fleets](../../fleets.md) | +| `--scene NAME` | Select the simulation scene by shortname from the shared scene catalog (`simulation/scenes.yaml`): maps `NAME` to the active simulator's scene reference and exports it. See [Scenes](../../../simulation/scenes.md) | -Also supported: `--build` (build images before starting), `--recreate` (recreate containers), and `--env-file FILE` (layer an extra env file on top of the root `.env`). +Also supported: `--build` (build images before starting) and `--env-file FILE` (layer an extra env file on top of the root `.env`). ## Command Details diff --git a/docs/development/beginner/key_concepts.md b/docs/development/beginner/key_concepts.md index f3b037f88..06c419d3e 100644 --- a/docs/development/beginner/key_concepts.md +++ b/docs/development/beginner/key_concepts.md @@ -175,20 +175,18 @@ ISAAC_SIM_SCRIPT_NAME=example_one_px4_pegasus_launch_script.py **Runtime overrides**: ```bash -# Don't auto-launch (useful for development) -airstack up --no-autolaunch +# Pick the simulator and robot count without editing .env +airstack up --sim isaac --robots 3 # Different scene (launch scripts live in simulation/isaac-sim/launch_scripts/) ISAAC_SIM_SCRIPT_NAME=my_scene_script.py airstack up -# Pick the simulator and robot count without editing .env -airstack up --sim airsim -airstack up --sim isaac --robots 3 - # Custom env file with overrides airstack up --env-file overrides/custom.env ``` +Launch-intent flags like `--sim` and `--robots` derive the right env vars for you — the full flags table is in the [CLI reference](airstack-cli/index.md#airstack-up-flags). + You can layer multiple env files to compose configurations. **NOTE**: Unlike docker compose, the airstack cli always uses the root `.env` file as the base, and then applies any additional env files on top of it. This ensures that essential defaults are always present, while still allowing for flexible overrides. ## What Makes AirStack Different diff --git a/docs/development/beginner/vscode/vscode_debug.md b/docs/development/beginner/vscode/vscode_debug.md index 79c030e02..6debbf6dd 100644 --- a/docs/development/beginner/vscode/vscode_debug.md +++ b/docs/development/beginner/vscode/vscode_debug.md @@ -4,17 +4,17 @@ This guide explains how to set up Visual Studio Code (VSCode) to work with AirSt Integration with the Docker containers works by using the [Dev Containers extension](https://code.visualstudio.com/docs/remote/containers). This allows you to open a folder within a container, and use VSCode's features (like the debugger) as if you were working on your local machine. -The two available containers for breakpoint debugging are `robot` and `gcs`. +The available containers for breakpoint debugging are `robot`, `gcs`, and `isaac-sim`, each with its own configuration under `.devcontainer/robot/`, `.devcontainer/gcs/`, and `.devcontainer/isaac-sim/`. ## Open Container 1. Open AirStack folder in VSCode on your host machine. 2. If it's your first time, VSCode will prompt with a popup to install recommended extensions. Click "Install All". Or, you can manually install the ["Dev Containers" extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). -3. Now open the Command Palette (F1) and type "Dev Containers: Reopen in Container". Select "Robot Container" or "GCS Container" depending on which you want to work with. +3. Now open the Command Palette (F1) and type "Dev Containers: Reopen in Container". Select "Robot Container", "GCS Container", or "Isaac Sim Container" depending on which you want to work with. This will build the container (if it hasn't been built before) and open the AirStack folder within the container. This may take a few minutes. - If you want to customize the container (for example, to add additional tools), you can edit `.devcontainer/devcontainer.json` and `.devcontainer/Dockerfile`. + If you want to customize a container (for example, to add additional tools), you can edit its `devcontainer.json` under the matching `.devcontainer//` directory (e.g. `.devcontainer/robot/devcontainer.json`). You can also open a terminal in the container by hitting `Ctrl-`` (backtick). diff --git a/docs/development/creating_a_stack.md b/docs/development/creating_a_stack.md new file mode 100644 index 000000000..dd8a5ee0a --- /dev/null +++ b/docs/development/creating_a_stack.md @@ -0,0 +1,79 @@ +# Creating a Custom Stack Topology + +Make a custom stack when you need a topology no reference stack provides: a different module mix, an experiment variant, or an onboard/offboard split. This is the how-to; the design rationale, folder anatomy, and single-locus rule are in [AirStack Stacks](stacks.md) — and if this is your first contact with stacks, walk the tutorial rendition first: [Modular AirStack Walkthrough §5](../getting_started/modular_airstack.md#5-make-your-own-stack). + +## Step 1 — Copy a reference stack + +Never build a stack folder by hand — copy the closest reference: + +```bash +airstack stack list # see what exists (entry points, wiring.md, compat) +airstack stack new full_default my_experiment # airstack stack new +``` + +`full_default` = everything onboard (the baseline); `lite_default` = no global/logging; `lite_offload_global` = the reference split. Names must be lowercase snake_case; `stack new` refuses to overwrite and deliberately does **not** copy `wiring.md` — that file is the *source* stack's observed graph and would lie about yours. + +**Verify:** `stacks/my_experiment/` exists with `launch/`, `modules.repos`, `docker-compose.yaml`, `README.md` — and no `wiring.md`. + +## Step 2 — Edit the entry launch file + +`stacks/my_experiment/launch/stack.launch.xml` is **the** wiring document: a flat list of module ``s. All cross-module remaps and topic-arg overrides live here and nowhere else (the single-locus rule, lint-enforced by `tests/meta/test_launch_single_locus.py`). Pass only deviations from canonical defaults — a real example from `stacks/full_macvo/launch/stack.launch.xml`, where DROAN's disparity source is rewired from stereo to MAC-VO: + +```xml + + + +``` + +Rules the unit lints enforce: every declared `` needs a `description=`; never include `robot.launch.xml` (it is the dispatcher that includes *you* — infinite recursion); remaps are legal only under `stacks/*/launch/`. Update `README.md` too — the layout contract rejects READMEs under 200 characters. Canonical topic names/types are in the [Interface Conventions Spec](../robot/autonomy/interface_conventions.md); authoring details in the [create-stack skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-stack/SKILL.md). + +**Verify:** `airstack test -m unit -v` passes the layout contract and single-locus lint. + +## Step 3 — Declare module pins if the stack needs external modules + +If your topology includes packages that don't live in trunk, pin them in the stack's `modules.repos` — vcstool format, tags/SHAs only (never branches), plus a top-level `airstack_compat:` key declaring the trunk semver range you tested against (see `stacks/full_default/modules.repos` for the annotated shape; its `repositories: {}` shows a trunk-only stack). `airstack module add --version ` pins and syncs a module at the checkout level; a stack commits its own pins. If the capability doesn't exist yet, scaffold it as a module rather than growing trunk — `airstack module create --in-tree ` scaffolds the boundary at `robot/ros_ws/src/modules//`; see [AirStack Modules](modules.md) and the [create-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md). + +**Verify:** `airstack module doctor` reports valid manifests and overlay integrity. + +## Step 4 — Launch it + +```bash +airstack up --stack my_experiment --sim isaac --robots 1 +airstack ready +``` + +`--stack ` exports `AIRSTACK_STACK_DIR`/`AIRSTACK_STACK_ENTRY`; the `--stack :` form selects an alternate entry file (split stacks). Stack launch files are bind-mounted into the containers — edit and re-launch, no `colcon build` needed. + +**Verify:** `airstack ready` reports flight-ready, and `docker exec airstack-robot-desktop-1 bash -c "ros2 node list"` shows your topology's nodes. + +## Step 5 — Bootstrap and commit wiring.md + +`wiring.md` is generated from the *running* graph — never hand-edited: + +```bash +airstack test -m wiring --stack my_experiment --sim isaacsim --num-robots 1 +``` + +With no committed `wiring.md`, the test PASSES and logs an INSTRUCTION pointing at the observed snapshot (`tests/results//wiring/observed_my_experiment.md`). Validate it, copy it to `stacks/my_experiment/wiring.md`, commit. From then on CI drift-checks the running graph against it, so any PR that changes wiring must regenerate the file. Needs a GPU + sim license; hardware-only stacks use `airstack doctor --snapshot --stack my_experiment` instead, which writes `wiring.md` with an `unverified-in-CI` provenance line. Details: [wiring.md generation and drift-checking](stacks.md#wiringmd-generation-and-drift-checking). + +**Verify:** re-running the wiring test against the committed file passes with no drift. + +## Step 6 — Validate + +```bash +airstack doctor # anatomy, module manifests, overlay, both hard gates +airstack doctor --live --stack my_experiment # RUNNING graph vs wiring.md — exit 1 on drift +airstack test -m unit -v # layout contract + single-locus lint + bridge contract +airstack stack diff full_default my_experiment # topology delta vs the stack you copied +``` + +`stack diff` compares the two stacks' generated wiring graphs (nodes/edges/topics/QoS), never XML formatting noise — a quick sanity check that your intended change is the *only* change. + +**Verify:** doctor exits 0, `--live` prints "graph matches wiring.md", and the diff shows exactly your intended delta. + +## Step 7 — Split topologies (onboard/offboard) + +A split is a stack *shape*: one entry file per host role plus a `bridge.yaml` listing every topic/service/action that crosses the machine boundary. Don't design one from scratch — copy the reference (`airstack stack new lite_offload_global my_split`), edit `bridge.yaml` first, regenerate the router config with `python3 tools/gen_dds_router.py stacks/my_split/bridge.yaml`, and run each half with `--stack my_split:onboard` / `:offboard`. Mind the hard gate: `control_setpoint` and the `trajectory_controller/*` group must never appear in a `bridge.yaml` — the controller and safety executive are onboard-only. The full recipe, including the bridge entry shape and fleet-driven placement, is in [Split stacks and bridge.yaml](stacks.md#split-stacks-and-bridgeyaml) and the [create-stack skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-stack/SKILL.md). + +**Verify:** `airstack doctor` passes the bridge hard gate, and the layout contract accepts the multi-entry folder (≥2 entry points ⇒ `bridge.yaml` present). diff --git a/docs/development/development_environment.md b/docs/development/development_environment.md deleted file mode 100644 index b02c3b62b..000000000 --- a/docs/development/development_environment.md +++ /dev/null @@ -1,94 +0,0 @@ -# Development Environment Setup - -Configure your development environment for optimal AirStack development workflow. This guide covers IDE setup, debugging tools, and recommended configurations. - -## Prerequisites - -- Completed [Getting Started](../getting_started/index.md) -- Docker and Docker Compose installed -- Basic familiarity with ROS 2 and Linux - -## Recommended Setup - -### Host Machine - -**Operating System**: Ubuntu 22.04/24.04 LTS (recommended) - -**Hardware**: - -- **CPU**: 8+ cores recommended -- **RAM**: 16GB minimum, 32GB+ recommended -- **GPU**: NVIDIA RTX 3070 or better for Isaac Sim -- **Storage**: SSD with 100GB+ free space - -**Software**: - -- Docker with NVIDIA Container Toolkit -- Git with SSH keys configured -- VSCode or your preferred IDE - -### Docker-Based Development - -All AirStack development happens inside Docker containers. This ensures: - -- **Consistent environment** across developers -- **Isolated dependencies** don't affect host system -- **Easy cleanup** without residual files -- **Reproducible builds** and testing - -## IDE Configuration - -### VSCode (Recommended) - -VSCode provides excellent ROS 2 and C++ support. - -**Recommended Extensions**: - -The recommended extensions can be installed automatically in VSCode. When you open the AirStack folder in VSCode, it should prompt you to install the recommended extensions from `.vscode/extensions.json`. Click "Install All" to get the necessary tools for development. - -- ROS extension -- C/C++ extension (Microsoft) -- Python extension -- Docker extension -- CMake Tools -- YAML extension - -## Development Workflow - -### Container-Based Workflow - -1. **Start containers without autolaunch**: - ```bash - airstack up robot-desktop --no-autolaunch - ``` - -2. **Build your changes**: - ```bash - docker exec airstack-robot-desktop-1 bash -c "bws --packages-select my_package" - ``` - -3. **Source workspace**: - ```bash - docker exec airstack-robot-desktop-1 bash -c "sws" - ``` - -4. **Run/test**: - ```bash - docker exec airstack-robot-desktop-1 bash -c "sws && ros2 launch my_package my_launch.xml" - ``` - -### Interactive Development - -For frequent rebuilds during development: - -```bash -# Connect to container interactively (careful with prompts!) -airstack connect airstack-robot-desktop-1 - -# Inside container -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 340a7f3e5..5123ee3e7 100644 --- a/docs/development/fleets.md +++ b/docs/development/fleets.md @@ -28,7 +28,7 @@ platform class → vehicle type → vehicle unit → robot instance - **Vehicle type** — data: `config/vehicles//vehicle.yaml` (URDF, sensor suite with each sensor's real driver + sim representation declared together, sim asset). See - [config/vehicles/README.md](https://github.com/castacks/AirStack/tree/develop/config/vehicles). + [config/vehicles/README.md](../../config/vehicles/README.md). - **Vehicle unit** — one serial number's calibration, in the gitignored `config/local/calibration//` overlay; a fleet entry binds it with `unit:` (exported as `CALIBRATION_DIR`). diff --git a/docs/development/index.md b/docs/development/index.md index d7705f5ae..45c2bd230 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -1,92 +1,31 @@ -# Developer Guide +# How-to Guides -Welcome to AirStack development! This guide will help you extend and customize the autonomy stack for your needs. AirStack is designed with modularity in mind, making it straightforward to swap components and add new capabilities. +Task-oriented recipes for working on AirStack. Each guide assumes you've completed [Get AirStack Flying](../getting_started/index.md) and know the [Key Concepts](beginner/key_concepts.md); it gets you through one job without re-teaching the system. -!!! tip "New to AirStack?" - Start with the **Beginner Tutorials** to understand core concepts, then progress through intermediate and advanced topics as needed. +## Guide groups -## Learning Path +| Group | Covers | +|---|---| +| [Development Environment](beginner/development_environment.md) | Local setup, [VSCode & debugging](beginner/vscode/vscode_debug.md), [forking](beginner/fork_your_own_project.md), [remote dev on OSMO](../tutorials/airstack_on_osmo.md) | +| [Docker & Builds](beginner/airstack-cli/docker_usage.md) | Pulling/building images, per-container shells, [build profiles](intermediate/docker-build-profiles.md) | +| [Simulation](../simulation/scenes.md) | Scene catalog, [spawning drones](../simulation/isaac_sim/spawning_drones.md), [overhead camera](../simulation/isaac_sim/overhead_camera.md), [Unreal export](../simulation/isaac_sim/export_stages_from_unreal.md) | +| [GCS Operation](../gcs/waypoints_and_geofences.md) | Waypoints & geofences, [Foxglove](../gcs/foxglove.md) | +| [Robot & Field](../real_world/index.md) | Hardware install, HITL, [logging](../robot/logging/index.md), data offloading | +| [Modules & Stacks](../robot/autonomy/integration_checklist.md) | Integrating a module into a stack, [module CI](module_ci.md) | +| [Testing](intermediate/testing/index.md) | [Unit tests](intermediate/testing/unit_testing.md), [end-to-end benchmarks](intermediate/testing/end_to_end_testing.md) | +| [Contributing](intermediate/contributing.md) | Branching & releases, [docs](intermediate/documentation.md), [feature notebook](intermediate/feature_notebook.md), [extending the CLI](advanced/airstack-cli/extending.md) | -### 🎯 Beginner Tutorials +## Quick command reference -**Start here** if you're new to AirStack development: - -1. **[Key Concepts](beginner/key_concepts.md)** - Understand the AirStack workflow and architecture -2. **[Development Environment](beginner/development_environment.md)** - Set up your IDE and tools -3. **[Fork Your Own Project](beginner/fork_your_own_project.md)** - Create your own AirStack-based project -4. **[VSCode Debugging](beginner/vscode/vscode_debug.md)** - Debug ROS 2 nodes in containers - -**Reference Documentation:** - -- [AirStack CLI Introduction](beginner/airstack-cli/index.md) - Essential CLI commands -- [Docker Workflow](beginner/airstack-cli/docker_usage.md) - Container management details - -### 📈 Intermediate Tutorials - -**Build on the basics** with testing and best practices: - -- **[Testing Guide](intermediate/testing/index.md)** - Test your modules effectively - - [Unit Testing](intermediate/testing/unit_testing.md) - - [Integration Testing](intermediate/testing/index.md) - - [System Testing](../../tests/README.md) - - [End-to-End Testing](intermediate/testing/end_to_end_testing.md) - - [CI/CD](intermediate/testing/ci_cd.md) -- **[Frame Conventions](intermediate/frame_conventions.md)** - Coordinate frame standards -- **[Docker Build Profiles](intermediate/docker-build-profiles.md)** - Robot image build args and platform profiles (`robot-desktop`, `robot-l4t`, etc.) -- **[Contributing](intermediate/contributing.md)** - Contribute to AirStack -- **[Documentation Guide](intermediate/documentation.md)** - Write great documentation -- **[Feature Notebook](intermediate/feature_notebook.md)** - Record design specs and test results per feature; populate PRs from them - -### 🚀 Advanced Tutorials - -**Deep dives** into advanced topics: - -- **[AI Agent Guide](advanced/ai_agent_guide.md)** - Automate development with AI -- **[AirStack Modules](modules.md)** - Package capabilities as pinned external module repos -- **[AirStack Stacks](stacks.md)** - Self-contained topology folders with observed wiring -- **[AirStack Fleets](fleets.md)** - Declare whole deployments (robots, vehicles, stacks) in one file -- **[Extending the CLI](advanced/airstack-cli/extending.md)** - Add custom CLI commands -- **[CLI Architecture](advanced/airstack-cli/architecture.md)** - Understand CLI internals - -## Quick Reference - -### Common Tasks - -| Task | Guide | -|------|-------| -| Add a new planner | [AirStack Modules](modules.md) + [create-module skill](../../.agents/skills/create-module/SKILL.md) | -| Add a world model | [AirStack Modules](modules.md) + [create-module skill](../../.agents/skills/create-module/SKILL.md) | -| Create simulation scene | [Isaac Sim Setup](../simulation/isaac_sim/pegasus_scene_setup.md) | -| Debug a module | [VSCode Debugging](beginner/vscode/vscode_debug.md) | -| Build robot images for a platform | [Docker Build Profiles](intermediate/docker-build-profiles.md) | -| Write tests | [Testing Guide](intermediate/testing/index.md) | - -### Essential Commands +The full command and flag tables live in the [CLI Reference](beginner/airstack-cli/index.md). The ones you'll use constantly: ```bash -# Start development environment -airstack up robot-desktop - -# Build and test your code -docker exec airstack-robot-desktop-1 bash -c "bws --packages-select my_package" - -# Connect to container -airstack connect robot +airstack up --sim isaac --robots 1 # launch sim + robot + GCS +airstack status # container status +airstack connect robot-desktop # tmux into a container +airstack test -m unit -v # run unit tests +airstack docs # serve these docs locally +airstack down # stop everything ``` -See [CLI Introduction](beginner/airstack-cli/index.md) for complete command reference. - -## Architecture Resources - -Before diving into development, understand the system: - -- **[System Architecture](../robot/autonomy/system_architecture.md)** - How components interact -- **[Autonomy Modules](../robot/autonomy/index.md)** - Layer-by-layer breakdown -- **[Integration Checklist](../robot/autonomy/integration_checklist.md)** - Module integration guide - -## Getting Help - -- **Documentation**: Search this site for guides and references -- **GitHub Issues**: Report bugs and request features -- **AirStack Slack**: Join `#airstack` for discussions (AirLab members) - +Looking for *why* things are built this way? See **Concepts**: [Key Concepts](beginner/key_concepts.md), [System Architecture](../robot/autonomy/system_architecture.md), and the [Modular AirStack design pages](modules.md). diff --git a/docs/development/intermediate/contributing.md b/docs/development/intermediate/contributing.md index 1c0716acd..e6b26fd49 100644 --- a/docs/development/intermediate/contributing.md +++ b/docs/development/intermediate/contributing.md @@ -13,16 +13,17 @@ If you need to add a dependency that's not in the docker image, please add a sec Please make sure to document your work. Docs are under `AirStack/docs/`. The navigation tree is under `AirStack/mkdocs.yml`. +The site nav is organized by the [Diátaxis](https://diataxis.fr) quadrants — **Tutorials**, **How-to Guides**, **Reference**, and **Concepts**. Every new page is exactly one of the four: pick the quadrant first with the decision tree in the [Documentation Guide](documentation.md#2-system-documentation), then nav-link the page under the matching tab. Module `README.md` files are exempt (they intentionally combine all four for one package). + This documentation is built with Material MKDocs. Visit [mkdocs.org](https://www.mkdocs.org) and [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) to learn how to use it. ### Commands ``` -pip install mkdocs-material -mkdocs serve +airstack docs ``` -Launches docs on https://localhost:8000. +Launches docs on https://localhost:8000 (containerized — no local Python needed; changes auto-reload). - `mkdocs -h` - Print help message and exit. diff --git a/docs/development/intermediate/documentation.md b/docs/development/intermediate/documentation.md index 7b31852b8..fecc4da50 100644 --- a/docs/development/intermediate/documentation.md +++ b/docs/development/intermediate/documentation.md @@ -68,7 +68,7 @@ graph LR - `/robot1/obstacle_map` (sensor_msgs/PointCloud2) - Obstacles ### Published -- `/robot1/trajectory` (airstack_msgs/TrajectorySegment) - Planned trajectory +- `/robot1/trajectory` (airstack_msgs/TrajectoryXYZVYaw) - Planned trajectory ## Parameters @@ -100,13 +100,21 @@ colcon test --packages-select my_planner ### 2. System Documentation -High-level documentation under `docs/` directory. +High-level documentation under `docs/` directory. The site is organized by the [Diátaxis](https://diataxis.fr) framework — every page is exactly one of four kinds, and the nav tabs mirror them: -**Categories**: +- **Tutorials**: learning-oriented lessons — a guided path to a working result (e.g. `docs/getting_started/index.md`) +- **How-to Guides**: task recipes for a competent user, goal in the title (e.g. `docs/gcs/waypoints_and_geofences.md`) +- **Reference**: austere, complete look-up material — tables over prose (e.g. `docs/robot/autonomy/interface_conventions.md`) +- **Concepts** (explanation): why the system is shaped this way — no steps (e.g. `docs/robot/autonomy/system_architecture.md`) -- **Tutorials**: Step-by-step workflows (`docs/tutorials/`) -- **Guides**: In-depth topic coverage (`docs/robot/autonomy/`) -- **Reference**: Technical specifications (`docs/robot/autonomy/integration_checklist.md`) +**Which kind is my new doc?** Walk this decision tree: + +1. Is the reader *learning by doing*, following you step-by-step to a guaranteed result, with no decisions to make? → **Tutorial**. One golden path, numbered steps, ends with something working. Repeating reference/how-to content is fine here. +2. Is the reader *accomplishing a task* they already understand, possibly with their own context (different robot, different sim)? → **How-to Guide**. Assume competence; link concepts, don't teach them. +3. Is the reader *looking something up* — a flag, a topic, a schema, a default? → **Reference**. One canonical home per fact — link to it from everywhere else, never copy it. +4. Is the reader trying to *understand why*? → **Concepts**. No steps, no option tables. Written standalone — no RFC/PR references (that context belongs in Release Notes). + +If your draft does two of these, it's two pages. The most common failure: a how-to that stops to explain design rationale (move it to a Concepts page and link), or a concept page that accumulates setup steps (move them to a how-to). Package READMEs are exempt — they intentionally combine all four for one package (use the template below). **Structure**: ```markdown diff --git a/docs/development/intermediate/frame_conventions.md b/docs/development/intermediate/frame_conventions.md index 8b038a244..f81089604 100644 --- a/docs/development/intermediate/frame_conventions.md +++ b/docs/development/intermediate/frame_conventions.md @@ -1,7 +1,10 @@ +# Frame Conventions -AirStack uses the East-North-Up (ENU) coordinate system. The robot's `map` frame is expected to be in ENU. However, the `world` frame is free to be in any coordinate system, as long as a suitable transform is provided. +AirStack uses the East-North-Up (ENU) coordinate convention for everything ROS-side: the robot's `map` frame is ENU with units in meters, angles in radians, right-handed, yaw about +Z. -Isaac Sim follows the Forward-Left-Up (FLU) coordinate system. This means that the X-axis points forward, the Y-axis points left, and the Z-axis points up. More info on Isaac Sim frames can be found [here](https://docs.omniverse.nvidia.com/isaacsim/latest/reference_conventions.html). +The launch preamble ([`autonomy_bringup/launch/robot.launch.xml`](https://github.com/castacks/AirStack/blob/develop/robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml)) publishes a static identity transform `world → map`, so `world` is a fixed origin and `map` is the planning/state frame in which `odometry.pose`, `global_plan`, and the map live. The full frame table (including `base_link` and units) is specified in the [Interface Conventions Specification](../../robot/autonomy/interface_conventions.md#tf-frames-and-units) — that spec is canonical. -Read [Scene Setup](../../simulation/isaac_sim/scene_setup.md#frame-conventions) for more information on how Isaac Sim's FLU is converted to ENU for AirStack. +Two boundaries to watch: +- **PX4 / MAVROS**: PX4 works in NED; MAVROS performs the NED↔ENU conversion. Everything above MAVROS is ENU. Mixing these up is the classic silent failure — see the warning in the spec. +- **Isaac Sim**: Isaac Sim follows the Forward-Left-Up (FLU) convention ([Isaac Sim reference](https://docs.isaacsim.omniverse.nvidia.com/latest/reference_material/reference_conventions.html)). The Pegasus-based launch scripts handle the sim-side conversion when spawning drones — see [Spawning Drones](../../simulation/isaac_sim/spawning_drones.md). diff --git a/docs/development/intermediate/testing/ci_cd.md b/docs/development/intermediate/testing/ci_cd.md index 49fe3be9c..f67dcd243 100644 --- a/docs/development/intermediate/testing/ci_cd.md +++ b/docs/development/intermediate/testing/ci_cd.md @@ -10,9 +10,13 @@ service watches the GitHub Actions queue and submits one **ephemeral registers as a single-use GitHub Actions runner, executes exactly one job, and is destroyed. -This page documents the whole system: the architecture, the job lifecycle, -what each test suite actually catches, how to trigger and read a run, and how -to fit CI into your day-to-day development loop. +This page explains how the system works — the architecture, the job +lifecycle, the anatomy of an ephemeral runner pod, the cache strategy, and the +security model. It is aimed at maintainers of the pipeline itself. + +!!! tip "Just want to run CI on your PR?" + See [Using CI](using_ci.md) — triggering runs with `/pytest`, choosing + marks, and reading the results. !!! note "Related pages" - [`tests/README.md`](../../../../tests/README.md) — the test suite reference: marks, fixtures, metrics, CLI flags. @@ -156,9 +160,9 @@ flowchart TB | Piece | File | What it contributes | |---|---|---| -| Image | [`runner.Dockerfile`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/runner.Dockerfile) | Ubuntu 24.04 + Docker CE + compose/buildx + NVIDIA container toolkit + pinned `actions/runner` (2.334.0) | -| Entrypoint | [`runner-entrypoint.sh`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/runner-entrypoint.sh) | Starts `dockerd`, waits up to 60 s for it, runs `nvidia-smi` as a non-fatal GPU sanity check, then `exec`s `run.sh --jitconfig` | -| Pod shape | [`runner-workflow.yaml.j2`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/runner-workflow.yaml.j2) | Resource request, `privileged: true`, the JIT config and `RUNNER_ALLOW_RUNASROOT` env | +| Image | [`runner.Dockerfile`](../../../../.github/orchestrator/runner.Dockerfile) | Ubuntu 24.04 + Docker CE + compose/buildx + NVIDIA container toolkit + pinned `actions/runner` (2.334.0) | +| Entrypoint | [`runner-entrypoint.sh`](../../../../.github/orchestrator/runner-entrypoint.sh) | Starts `dockerd`, waits up to 60 s for it, runs `nvidia-smi` as a non-fatal GPU sanity check, then `exec`s `run.sh --jitconfig` | +| Pod shape | [`runner-workflow.yaml.j2`](../../../../.github/orchestrator/runner-workflow.yaml.j2) | Resource request, `privileged: true`, the JIT config and `RUNNER_ALLOW_RUNASROOT` env | | Sizing | `config.yaml` | `cpu: 8`, `gpu: 1`, `memory: 32Gi`, `storage: 300Gi` — sized for sim + robot + GCS images plus Isaac assets | !!! warning "Privileged is mandatory" @@ -168,56 +172,19 @@ flowchart TB `osmo workflow logs` shows `dockerd did not become ready`. Build and publish the image with -[`build-and-push.sh`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/build-and-push.sh), +[`build-and-push.sh`](../../../../.github/orchestrator/build-and-push.sh), or — if you have no local Docker — submit -[`build-runner-on-osmo.yaml`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/build-runner-on-osmo.yaml), +[`build-runner-on-osmo.yaml`](../../../../.github/orchestrator/build-runner-on-osmo.yaml), a one-shot OSMO job that builds the runner image inside an OSMO pod and pushes it to Harbor. --- -## Triggering a run - -### The three entry points - -| Trigger | When it fires | What it runs | -|---|---|---| -| `unit-tests.yml` pull request | PR to `main`/`develop` opened, synchronized, or reopened (including forks) | `pytest tests/ -m unit` on `ubuntu-latest` | -| `system-tests.yml` pull request | PR opened, synchronized, or reopened, same-repo branches only | `-m build_packages` on an OSMO worker | -| `/pytest` PR comment | Any time, from a user with `OWNER`/`MEMBER`/`COLLABORATOR` association | Whatever args you put on the first line of the comment | -| `workflow_dispatch` | Manual, from the Actions tab | The form inputs: `marks`, `sim`, `num_robots`, `stress_iterations`, `stable_duration`, `baseline_run_id` | - -PR pushes re-run the fast unit gate and the pull-only `build_packages` gate. -GPU-intensive simulations do **not** run automatically; select the campaign -whose policy or integration changed with `/pytest`. - -### Comment syntax +## Inside a system-tests job -The first line is parsed with `shlex`; everything after it is free-form notes. - -```text -/pytest -m liveliness --sim msairsim --num-robots 1 --stress-iterations 1 - -Checking whether the DDS bridge fix holds under 3 robots — see thread above. -``` - -`-m build_packages` is **pull-only**: it retags floating `cache_*` images onto the PR `VERSION` tag and never runs `images build` (and does not pull Isaac Sim). Use that when iterating on colcon/pytest failures. For other marks, add `--no-image-build` to skip the bake: - -```text -/pytest -m build_packages -/pytest -m liveliness --sim msairsim --no-image-build -``` - -The workflow replies on the thread with the exact `pytest` command it resolved -and a link to the run, and opens a **Check Run** pinned to the PR head SHA so -comment-triggered runs still show up in the PR's Checks tab. - -!!! tip "`build_packages` is prepended for you" - Whenever you pass `-m`, the workflow rewrites the expression to - `build_packages or `. Launch tests are useless against a stale - `install/` tree, and this removes the most common way to waste a 40-minute - GPU run. It is skipped when you already named `build_packages`, and when you - pass no marks at all (pytest then runs everything anyway). +How to trigger a run — the PR gates, `/pytest` comment syntax, and +`workflow_dispatch` — is covered in [Using CI](using_ci.md). This section +follows what `system-tests.yml` does once a run is requested. ### What the job does, step by step @@ -290,10 +257,10 @@ an experimental cache line separate. docs-only changes. On `main`/`develop`, that would otherwise mean a full multi-hour rebuild of every image for a no-op Docker change. -[`docker-build.yml`](https://github.com/castacks/AirStack/blob/main/.github/workflows/docker-build.yml) +[`docker-build.yml`](../../../../.github/workflows/docker-build.yml) therefore plans per service before building: -1. [`.github/workflows/scripts/docker_image_plan.py`](https://github.com/castacks/AirStack/blob/main/.github/workflows/scripts/docker_image_plan.py) +1. [`.github/workflows/scripts/docker_image_plan.py`](../../../../.github/workflows/scripts/docker_image_plan.py) hashes each service’s Dockerfile, compose-related files, build args, and tracked fingerprint roots into `org.airstack.content-fingerprint`. 2. It inspects the **previous** versioned image’s label (from `HEAD~1`’s @@ -314,168 +281,10 @@ Manual dispatch accepts `force_rebuild=true` to rebuild and relabel everything --- -## What the pipeline tests, and what that catches - -Tests are selected with pytest marks. Collection order is fixed in -`tests/conftest.py` so cheap and prerequisite suites always run first — a -`colcon` break fails in minutes instead of after a sim bring-up. - -```mermaid -flowchart LR - u["unit
seconds, no Docker"] --> bd["build_docker
image builds"] - bd --> bp["build_packages
colcon build in containers"] - bp --> lv["liveliness
stack comes up"] - lv --> sn["sensors
streams flow at rate"] - sn --> th["takeoff_hover_land
flight chain"] - th --> au["autonomy
trajectory tracking"] -``` - -| Mark | Module | What it verifies | Bugs it is good at catching | -|---|---|---|---| -| `unit` | `/test/` (co-located) | Hermetic Python/numpy logic co-located with each ROS 2 package | Off-by-one and boundary errors in filters, converters, validators; regressions in pure algorithm code | -| `build_docker` | `system/test_build_docker.py` | Every image builds; records image sizes | Broken Dockerfiles, deleted apt packages, upstream base-image drift, accidental image bloat | -| `build_packages` | `system/test_build_packages.py` | `colcon build` inside robot, GCS, and ms-airsim workspaces | Missing `package.xml` dependencies, uninstalled launch/config files, C++ breakage on a clean tree | -| `liveliness` | `system/test_liveliness.py` | Containers reach Running, `/clock` publishes, tmux panes alive, sentinel ROS 2 nodes present, compute snapshot, stability poll | Launch files that crash on start, nodes that die after 30 s, `ROBOT_NAME`/domain-ID misconfiguration, runaway CPU or memory | -| `sensors` | `system/test_sensors.py` | Stereo and depth publish rates on both sim and robot side, filtered LiDAR liveness plus geometry sanity, sim real-time factor, time-series stability | Broken sim-to-ROS bridges, sensor Hz that silently halves, RTF collapse from a heavy new node, LiDAR filter range regressions | -| `takeoff_hover_land` | `system/test_takeoff_hover_land.py` | Four-phase chain per (sim, robots, iteration, velocity): PX4 ready → takeoff to 10 m → hover → land | Controller tuning regressions, altitude overshoot, hover drift, state-estimation bias against ground truth, PX4/MAVROS handshake breakage | -| `autonomy` | `system/test_fixed_trajectory.py` | Same chain with a Circle / Figure8 / Racetrack / Line pattern in the middle; records cross-track error and path RMSE | Path-tracker regressions, trajectory-library math errors, velocity/acceleration limit violations that show up as corner-cutting | - -### The flight chain - -Both flight suites run as an ordered chain per parametrization, so the drone -always ends on the ground before the next configuration starts: - -```mermaid -flowchart LR - r["test_px4_ready
MAVROS + EKF"] --> t["test_takeoff
within 10% of 10 m"] - t --> x["test_hover or test_fixed_trajectory"] - x --> l["test_landing
final altitude < 0.5 m"] - r -. "failure" .-> s["remaining phases skipped"] - t -. "failure" .-> s - x -. "failure still lands" .-> l -``` - -A failure in the middle phase (`test_hover` or `test_fixed_trajectory`) does -**not** skip landing — a bad tracker must not leave a drone stuck in the air -blocking the rest of the sweep. A failure in `test_px4_ready` or `test_takeoff` -does skip the remaining phases for that configuration. - -### Bring-up scope, and why mark selection costs money - -`airstack_env` is **class-scoped** and parametrized over -`(sim, num_robots, iteration)`. Each test class does its own `airstack up` and -`airstack down`. Selecting two suites with `or` therefore performs **two full -stack cycles per tuple**: - -```text --m liveliness → 1 bring-up per (sim, robots, iter) --m "liveliness or sensors" → 2 bring-ups per (sim, robots, iter) ---sim msairsim → opt in; both sims doubles all of the above ---num-robots 1,3 → doubles it again -``` - -Run one mark at a time unless you genuinely need both. - ---- - -## Reading the results - -### The PR comment - -After `run-tests` finishes — pass or fail — a `report` job on `ubuntu-latest` -downloads the current artifact plus a **baseline** artifact and runs -[`parse_metrics.py`](https://github.com/castacks/AirStack/blob/main/tests/parse_metrics.py) -in diff mode only when both artifacts have the same complete simulation -campaign fingerprint (selected tests and parameters). - -| Run type | Baseline used | -|---|---| -| PR opened or `/pytest` | Latest `system-tests.yml` artifact on the PR's base branch | -| `workflow_dispatch` with `baseline_run_id` | That specific run | -| `workflow_dispatch` without it | Latest artifact on `main` | - -For a complete simulation campaign, the comment has pass rates plus a flat -**Metrics** table, a **Sim publishing rates** pivot (topic Hz aggregates from -the `sensors` mark), and a **Compute usage** pivot (CPU / memory / GPU per -container). Regressions are marked with a red circle, improvements with a -green one, and the job **fails** if any comparable metric moves more than the -20% threshold in the wrong direction. - -`run_meta.json` separates those policy results from CI failures. A collection -error, zero-test selection, internal pytest error, cancellation, or timeout is -reported as **simulation metrics are not comparable**. Pass-rate and regression -tables are suppressed in that case; the infrastructure problem cannot appear as -a false 0% policy score. A policy assertion that runs and fails remains a real -simulation result and keeps its recorded error metrics. - -### The artifact - -`test-results--`, retained 90 days: - -```text -tests/results/2026-08-06_14-30-00/ -├── summary.txt # human-readable per-chain summary — open this first -├── results.xml # JUnit XML: durations, pass/fail per test -├── run_meta.json # completion state, pytest exit, selected/executed sim counts -└── metrics.json # every recorded metric, including time series -``` - -There are no per-test log files. Live output streams to the Actions log via -pytest's `log_cli`, and failed assertions embed the tail of the relevant -`docker` or `ros2` subprocess output directly in the failure message. - -Regenerate a report locally from a downloaded artifact: - -```bash -python tests/parse_metrics.py \ - --current path/to/current-run/ \ - --baseline path/to/baseline-run/ \ - --threshold 20 -``` - ---- - -## Using CI well while developing - -The pipeline is expensive at the far end and nearly free at the near end. Push -each class of failure as far left as it will go. - -```mermaid -flowchart TD - q{"What did you change?"} - q -- "Pure Python / numpy logic" --> u["airstack test -m unit
seconds, no GPU"] - q -- "Dockerfile / dependency" --> b["airstack test -m build_docker or build_packages
minutes, no GPU"] - q -- "Launch file / new node" --> l["airstack test -m liveliness --sim msairsim --num-robots 1"] - q -- "Sensor or bridge" --> s["airstack test -m sensors --sim isaacsim --num-robots 1"] - q -- "Controller / planner" --> a["airstack test -m autonomy --sim msairsim --trajectory-types Circle"] - u --> pr["Push branch, open PR"] - b --> pr - l --> pr - s --> pr - a --> pr - pr --> fast["unit-tests.yml on ubuntu-latest"] - pr --> ci["build_packages on an ephemeral OSMO pod"] - fast --> rep["Read automatic check results"] - ci --> rep - rep --> iter["/pytest with the relevant simulation mark"] - iter --> metrics["Read like-for-like policy metrics"] -``` - -Practical rules that follow from how the system is built: - -- **Reproduce CI locally with the same command.** `airstack test` and CI both call `pytest tests/` with the same flags. If a run fails in CI, copy the resolved command from the acknowledgment comment and run it on any GPU box — including an [interactive OSMO dev pod](../../../tutorials/airstack_on_osmo.md) if you do not have a local GPU. -- **Narrow before you re-run.** A `/pytest` with no args re-runs everything. `/pytest -m autonomy --sim msairsim --trajectory-types Circle` re-runs the one chain you are fixing, in a fraction of the time. -- **Never trust a green launch test against a stale build.** This is why `build_packages` is auto-prepended; keep it that way when writing your own `/pytest` line. -- **Read `summary.txt` before the raw log.** It groups each flight chain with per-phase wall times and status, so the failing phase is obvious without scrolling a 40-minute log. -- **Treat a like-for-like metrics diff as a review artifact.** The reporter compares only identical selected simulation campaigns; a PR that turns a metric red needs an explanation even when every assertion passed. -- **Bump `VERSION` in `.env` when image content changes.** [`check-version-increment.yml`](https://github.com/castacks/AirStack/blob/main/.github/workflows/check-version-increment.yml) gates the PR on a strictly-greater semver, and merging that bump is what triggers the release build below. - ---- - ## The release path `system-tests.yml` is not the only workflow on the ephemeral runners. -[`docker-build.yml`](https://github.com/castacks/AirStack/blob/main/.github/workflows/docker-build.yml) +[`docker-build.yml`](../../../../.github/workflows/docker-build.yml) also requests `runs-on: [self-hosted, airstack-ephemeral]` and therefore gets the same per-job pod treatment. @@ -525,8 +334,8 @@ covers that digest; the job re-signs the same digest under the new tags’ refs) ## Troubleshooting A failed run can break at the orchestrator, at the OSMO pod, at the runner, or -in the tests themselves, and each layer has a different inspection path. Work -down the list. +in the tests themselves, and each layer has a different inspection path. The +entries below all require orchestrator or OSMO access; work down the list. | Symptom | Layer | First thing to check | |---|---|---| @@ -539,9 +348,11 @@ down the list. | `nvidia-smi unavailable` | Pod | GPU not requested or the toolkit is not configured on the node | | `Cannot connect to the Docker daemon` mid-test | Pod | Inner dockerd crashed — `osmo workflow exec "$WF" runner`, then read `/var/log/dockerd.log` | | `No space left on device` | Pod | Bump `storage` in `config.yaml`; Isaac assets plus all images are large | -| Runner registered, then pytest failed | Tests | A real test failure — the GitHub Actions log and `summary.txt` are canonical | -| Report says “simulation metrics are not comparable” | Collection/infrastructure | Read the run outcome and pytest exit status in `run_meta.json`; no policy regression was scored | -| Metrics report job failed with no test failures | Report | A like-for-like metric regressed past the 20% threshold, or report generation itself failed; read the report step log | + +Failures in the tests or the metrics report themselves — a runner that +registered and then failed pytest, a "simulation metrics are not comparable" +report, a report job that failed with no test failures — are covered from the +developer's side in [Using CI → Troubleshooting](using_ci.md#troubleshooting). To map a GitHub job to its pod: @@ -565,19 +376,20 @@ Full runbook, including credential rotation and worker-side diagnostics: | Path | Role | |---|---| | [`.github/workflows/unit-tests.yml`](../../../../.github/workflows/unit-tests.yml) | Fast Python unit/harness gate on GitHub-hosted runners | -| [`.github/workflows/system-tests.yml`](https://github.com/castacks/AirStack/blob/main/.github/workflows/system-tests.yml) | The test workflow: triggers, arg parsing, image prep, pytest, artifact, metrics report | -| [`.github/orchestrator/orchestrator.py`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/orchestrator.py) | The spawn and reap loops, GitHub polling, JIT minting, OSMO CLI plumbing | -| [`.github/orchestrator/runner-workflow.yaml.j2`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/runner-workflow.yaml.j2) | Per-job OSMO workflow template | -| [`.github/orchestrator/runner.Dockerfile`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/runner.Dockerfile) | Prebaked worker image | -| [`.github/orchestrator/runner-entrypoint.sh`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/runner-entrypoint.sh) | dockerd bring-up, GPU check, single-job runner | -| [`.github/orchestrator/config.example.yaml`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/config.example.yaml) | Every tunable: pool, platform, resources, limits, poll intervals | -| [`.github/orchestrator/setup.sh`](https://github.com/castacks/AirStack/blob/main/.github/orchestrator/setup.sh) | One-time orchestrator host install | -| [`tests/conftest.py`](https://github.com/castacks/AirStack/blob/main/tests/conftest.py) | `airstack_env` fixture, collection order, `MetricsRecorder` | -| [`tests/parse_metrics.py`](https://github.com/castacks/AirStack/blob/main/tests/parse_metrics.py) | Report generation and the regression gate | -| [`tests/run_summary.py`](https://github.com/castacks/AirStack/blob/main/tests/run_summary.py) | `summary.txt` generation | +| [`.github/workflows/system-tests.yml`](../../../../.github/workflows/system-tests.yml) | The test workflow: triggers, arg parsing, image prep, pytest, artifact, metrics report | +| [`.github/orchestrator/orchestrator.py`](../../../../.github/orchestrator/orchestrator.py) | The spawn and reap loops, GitHub polling, JIT minting, OSMO CLI plumbing | +| [`.github/orchestrator/runner-workflow.yaml.j2`](../../../../.github/orchestrator/runner-workflow.yaml.j2) | Per-job OSMO workflow template | +| [`.github/orchestrator/runner.Dockerfile`](../../../../.github/orchestrator/runner.Dockerfile) | Prebaked worker image | +| [`.github/orchestrator/runner-entrypoint.sh`](../../../../.github/orchestrator/runner-entrypoint.sh) | dockerd bring-up, GPU check, single-job runner | +| [`.github/orchestrator/config.example.yaml`](../../../../.github/orchestrator/config.example.yaml) | Every tunable: pool, platform, resources, limits, poll intervals | +| [`.github/orchestrator/setup.sh`](../../../../.github/orchestrator/setup.sh) | One-time orchestrator host install | +| [`tests/conftest.py`](../../../../tests/conftest.py) | `airstack_env` fixture, collection order, `MetricsRecorder` | +| [`tests/parse_metrics.py`](../../../../tests/parse_metrics.py) | Report generation and the regression gate | +| [`tests/run_summary.py`](../../../../tests/run_summary.py) | `summary.txt` generation | ## See also +- [Using CI](using_ci.md) — triggering runs, choosing marks, and reading results day-to-day. - [System Tests](../../../../tests/README.md) — marks, fixtures, metrics, and every CLI flag. - [Unit Testing](unit_testing.md) — the co-location and proxy pattern for package-level tests. - [End-to-End Testing](end_to_end_testing.md) — the fixed-trajectory benchmark in depth. diff --git a/docs/development/intermediate/testing/end_to_end_testing.md b/docs/development/intermediate/testing/end_to_end_testing.md index 4b85802f5..5b6f233e9 100644 --- a/docs/development/intermediate/testing/end_to_end_testing.md +++ b/docs/development/intermediate/testing/end_to_end_testing.md @@ -432,7 +432,6 @@ Action server: `/{robot_name}/tasks/fixed_trajectory` — see also [Tasks and Ta | `trajectory_success = 0` | Tracker stall or timeout | Check trajectory_controller logs; rebuild the workspace (`-m build_packages`) | | Cross-track error >> 5 m | Wrong tracker params or frame bug | Compare launch params; check world-frame transform | | Tests run for hours | Default `--num-robots 1,3` (and `--sim msairsim` if opted in) | Pin `--sim isaacsim --num-robots 1 --stress-iterations 1` | -| Unknown mark warning `autonomy` | Mark not in `pytest.ini` | Harmless; filter still works | --- diff --git a/docs/development/intermediate/testing/index.md b/docs/development/intermediate/testing/index.md index aa1cead9e..260997e0c 100644 --- a/docs/development/intermediate/testing/index.md +++ b/docs/development/intermediate/testing/index.md @@ -31,17 +31,12 @@ locally with no Docker or GPU needed. ## System tests (`tests/system/`) -Full Docker-stack integration tests. The canonical reference is -**[`tests/README.md`](../../../../tests/README.md)**. In short: - -| Mark | Module | Role | -|---|---|---| -| `build_docker` | `system/test_build_docker.py` | Docker image builds | -| `build_packages` | `system/test_build_packages.py` | `colcon build` inside containers | -| `liveliness` | `system/test_liveliness.py` | Containers, `/clock` readiness, tmux, sentinel ROS 2 nodes, compute, infra-only stability poll | -| `sensors` | `system/test_sensors.py` | Sim + robot stereo/depth Hz, filtered LiDAR (`echo --once` + validation script on Isaac), sim RTF, sensor stability time-series | -| `takeoff_hover_land` | `system/test_takeoff_hover_land.py` | Four-phase flight chain per configuration (takeoff → hover → land) | -| `autonomy` | `system/test_fixed_trajectory.py` | Fixed-pattern path-tracker benchmark (takeoff → trajectory → land) | +Full Docker-stack integration tests, selected by pytest mark — from cheap +build gates (`build_docker`, `build_packages`) through stack bring-up +(`liveliness`, `wiring`, `sensors`) to full flight campaigns +(`takeoff_hover_land`, `autonomy`, `waypoint_flight`). The canonical mark +reference — what each mark runs and verifies — is +**[`tests/README.md`](../../../../tests/README.md)**. Collection order is defined in `tests/conftest.py` (unit tests first, then `build_docker` → `build_packages` → `liveliness` → `sensors` → `takeoff_hover_land` @@ -86,5 +81,5 @@ airstack test -m "build_packages or autonomy" \ ## Other testing docs - [Unit Testing](unit_testing.md) — `@pytest.mark.unit`, co-located tests, CI workflow -- [Testing frameworks](testing_frameworks.md) — `colcon test`, rostest patterns +- [System test suite reference](../../../../tests/README.md) — marks, fixtures, CLI options - [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/testing_frameworks.md b/docs/development/intermediate/testing/testing_frameworks.md deleted file mode 100644 index 9b629ac7f..000000000 --- a/docs/development/intermediate/testing/testing_frameworks.md +++ /dev/null @@ -1,112 +0,0 @@ -# Testing Frameworks - -## Testing Categories -Testing is organized into four main categories: - -1. **Integration Testing (End-to-End Testing):** - Validates the entire system within its operational environment, whether in simulation or on hardware. - -2. **System Testing:** - Tests interactions between system components, such as communication between ROS nodes. - -3. **Node Testing:** - Focuses on verifying the functionality of individual nodes, from initialization to execution. - -4. **Unit Testing:** - Tests specific functions or business logic to ensure the correctness of the smallest units of code. - ---- - -## Testing Utilities -Since our autonomy system primarily relies on ROS 2, we use the `colcon test` framework to run tests. Most tests are written in Python using the `pytest` package, as demonstrated in the example below. - ---- - -## Testing Structure -All tests should be included in a ```tests/``` folder in their respective heirarchy of the architecture. For example, integration testing should on the same level as the ```robot/``` and ```simulation/``` folders, where a node test should reside in the ros package directoy. ```colcon test``` will search through the workspace to find all testing packages, provided they are specified in the Cmake.txt or setup.py files. - -## Example Testing Script - -Below is an example of a systems test that can give the general structure of a testing script. - -``` -import os -import sys -import time -import unittest -import uuid - -import launch -from launch.launch_service import LaunchService -import launch_ros -import launch_ros.actions -import launch_testing.actions -from launch_testing.io_handler import ActiveIoHandler -import launch_testing_ros - -import pytest - -import rclpy -from rclpy.node import Node - -import std_msgs.msg -from std_msgs.msg import String -import mavros_msgs.srv - -import time - -@pytest.mark.rostest -# this is the test descriptioon used to launch the full robot system with launch_robot_headless.yaml -def generate_test_description(): - robot_launch_path = 'path/to/launch/file' - - gui_arg = launch.actions.DeclareLaunchArgument('use_gui', default_value='false', description='Whether to launch the GUI') - - robot_launch = launch.actions.IncludeLaunchDescription( launch.launch_description_sources.AnyLaunchDescriptionSource(robot_launch_path), - launch_arguments={'use_gui': launch.substitutions.LaunchConfiguration('use_gui')}.items()) - return ( - launch.LaunchDescription([ - gui_arg, - robot_launch, - launch_testing.actions.ReadyToTest(), - ]), - {} - ) - -class TestRobotSystem(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Initialize the ROS context for the test node - rclpy.init() - - @classmethod - def tearDownClass(cls): - # Shutdown the ROS context - rclpy.shutdown() - - def setUp(self): - # Create a ROS node for tests - self.node = rclpy.create_node('robot_tester_node') - self.service_timeout = 2 - - def tearDown(self): - self.node.destroy_node() - - def test_set_mode(self): - client = self.node.create_client(mavros_msgs.srv.SetMode, '/mavros/set_mode') - self.node.get_logger().info("Waiting for service to be available...") - accum_time = 0 - while not client.wait_for_service(timeout_sec=1.0): - print('service not available, waiting again...') - accum_time += 1 - if accum_time > self.service_timeout: - print('service not available, aborting test...') - self.assertTrue(False) - request = mavros_msgs.srv.SetMode.Request() - request.custom_mode = "GUIDED" - print("Sending request to set mode to GUIDED") - future = client.call_async(request) - rclpy.spin_until_future_complete(self.node, future) - response = future.result() - self.assertTrue(response.mode_sent) -``` \ 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 5dcc58ee0..078c544df 100644 --- a/docs/development/intermediate/testing/unit_testing.md +++ b/docs/development/intermediate/testing/unit_testing.md @@ -211,4 +211,4 @@ or CI needed. - [`.agents/skills/add-unit-tests`](../../../../.agents/skills/add-unit-tests/SKILL.md) — step-by-step agent workflow - [System tests](../../../../tests/README.md) — full Docker-stack integration tests - [CI/CD](ci_cd.md) — pipeline overview and ephemeral runner architecture -- [Testing frameworks](testing_frameworks.md) — `colcon test`, ament linters +- [System test suite reference](../../../../tests/README.md) — marks, fixtures, CLI options diff --git a/docs/development/intermediate/testing/using_ci.md b/docs/development/intermediate/testing/using_ci.md new file mode 100644 index 000000000..4d6c54ff8 --- /dev/null +++ b/docs/development/intermediate/testing/using_ci.md @@ -0,0 +1,242 @@ +# Using CI + +How to run AirStack CI on your pull request and read what comes back: what runs +automatically, how to select simulation campaigns with `/pytest`, and where the +results land. For how the pipeline itself works — the orchestrator, ephemeral +OSMO pods, caching, and security — see [CI/CD Pipeline on OSMO](ci_cd.md). + +--- + +## Triggering a run + +### The three entry points + +| Trigger | When it fires | What it runs | +|---|---|---| +| `unit-tests.yml` pull request | PR to `main`/`develop` opened, synchronized, or reopened (including forks) | `pytest tests/ -m unit` on `ubuntu-latest` | +| `system-tests.yml` pull request | PR opened, synchronized, or reopened, same-repo branches only | `-m build_packages` on an OSMO worker | +| `/pytest` PR comment | Any time, from a user with `OWNER`/`MEMBER`/`COLLABORATOR` association | Whatever args you put on the first line of the comment | +| `workflow_dispatch` | Manual, from the Actions tab | The form inputs: `marks`, `sim`, `num_robots`, `stress_iterations`, `stable_duration`, `baseline_run_id` | + +PR pushes re-run the fast unit gate and the pull-only `build_packages` gate. +GPU-intensive simulations do **not** run automatically; select the campaign +whose policy or integration changed with `/pytest`. + +### Comment syntax + +The first line is parsed with `shlex`; everything after it is free-form notes. + +```text +/pytest -m liveliness --sim msairsim --num-robots 1 --stress-iterations 1 + +Checking whether the DDS bridge fix holds under 3 robots — see thread above. +``` + +`-m build_packages` is **pull-only**: it retags floating `cache_*` images onto the PR `VERSION` tag and never runs `images build` (and does not pull Isaac Sim). Use that when iterating on colcon/pytest failures. For other marks, add `--no-image-build` to skip the bake: + +```text +/pytest -m build_packages +/pytest -m liveliness --sim msairsim --no-image-build +``` + +The workflow replies on the thread with the exact `pytest` command it resolved +and a link to the run, and opens a **Check Run** pinned to the PR head SHA so +comment-triggered runs still show up in the PR's Checks tab. + +!!! tip "`build_packages` is prepended for you" + Whenever you pass `-m`, the workflow rewrites the expression to + `build_packages or `. Launch tests are useless against a stale + `install/` tree, and this removes the most common way to waste a 40-minute + GPU run. It is skipped when you already named `build_packages`, and when you + pass no marks at all (pytest then runs everything anyway). + +--- + +## What the pipeline tests, and what that catches + +Tests are selected with pytest marks. Collection order is fixed in +`tests/conftest.py` so cheap and prerequisite suites always run first — a +`colcon` break fails in minutes instead of after a sim bring-up. + +```mermaid +flowchart LR + u["unit
seconds, no Docker"] --> bd["build_docker
image builds"] + bd --> bp["build_packages
colcon build in containers"] + bp --> lv["liveliness
stack comes up"] + lv --> sn["sensors
streams flow at rate"] + sn --> th["takeoff_hover_land
flight chain"] + th --> au["autonomy
trajectory tracking"] +``` + +The table below focuses on what each suite *catches*; the authoritative mark +list and per-mark reference live in +[`tests/README.md`](../../../../tests/README.md). + +| Mark | Module | What it verifies | Bugs it is good at catching | +|---|---|---|---| +| `unit` | `/test/` (co-located) | Hermetic Python/numpy logic co-located with each ROS 2 package | Off-by-one and boundary errors in filters, converters, validators; regressions in pure algorithm code | +| `build_docker` | `system/test_build_docker.py` | Every image builds; records image sizes | Broken Dockerfiles, deleted apt packages, upstream base-image drift, accidental image bloat | +| `build_packages` | `system/test_build_packages.py` | `colcon build` inside robot, GCS, and ms-airsim workspaces | Missing `package.xml` dependencies, uninstalled launch/config files, C++ breakage on a clean tree | +| `liveliness` | `system/test_liveliness.py` | Containers reach Running, `/clock` publishes, tmux panes alive, sentinel ROS 2 nodes present, compute snapshot, stability poll | Launch files that crash on start, nodes that die after 30 s, `ROBOT_NAME`/domain-ID misconfiguration, runaway CPU or memory | +| `wiring` | `system/test_wiring_snapshot.py` | Observed wiring snapshot of the running ROS graph, drift-checked against the stack's committed `stacks//wiring.md` | Topic remaps that silently disconnect, nodes publishing into the void, launch-file edits that change the graph without updating `wiring.md` | +| `sensors` | `system/test_sensors.py` | Stereo and depth publish rates on both sim and robot side, filtered LiDAR liveness plus geometry sanity, sim real-time factor, time-series stability | Broken sim-to-ROS bridges, sensor Hz that silently halves, RTF collapse from a heavy new node, LiDAR filter range regressions | +| `takeoff_hover_land` | `system/test_takeoff_hover_land.py` | Four-phase chain per (sim, robots, iteration, velocity): PX4 ready → takeoff to 10 m → hover → land | Controller tuning regressions, altitude overshoot, hover drift, state-estimation bias against ground truth, PX4/MAVROS handshake breakage | +| `autonomy` | `system/test_fixed_trajectory.py` | Same chain with a Circle / Figure8 / Racetrack / Line pattern in the middle; records cross-track error and path RMSE | Path-tracker regressions, trajectory-library math errors, velocity/acceleration limit violations that show up as corner-cutting | +| `waypoint_flight` | `system/test_waypoint_flight.py` | Same chain with an ordered `NavigateTask` waypoint route in the middle; the odometry track is judged by the standalone `waypoint_checker.py` (in-order corridor arrival, final-goal tolerance, per-waypoint timeout) | Waypoint sequencing regressions, behavior-tree/navigation task breakage, routes that skip or stall at a waypoint | + +### The flight chain + +Both flight suites run as an ordered chain per parametrization, so the drone +always ends on the ground before the next configuration starts: + +```mermaid +flowchart LR + r["test_px4_ready
MAVROS + EKF"] --> t["test_takeoff
within 10% of 10 m"] + t --> x["test_hover or test_fixed_trajectory"] + x --> l["test_landing
final altitude < 0.5 m"] + r -. "failure" .-> s["remaining phases skipped"] + t -. "failure" .-> s + x -. "failure still lands" .-> l +``` + +A failure in the middle phase (`test_hover` or `test_fixed_trajectory`) does +**not** skip landing — a bad tracker must not leave a drone stuck in the air +blocking the rest of the sweep. A failure in `test_px4_ready` or `test_takeoff` +does skip the remaining phases for that configuration. + +### Bring-up scope, and why mark selection costs money + +`airstack_env` is **class-scoped** and parametrized over +`(sim, num_robots, iteration)`. Each test class does its own `airstack up` and +`airstack down`. Selecting two suites with `or` therefore performs **two full +stack cycles per tuple**: + +```text +-m liveliness → 1 bring-up per (sim, robots, iter) +-m "liveliness or sensors" → 2 bring-ups per (sim, robots, iter) +--sim msairsim → opt in; both sims doubles all of the above +--num-robots 1,3 → doubles it again +``` + +Run one mark at a time unless you genuinely need both. + +--- + +## Reading the results + +### The PR comment + +After `run-tests` finishes — pass or fail — a `report` job on `ubuntu-latest` +downloads the current artifact plus a **baseline** artifact and runs +[`parse_metrics.py`](../../../../tests/parse_metrics.py) +in diff mode only when both artifacts have the same complete simulation +campaign fingerprint (selected tests and parameters). + +| Run type | Baseline used | +|---|---| +| PR opened or `/pytest` | Latest `system-tests.yml` artifact on the PR's base branch | +| `workflow_dispatch` with `baseline_run_id` | That specific run | +| `workflow_dispatch` without it | Latest artifact on `main` | + +For a complete simulation campaign, the comment has pass rates plus a flat +**Metrics** table, a **Sim publishing rates** pivot (topic Hz aggregates from +the `sensors` mark), and a **Compute usage** pivot (CPU / memory / GPU per +container). Regressions are marked with a red circle, improvements with a +green one, and the job **fails** if any comparable metric moves more than the +20% threshold in the wrong direction. + +`run_meta.json` separates those policy results from CI failures. A collection +error, zero-test selection, internal pytest error, cancellation, or timeout is +reported as **simulation metrics are not comparable**. Pass-rate and regression +tables are suppressed in that case; the infrastructure problem cannot appear as +a false 0% policy score. A policy assertion that runs and fails remains a real +simulation result and keeps its recorded error metrics. + +### The artifact + +`test-results--`, retained 90 days: + +```text +tests/results/2026-08-06_14-30-00/ +├── summary.txt # human-readable per-chain summary — open this first +├── results.xml # JUnit XML: durations, pass/fail per test +├── run_meta.json # completion state, pytest exit, selected/executed sim counts +└── metrics.json # every recorded metric, including time series +``` + +There are no per-test log files. Live output streams to the Actions log via +pytest's `log_cli`, and failed assertions embed the tail of the relevant +`docker` or `ros2` subprocess output directly in the failure message. + +Regenerate a report locally from a downloaded artifact: + +```bash +python tests/parse_metrics.py \ + --current path/to/current-run/ \ + --baseline path/to/baseline-run/ \ + --threshold 20 +``` + +--- + +## Using CI well while developing + +The pipeline is expensive at the far end and nearly free at the near end. Push +each class of failure as far left as it will go. + +```mermaid +flowchart TD + q{"What did you change?"} + q -- "Pure Python / numpy logic" --> u["airstack test -m unit
seconds, no GPU"] + q -- "Dockerfile / dependency" --> b["airstack test -m build_docker or build_packages
minutes, no GPU"] + q -- "Launch file / new node" --> l["airstack test -m liveliness --sim msairsim --num-robots 1"] + q -- "Sensor or bridge" --> s["airstack test -m sensors --sim isaacsim --num-robots 1"] + q -- "Controller / planner" --> a["airstack test -m autonomy --sim msairsim --trajectory-types Circle"] + u --> pr["Push branch, open PR"] + b --> pr + l --> pr + s --> pr + a --> pr + pr --> fast["unit-tests.yml on ubuntu-latest"] + pr --> ci["build_packages on an ephemeral OSMO pod"] + fast --> rep["Read automatic check results"] + ci --> rep + rep --> iter["/pytest with the relevant simulation mark"] + iter --> metrics["Read like-for-like policy metrics"] +``` + +Practical rules that follow from how the system is built: + +- **Reproduce CI locally with the same command.** `airstack test` and CI both call `pytest tests/` with the same flags. If a run fails in CI, copy the resolved command from the acknowledgment comment and run it on any GPU box — including an [interactive OSMO dev pod](../../../tutorials/airstack_on_osmo.md) if you do not have a local GPU. +- **Narrow before you re-run.** A `/pytest` with no args re-runs everything. `/pytest -m autonomy --sim msairsim --trajectory-types Circle` re-runs the one chain you are fixing, in a fraction of the time. +- **Never trust a green launch test against a stale build.** This is why `build_packages` is auto-prepended; keep it that way when writing your own `/pytest` line. +- **Read `summary.txt` before the raw log.** It groups each flight chain with per-phase wall times and status, so the failing phase is obvious without scrolling a 40-minute log. +- **Treat a like-for-like metrics diff as a review artifact.** The reporter compares only identical selected simulation campaigns; a PR that turns a metric red needs an explanation even when every assertion passed. +- **Bump `VERSION` in `.env` when image content changes.** [`check-version-increment.yml`](../../../../.github/workflows/check-version-increment.yml) gates the PR on a strictly-greater semver, and merging that bump is what triggers the release build. + +--- + +## Troubleshooting + +The failures a CI *user* can act on from the GitHub side: + +| Symptom | Layer | First thing to check | +|---|---|---| +| `/pytest` comment produced no run | Trigger guard | You need `OWNER`/`MEMBER`/`COLLABORATOR` association, and the PR must come from a same-repo branch — fork PRs are blocked from the GPU runners | +| `system-tests.yml` never ran on a fork PR | Trigger guard | Expected: the `pull_request` path only runs for same-repo branches; only the `ubuntu-latest` unit gate runs on forks | +| Runner registered, then pytest failed | Tests | A real test failure — the GitHub Actions log and `summary.txt` are canonical | +| Report says “simulation metrics are not comparable” | Collection/infrastructure | Read the run outcome and pytest exit status in `run_meta.json`; no policy regression was scored | +| Metrics report job failed with no test failures | Report | A like-for-like metric regressed past the 20% threshold, or report generation itself failed; read the report step log | +| Job sits `queued` forever, no runner appears | Orchestrator / pod | Not fixable from the PR — an admin needs to inspect the orchestrator and pod; see the [pipeline troubleshooting table](ci_cd.md#troubleshooting) | + +Orchestrator-, OSMO-, and pod-level failures (auth errors, `dockerd did not +become ready`, disk exhaustion, …) require orchestrator host access — work +through [CI/CD Pipeline on OSMO → Troubleshooting](ci_cd.md#troubleshooting). + +--- + +## See also + +- [CI/CD Pipeline on OSMO](ci_cd.md) — architecture, job lifecycle, pod anatomy, cache strategy, security model. +- [System Tests](../../../../tests/README.md) — marks, fixtures, metrics, and every CLI flag. +- [CI/CD Orchestrator](../../../../tests/ci-cd-orchestrator.md) — admin setup, rotation, and break-glass procedures. diff --git a/docs/development/working_with_coding_agents.md b/docs/development/working_with_coding_agents.md new file mode 100644 index 000000000..de367786e --- /dev/null +++ b/docs/development/working_with_coding_agents.md @@ -0,0 +1,74 @@ +# Getting the Most out of Your Coding Agent + +This guide is for a human developer directing a coding agent (Claude Code, OpenHands, etc.) on AirStack. The repo does a lot of the prompting for you — this page tells you what to ask for so the built-in machinery actually gets used. + +## Why this repo is agent-ready + +Every agent session starts with [`AGENTS.md`](https://github.com/castacks/AirStack/blob/develop/AGENTS.md) in context (`CLAUDE.md` symlinks to it): architecture, topic conventions, reference implementations, pitfalls. Beyond that, [`.agents/skills/`](https://github.com/castacks/AirStack/blob/develop/.agents/README.md) holds 20+ step-verified workflow guides that agents discover by task description, and the [feature notebook convention](intermediate/feature_notebook.md) gives every feature a design spec and evidence trail that outlive the session. Agents also get a condensed cheat sheet at [AI Agent Quick Reference](advanced/ai_agent_guide.md). + +## 1. Start every feature with a notebook design spec + +Before the agent writes any code, ask it to create the notebook entry: + +> Follow the use-feature-notebook skill: write `notebook/NNN-my-feature/design_spec.md` from our discussion before implementing anything. Show me the spec first. + +`AGENTS.md` already instructs agents to do this at the start of every feature, but an explicit nudge guarantees it — and reviewing the spec before code is written is the cheapest design review you will ever do. A good spec (template in [`use-feature-notebook`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/use-feature-notebook)) contains: + +- **Problem context** — the motivation, constraints, and decisions *from your conversation*. This is the one section that preserves context existing nowhere else; check it captures what you actually discussed. +- **Proposed implementation** — affected packages, nodes, topics, data flow, with per-section status labels (`DESIGN/TODO` / `WIP` / `DONE`) the agent updates as it works, and `Date started` / `Last updated` in the header — a lab journal entry without a date is unusable later. +- **Lettered test plan** — sections `(a)`, `(b)`, `(c)`…, each stating what runs (unit test, system-test mark, sim scenario), what is measured, and what counts as pass. The letters name the results folders later. + +Why this pays off: agent sessions end, but the spec survives on disk — the next session (or the next agent, or you in three weeks) picks it up instead of re-deriving intent, and the status labels show exactly which parts are designed, in progress, or finished. Note `notebook/` is **gitignored and local-only**: nothing in it is committed or referenced from committed code; its content reaches the world only distilled into the PR body ([full convention](intermediate/feature_notebook.md)). + +## 2. Point the agent at skills, not raw prompts + +Instead of describing a workflow from scratch, name the skill — *"use the add-ros2-package skill"* beats a paragraph of instructions, because the skill encodes repo-specific steps (templates, install directives, canonical-default launch args) that a generic prompt misses. Agents match skills to tasks automatically via each skill's trigger description, but naming one removes the guesswork. High-leverage ones for common asks: + +| Ask | Skill | +|-----|-------| +| New algorithm module | [`add-ros2-package`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/add-ros2-package), then [`integrate-module-into-layer`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/integrate-module-into-layer) to wire it into a stack | +| New launch topology | [`create-stack`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/create-stack) | +| "It doesn't work" | [`debug-module`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/debug-module) | +| End-to-end verification | [`test-in-simulation`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/test-in-simulation) | +| Docs for the new thing | [`update-documentation`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/update-documentation) | + +The full catalog with one-line triggers is the table in [`.agents/README.md`](https://github.com/castacks/AirStack/blob/develop/.agents/README.md) (mirrored in `AGENTS.md`). Two other steering moves that cost you one sentence each: + +- **Name the reference implementation.** `AGENTS.md` lists one well-structured package per module type (e.g. `droan_local_planner` for local planners, `random_walk` for global planners); *"study the DROAN local planner first, then follow its structure"* anchors the agent to working code instead of invented patterns. +- **Say where the work lands.** Modules get wired in a stack's entry launch file, not in per-layer bringups (the old layer-bringup workflow is legacy) — telling the agent which stack you're targeting up front avoids a wrong-locus integration. + +## 3. Make the agent test and record + +Don't accept "it builds" as done. Ask for evidence against the spec's lettered plan: + +1. Each test run drops raw artifacts (metrics files copied from `tests/results//`, plots, sim screenshots, log *excerpts* — not full container logs) into `notebook/NNN-slug/results/-
/`, letters matching the spec, run timestamps preserved. +2. After validation, the agent writes `results/results_summary.md` — self-contained, tables and figures embedded directly (not linked), one section per letter, ending with per-section verdicts and known limitations. +3. The PR body is populated from the notebook: problem context from `design_spec.md`, validation tables from `results_summary.md`, key figures uploaded as PR attachments — the only route notebook content takes off your machine, since reviewers can't see `notebook/`. + +A useful review habit: read `results_summary.md` before the diff. If a lettered section has no artifacts folder, that part of the plan wasn't run. + +## 4. Close the loop: capture what the agent learned + +When an agent spends real time digging — a long grep-and-read session, a debugging chain that resolved on a non-obvious cause, a discovery contradicting `AGENTS.md` — tell it to run [`capture-discovered-knowledge`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/capture-discovered-knowledge) before finishing. The skill decides where the knowledge belongs (fix the wrong claim in `AGENTS.md`, extend an existing skill, or the package README for module-scoped quirks) and holds a deliberately high bar against bloat: if the code already says it, don't persist it. This is what keeps the next session from paying the same discovery cost. Keep the scopes straight: per-feature evidence stays in the notebook; only durable, repo-wide mechanisms get promoted to `AGENTS.md` or a skill. + +## 5. Practical tips (all verified in this repo) + +- **Non-interactive `docker exec`, always.** All development happens inside containers, and agents get stuck on interactive prompts. The pattern is `docker exec airstack-robot-desktop-1 bash -c ""` — never `airstack connect` or `docker exec -it` in an agent session ([`use-airstack-cli`](https://github.com/castacks/AirStack/tree/develop/.agents/skills/use-airstack-cli)). +- **`bws` / `sws` aliases.** Inside robot containers, `bws` is `colcon build` with the repo's flags and `sws` sources `install/setup.bash`: + + ```bash + docker exec airstack-robot-desktop-1 bash -c "bws --packages-select my_pkg && sws && ros2 launch my_pkg my.launch.xml" + ``` + +- **Bring the stack up agent-friendly.** `airstack up robot-desktop --no-autolaunch` starts the container without launching the autonomy stack (so the agent controls what runs), and `airstack ready` blocks until containers → sim `/clock` → nodes → PX4 are actually up (`--json` for scripts) — better than the agent inventing sleep loops. +- **Verify with `airstack test` marks.** `airstack test -m unit -v` for fast hermetic checks, `-m "build_docker or build_packages"` for build health, `-m liveliness` / `-m sensors` / `-m takeoff_hover_land` for staged sim verification ([tests/README.md](https://github.com/castacks/AirStack/blob/develop/tests/README.md)). Have the agent copy the resulting `tests/results//metrics.json` into the notebook. +- **`wiring.md` is generated ground truth.** Each stack's `wiring.md` is snapshotted from the *running* graph and drift-checked in CI — never hand-edited. After the agent changes a stack's topology, it must regenerate it (`airstack test -m wiring --stack ...`) or CI fails ([stacks guide](stacks.md#wiringmd-generation-and-drift-checking)). +- **Logs are `docker logs`-visible.** Container tmux output is mirrored to `docker logs`, so `docker logs airstack-robot-desktop-1` is the agent's window into bringup — no tmux attach needed. + +None of this makes an agent infallible — it still writes plausible-looking wiring that doesn't connect and tests that pass vacuously. The notebook spec, the lettered evidence, and `wiring.md` drift-checking exist precisely so you can check the agent's work without re-doing it. + +## Related pages + +- [Feature Notebook](intermediate/feature_notebook.md) — the full notebook convention this guide builds on +- [AI Agent Quick Reference](advanced/ai_agent_guide.md) — the condensed agent-facing cheat sheet +- [Contributing Guide](intermediate/contributing.md) — the human PR process the notebook feeds into diff --git a/docs/gcs/extending_foxglove.md b/docs/gcs/extending_foxglove.md new file mode 100644 index 000000000..dec078410 --- /dev/null +++ b/docs/gcs/extending_foxglove.md @@ -0,0 +1,84 @@ +# Extending the Foxglove Visualizer + +This page is the maintainer guide to extending the GCS visualizer node — `foxglove_visualizer_node` — with new or modified marker types, and to bridging new topics to the GCS. For what the node visualizes today, the topic tables, layouts, and troubleshooting, see [GCS Foxglove Visualization](foxglove.md). The agent workflow for adding a topic is [`.agents/skills/visualize-in-foxglove`](../../.agents/skills/visualize-in-foxglove/SKILL.md). + +## How to modify or add a marker type + +The visualizer is designed to be extended in-place. The pattern, taken from `gcs/ros_ws/src/gcs_visualizer/gcs_visualizer/foxglove_visualizer_node.py`: + +### 1. Add a suffix and regex + +```python +PLAN_SUFFIX = '/global_plan' +self._plan_pattern = re.compile(rf'^/({re.escape(self._prefix)}_\w+){re.escape(PLAN_SUFFIX)}$') +``` + +### 2. Add state + +```python +self._global_plans = {} # robot_name -> latest msg +self._subscribed_plan = set() +``` + +### 3. Subscribe in `_discover_robots` + +```python +if topic not in self._subscribed_plan: + m = self._plan_pattern.match(topic) + if m and 'nav_msgs/msg/Path' in type_list: + name = m.group(1) + self.create_subscription( + Path, topic, + lambda msg, n=name: self._plan_callback(msg, n), + 10, # 10 = default RELIABLE for planning topics; + # SENSOR_QOS for high-rate sensor streams + ) + self._subscribed_plan.add(topic) +``` + + +### 4. Add a callback + +```python +def _plan_callback(self, msg: Path, robot_name: str): + self._global_plans[robot_name] = msg +``` + +### 5. Render in `_publish_markers` + +```python +plan = self._global_plans.get(robot_name) +boot = self._gps_boot.get(robot_name) +if plan is not None and boot is not None: + bx, by, bz = boot + line = Marker() + line.header.frame_id = 'map' + line.ns = f'{robot_name}_global_plan' + line.type = Marker.LINE_STRIP + for ps in plan.poses: + p = ps.pose.position + line.points.append(Point(x=p.x + bx, y=p.y + by, z=p.z + bz)) + array.markers.append(line) +``` + + +### 6. Bridge the source topic across DDS domains + +The visualizer can only subscribe to topics that crossed the DDS bridge. Add the source topic to `robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml` under `allowlist` (or, for the split stack, to `stacks/lite_offload_global/bridge.yaml` and regenerate): + +```yaml +allowlist: + - name: "rt/$(env ROBOT_NAME)/your/new_topic" +``` + +Then restart the robot containers — the router only re-reads its allowlist on startup. + +## Bridging a topic without writing a callback + +If your topic is already in a Foxglove-native type (`nav_msgs/Path`, `sensor_msgs/PointCloud2`, `visualization_msgs/MarkerArray`) and doesn't need the GPS offset, you can skip the visualizer entirely — just bridge it through the DDS router and add a panel in Foxglove pointing at the topic. The visualizer is only required when you need georeferencing or want everything to flow through the combined `/gcs/robot_markers` namespace. + +## See also + +- [GCS Foxglove Visualization](foxglove.md) — operator guide: layouts, input/output topics, troubleshooting +- [Coordination Payloads](../robot/autonomy/coordination/payloads.md) — extending visualization with gossip-broadcast payloads +- [`.agents/skills/visualize-in-foxglove`](../../.agents/skills/visualize-in-foxglove/SKILL.md) — agent workflow for adding a topic diff --git a/docs/gcs/foxglove.md b/docs/gcs/foxglove.md index f00c2ba54..1a2a4d9f6 100644 --- a/docs/gcs/foxglove.md +++ b/docs/gcs/foxglove.md @@ -2,7 +2,7 @@ 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). +This page describes what the node visualizes today and the topic naming convention. To change or add a marker type, see [Extending the Foxglove Visualizer](extending_foxglove.md). For the gossip payload visualization (filtered rays, voxel maps, etc.) see [Coordination Payloads](../robot/autonomy/coordination/payloads.md). ![Full GCS Foxglove view — overhead-textured 3D panel on top, Robot Tasks panel and per-robot camera + depth feeds along the bottom](foxglove_full_screen.png) @@ -63,81 +63,6 @@ All of these are published by individual robots in their **local `map` frame** ( To change which prefix is matched (e.g. you renamed robots from `robot_*` to `drone_*`), set the `robot_name_prefix` parameter on the visualizer node. -## How to modify or add a marker type - -The visualizer is designed to be extended in-place. The pattern, taken from `gcs/ros_ws/src/gcs_visualizer/gcs_visualizer/foxglove_visualizer_node.py`: - -### 1. Add a suffix and regex - -```python -PLAN_SUFFIX = '/global_plan' -self._plan_pattern = re.compile(rf'^/({re.escape(self._prefix)}_\w+){re.escape(PLAN_SUFFIX)}$') -``` - -### 2. Add state - -```python -self._global_plans = {} # robot_name -> latest msg -self._subscribed_plan = set() -``` - -### 3. Subscribe in `_discover_robots` - -```python -if topic not in self._subscribed_plan: - m = self._plan_pattern.match(topic) - if m and 'nav_msgs/msg/Path' in type_list: - name = m.group(1) - self.create_subscription( - Path, topic, - lambda msg, n=name: self._plan_callback(msg, n), - 10, # 10 = default RELIABLE for planning topics; - # SENSOR_QOS for high-rate sensor streams - ) - self._subscribed_plan.add(topic) -``` - - -### 4. Add a callback - -```python -def _plan_callback(self, msg: Path, robot_name: str): - self._global_plans[robot_name] = msg -``` - -### 5. Render in `_publish_markers` - -```python -plan = self._global_plans.get(robot_name) -boot = self._gps_boot.get(robot_name) -if plan is not None and boot is not None: - bx, by, bz = boot - line = Marker() - line.header.frame_id = 'map' - line.ns = f'{robot_name}_global_plan' - line.type = Marker.LINE_STRIP - for ps in plan.poses: - p = ps.pose.position - line.points.append(Point(x=p.x + bx, y=p.y + by, z=p.z + bz)) - array.markers.append(line) -``` - - -### 6. Bridge the source topic across DDS domains - -The visualizer can only subscribe to topics that crossed the DDS bridge. Add the source topic to `robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml` under `allowlist` (or, for the split stack, to `stacks/lite_offload_global/bridge.yaml` and regenerate): - -```yaml -allowlist: - - name: "rt/$(env ROBOT_NAME)/your/new_topic" -``` - -Then restart the robot containers — the router only re-reads its allowlist on startup. - -## Bridging a topic without writing a callback - -If your topic is already in a Foxglove-native type (`nav_msgs/Path`, `sensor_msgs/PointCloud2`, `visualization_msgs/MarkerArray`) and doesn't need the GPS offset, you can skip the visualizer entirely — just bridge it through the DDS router and add a panel in Foxglove pointing at the topic. The visualizer is only required when you need georeferencing or want everything to flow through the combined `/gcs/robot_markers` namespace. - ## Sim-only: textured overhead ground When running in sim, the visualizer also subscribes to `/sim/overhead/image` + `/sim/overhead/spec`. On receiving both, it builds one `TRIANGLE_LIST` marker on `/gcs/sim_ground` (latched) and tears down its subscriptions. See [2D World Map in Foxglove](../simulation/isaac_sim/overhead_camera.md) for the producer side. @@ -154,7 +79,7 @@ When running in sim, the visualizer also subscribes to `/sim/overhead/image` + ` ## See also +- [Extending the Foxglove Visualizer](extending_foxglove.md) — maintainer guide: modifying or adding marker types, bridging topics - [Coordination Payloads](../robot/autonomy/coordination/payloads.md) — extending visualization with gossip-broadcast payloads - [Adding Waypoints and Geofences](waypoints_and_geofences.md) — interactive click-to-place editors - [Overhead Camera](../simulation/isaac_sim/overhead_camera.md) — sim-side ground texture producer -- [`.agents/skills/visualize-in-foxglove`](../../.agents/skills/visualize-in-foxglove/SKILL.md) — agent workflow for adding a topic diff --git a/docs/gcs/usage/user_interface.md b/docs/gcs/usage/user_interface.md index 110774deb..0208741d7 100644 --- a/docs/gcs/usage/user_interface.md +++ b/docs/gcs/usage/user_interface.md @@ -1,23 +1,89 @@ -# User Interface +# Operating the GCS -The operator interface is **Foxglove Studio**, extended with the AirStack panels installed by the GCS container: +The operator interface is **Foxglove Studio**, extended with AirStack's custom panels. +When the GCS container starts, Foxglove opens automatically, already connected +(`ws://localhost:8765` in-container) and already showing the **AirStack default +(`` robots)** layout — rendered to match `NUM_ROBOTS`, no manual import needed +(see [GCS Foxglove Visualization](../foxglove.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 +## The layout at a glance -## Debugging tips +The auto-seeded layout is built from these panels: -Launch just the GCS container: +- **3D panel** — the fleet view in a shared global frame: per-robot meshes, + live trajectories, global plan polylines, and VDB occupancy maps, all merged + onto `/gcs/robot_markers` by the GCS visualizer (plus the sim-only textured + ground plane). +- **Robot Tasks panel** — the custom command panel (from the `robot-commands` + extension). This is where you fly the fleet. +- **Per-robot tabs** — one tab per robot with **Image** panels showing its camera + and depth feeds. -```bash -airstack up gcs # desktop profile -docker compose --profile deploy up gcs-real # deployed GCS hardware -``` +The **Waypoint Editor** and **Polygon Editor** are not separate panels — they are +embedded inside the Robot Tasks panel's Navigate, Exploration, and Coverage tabs. + +## Commanding a robot + +Each task type gets a tab in the Robot Tasks panel: **Takeoff**, **Land**, +**Navigate**, **Exploration**, **Coverage**, **Semantic Search**, and +**Fixed Trajectory**. The flow is always the same: pick the tab, fill in the goal +fields, pick the robot, click **Send**. Goals travel as ROS 2 actions, relayed by +`action_relay` from the GCS domain into each robot's own domain. + +A first flight looks like: + +1. **Takeoff** — set `target_altitude_m` and `velocity_m_s`, pick the robot, **Send**. + Watch the drone climb in the 3D panel. +2. **Navigate** — pick a waypoint set (a saved one from the **from:** dropdown, or + **active** for whatever is in the embedded Waypoint Editor), set + `goal_tolerance_m`, **Send**. +3. **Land** — set `velocity_m_s`, **Send**. + +**Take off before anything else.** Land, Navigate, and the mission tasks all assume +the drone is airborne — `takeoff_landing_planner` publishes `is_airborne`, and the +RViz Tasks Panel greys those tasks out until it's true. The Foxglove Robot Tasks +panel offers the same task set but does not currently gate on `is_airborne`, so keep +the Takeoff-first order yourself. + +## Waypoints and geofences -Get a shell in the running container: +- **Waypoint Editor** (Navigate tab) — ordered 3D waypoints for a Navigate route. +- **Polygon Editor** (Exploration / Coverage tabs) — a closed 2D area used as the + geofence / `search_bounds` for area tasks. + +Both work by enabling click capture and clicking in the 3D panel; point sets can be +named, saved to disk (they survive container restarts), and picked from the task +dropdowns later. Full walkthrough: [Adding Waypoints and Geofences](../waypoints_and_geofences.md). + +## Monitoring + +- **3D panel** — each robot's executing trajectory, global plan, and occupancy map + update live; the pose arrow tracks odometry. +- **Per-robot tabs** — camera and depth Image feeds confirm sensors are streaming. +- **Map panel (optional)** — the visualizer publishes each robot's GPS fix on + `/gcs//location` (plus a fixed reference on `/gcs/map_origin/location`); + add a Foxglove Map panel subscribed to those to see the fleet on a world map. +- **Any topic** — this is stock Foxglove: add Raw Messages or Plot panels for + whatever the bridge exposes (battery, MAVROS state, etc.). + +You can edit the layout freely and **Save** it — your edits persist and the seeder +won't overwrite them (delete the layout in **Layouts** to reset to the generated +default). + +## Troubleshooting + +Robot missing from the 3D view, wrong global position, markers not appearing — +see the [troubleshooting table in GCS Foxglove Visualization](../foxglove.md#troubleshooting). +For container-level debugging: ```bash -docker exec -it airstack-gcs-1 bash +airstack logs gcs # follow the bringup session output +airstack connect gcs # attach to the container's tmux session ``` + +## See also + +- [GCS Foxglove Visualization](../foxglove.md) — layout seeding, visualizer topics, troubleshooting +- [Adding Waypoints and Geofences](../waypoints_and_geofences.md) — the click-to-place editors in detail +- [GCS Overview](../index.md) — architecture, launch structure, `gcs` vs `gcs-real` +- [Extending the Foxglove Visualizer](../extending_foxglove.md) — adding marker types (maintainers) diff --git a/docs/getting_started/build_your_own_scene.md b/docs/getting_started/build_your_own_scene.md new file mode 100644 index 000000000..88a6b50ce --- /dev/null +++ b/docs/getting_started/build_your_own_scene.md @@ -0,0 +1,132 @@ +# Build and Fly Your Own Scene + +In this tutorial you'll create a simulation environment of your own from +scratch — no Unreal Engine, no special server access — and fly the AirStack +drone in it. You'll author a small obstacle stage in the Isaac Sim editor, +register it in the **scene catalog** under a shortname, and launch it with +`airstack up --sim isaac --scene`, watching AirStack turn a graphics-only +stage into a physics-ready scene automatically. Plan for **30–45 minutes**. + +Prerequisites: you've completed [Get AirStack Flying](index.md) — cloned the +repo, built or pulled the images, and flown the default Isaac Sim scene once. + +## 1. Open the Isaac Sim editor + +AirStack ships a GUI-only compose service ([Isaac Sim Container Workflows](../simulation/isaac_sim/container_workflows.md)) +that opens the full Isaac Sim editor with no Pegasus launch script and no +drones — exactly what you want for authoring: + +```bash +airstack up --profile isaac-sim-gui isaac-sim-gui +``` + +**Check:** the Isaac Sim editor window appears on your display with an empty +stage. If it doesn't, run `airstack logs isaac-sim-gui` — the usual fix is +`xhost +local:docker` (see +[troubleshooting](../simulation/isaac_sim/container_workflows.md#troubleshooting)). + +## 2. Author a stage and save it into the repo + +Build a minimal obstacle course — the menus below are standard Isaac Sim GUI +actions, not AirStack-specific: + +1. Add a floor: **Create > Physics > Ground Plane**. +2. Add obstacles: **Create > Mesh > Cube**, then use the translate/scale + gizmos to spread three or four cubes a couple of meters apart, leaving the + world origin clear (the drone spawns there). Each cube is 1 m per side by + default — a handy scale reference. +3. Save with **File > Save As** to + `/isaac-sim/AirStack/simulation/isaac-sim/assets/scenes/my_first_scene.stage.usd`. + +The repo is mounted read-write at `/isaac-sim/AirStack` inside the container, +so the file lands in your checkout. The `*.stage.usd` suffix is the +[naming convention](../simulation/isaac_sim/index.md#usd-file-naming-conventions) +for "graphics only, no physics, no robots" — physics comes later, for free. + +**Check:** on the host, `ls -lh simulation/isaac-sim/assets/scenes/` lists +`my_first_scene.stage.usd`. Then `airstack down` to stop the editor. + +## 3. Register the scene in the catalog + +The [scene catalog](../simulation/scenes.md) (`simulation/scenes.yaml`) maps +simulator-agnostic shortnames to whatever each simulator understands — an +Isaac entry may be a Pegasus catalog key or any `omniverse://` / `https://` +URL / `*.usd` path. Add an entry pointing at your stage's **container path** +(Isaac Sim authors new stages in meters, so `stage_scale` stays `1.0`): + +```yaml +scenes: + # ... existing entries ... + my-first-scene: + isaac: + ref: /isaac-sim/AirStack/simulation/isaac-sim/assets/scenes/my_first_scene.stage.usd + stage_scale: 1.0 # scale applied to /World/stage; 0.01 for cm-authored stages +``` + +**Check:** the resolver turns the shortname into launch config: + +```bash +python3 simulation/resolve_scene.py --sim isaac --scene my-first-scene +# ISAAC_SIM_SCENE=/isaac-sim/AirStack/simulation/isaac-sim/assets/scenes/my_first_scene.stage.usd +# ISAAC_SIM_STAGE_SCALE=1.0 +``` + +## 4. Fly it + +```bash +airstack up --sim isaac --scene my-first-scene +airstack ready # waits: containers → sim /clock → nodes → PX4 ready +``` + +At launch, the launch script's shared base class +([`pegasus_app.PegasusApp`](../simulation/isaac_sim/spawning_drones.md)) +prepares your stage automatically: it scales `/World/stage` by `stage_scale`, +applies collision geometry to every mesh (`add_colliders` — without this, +drones fall through the floor), and adds a dome light (`add_dome_light`): your +graphics-only stage becomes a physics-ready scene, no USD edits by hand. + +**Check:** the drone spawns resting on your ground plane among the cubes. +Press `Takeoff` and then `Navigate` in Foxglove exactly as in +[Get AirStack Flying](index.md#move-robot) — the drone lifts off. If the +spawn point (world origin) is inside an obstacle, move the cube and relaunch. + +## 5. Bake a self-contained `*.scene.usd` + +The prep from step 4 happens in memory on every launch. To bake it into a +shareable, self-contained scene package, copy +`simulation/isaac-sim/launch_scripts/example_one_px4_pegasus_launch_script.py` +to `my_scene_bake.py` alongside it, and add one kwarg to `PegasusApp(...)`: + +```python +save_scene_to="/isaac-sim/AirStack/simulation/isaac-sim/assets/scenes/my_first_scene_baked", +``` + +Launch once with your script selected: + +```bash +ISAAC_SIM_SCRIPT_NAME=my_scene_bake.py airstack up --sim isaac --scene my-first-scene +``` + +`PegasusApp` exports the prepared stage (colliders and dome light included) +and collects every referenced asset into that directory via +[`save_scene_as_contained_usd`](../simulation/isaac_sim/spawning_drones.md#scene-prep-helpers). + +**Check:** `ls simulation/isaac-sim/assets/scenes/my_first_scene_baked/` +shows a root `prepared_scene.usd` (plus `SubUSDs/`). Rename it to +`my_first_scene.scene.usd` per the physics-enabled naming convention — it now +loads standalone, no prep needed. + +## Congratulations + +You built an environment, gave it a shortname every teammate (and every +simulator) can use, and flew the full autonomy stack in it — the same +`--scene` flow that serves the shared catalog scenes. Where to go next: + +- [Export Stages from Unreal](../simulation/isaac_sim/export_stages_from_unreal.md) — + photoreal stages from Fab Marketplace environments; the source of the + [catalog's](../simulation/scenes.md#scene-catalog) guest-readable AirLab + stages (loading those requires access to the AirLab Nucleus server) +- [Spawning Drones](../simulation/isaac_sim/spawning_drones.md) — custom + launch scripts: multiple drones, explicit spawn poses, GPS origins, hooks +- [Simulation Scenes](../simulation/scenes.md) — the full catalog reference + and how `--scene` resolution works diff --git a/docs/getting_started/change_a_parameter.md b/docs/getting_started/change_a_parameter.md new file mode 100644 index 000000000..d12187318 --- /dev/null +++ b/docs/getting_started/change_a_parameter.md @@ -0,0 +1,92 @@ +# Change a Parameter and See the Effect + +A ~10 minute lesson in the core AirStack developer loop: **edit a config file +on the host → relaunch → watch the behavior change in sim → revert**. By the +end you will have made the drone take off at a different climb speed, and +you'll know exactly when an edit needs a rebuild and when it doesn't. + +**Prerequisite:** you finished [Getting Started](index.md) and the stack is +running in Isaac Sim, with Foxglove open. Our vehicle for the lesson is +`takeoff_velocity` in +[takeoff_landing_planner](../../robot/ros_ws/src/local/planners/takeoff_landing_planner/README.md) +— the ascent speed used whenever a Takeoff goal's `velocity_m_s` is `0`. + +!!! note "Why a YAML edit needs no rebuild" + + Host `robot/ros_ws/` is **bind-mounted** as + `/root/AirStack/robot/ros_ws/` in the robot container + (`robot/docker/robot-base-docker-compose.yaml`), and the `bws` build alias + runs `colcon build --symlink-install` (`robot/docker/.bashrc`), so the + *installed* config YAML is a symlink back to the source file you edit on + the host. But ROS 2 nodes read parameters **once at startup** — so a YAML + edit needs no rebuild, only a relaunch. + +## 1. Fly the baseline + +In Foxglove's Robot Tasks panel, open the **Takeoff** tab and set +`velocity_m_s` to `0` — zero means "use the config default", currently +1.0 m/s. Keep `target_altitude_m` at 10.0, pick your robot, **Send**. +**Check:** the drone reaches 10 m in about 10 seconds. **Land** it. + +## 2. Edit the parameter on the host + +In `robot/ros_ws/src/local/planners/takeoff_landing_planner/config/takeoff_landing_planner.yaml`, change: + +```yaml +takeoff_velocity: 1.0 # -> 0.2 +``` + +**Check:** the *installed* copy inside the container already shows your edit — +bind mount plus symlink-install, no `bws` needed. Hop into the container with +`airstack connect robot-desktop` (opens the container's tmux session; new +shell window with ++ctrl+b++ ++c++) and run: + +```bash +grep takeoff_velocity ~/AirStack/robot/ros_ws/install/takeoff_landing_planner/share/takeoff_landing_planner/config/takeoff_landing_planner.yaml +``` + +## 3. Relaunch the robot stack + +The node read its parameters at startup, so restart just the robot container +(the sim keeps running): + +```bash +airstack down robot-desktop +airstack up robot-desktop +airstack ready +``` + +**Check:** the running node now holds your value. Connect again +(`airstack connect robot-desktop`, ++ctrl+b++ ++c++ for a shell window) and +run — it prints `Double value is: 0.2`: + +```bash +sws && ros2 param get /robot_1/takeoff_landing_planner/takeoff_landing_task takeoff_velocity +``` + +## 4. See the effect + +Send the same Takeoff as step 1 (`velocity_m_s` = `0`, altitude 10 m). + +**Check:** the drone now crawls upward — roughly 50 seconds to 10 m instead +of 10. That's your edit, flying. + +## 5. Revert + +Change `takeoff_velocity` back to `1.0`, then repeat step 3. +**Check:** `ros2 param get` reports `1.0` again. + +Congratulations! You just ran the loop every AirStack change goes through: +edit → relaunch → observe → revert. + +!!! tip "When you *do* need to rebuild" + + Editing C++/Python source, message definitions, or launch/CMake files + means running `bws` (optionally `bws --packages-select `) inside + the container before relaunching; `sws` sources the built workspace. + +**Next stops:** the [robot configuration reference](../robot/configuration/index.md) +for the rest of the knobs, `robot/docker/.bashrc` for the `bws` / `sws` / +`autolaunch` helpers in full, and the +[debug-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/debug-module/SKILL.md) +when a change doesn't behave. diff --git a/docs/getting_started/first_fleet.md b/docs/getting_started/first_fleet.md new file mode 100644 index 000000000..4223e9af8 --- /dev/null +++ b/docs/getting_started/first_fleet.md @@ -0,0 +1,122 @@ +# Your First Fleet + +A ~25 minute lesson. By the end you will have declared a two-robot fleet in +one YAML file, validated it, launched both drones in Isaac Sim, watched them +side by side in Foxglove, and commanded each one independently. + +**Prerequisite:** you finished the +[Modular AirStack Walkthrough](modular_airstack.md) — you've flown +`full_default` and know that a fleet file owns *who exists* while the stack +owns *how each robot flies*. + +## 1. Write the fleet file + +Copy the reference single-robot fleet and grow it to two: + +```bash +cp config/fleets/sim_one_default.yaml config/fleets/my_fleet.yaml +``` + +Edit `config/fleets/my_fleet.yaml` to exactly this: + +```yaml +# Fleet: my_fleet — two quad_default robots flying the full_default stack. +defaults: + vehicle: quad_default + stack: stacks/full_default + +robots: + robot_1: + spawn: [-2, 0, 0.07] + robot_2: + spawn: [2, 0, 0.07] + +sim: + scene: default + +network: + domain_policy: auto + gossip_domain: 99 +``` + +**Check:** the file has exactly two entries under `robots:` — both inherit +`defaults:` (same vehicle, same brain: a **homogeneous** fleet), differing +only in spawn position, 4 m apart along X. + +## 2. Validate it + +```bash +airstack fleet list +python3 tools/fleet/resolve_fleet.py config/fleets/my_fleet.yaml --table +``` + +**Check:** `fleet list` shows a `my_fleet` row (`ROBOTS 2`, `homogeneous`), +and the resolver prints this table — note `DOMAIN`: robot N → ROS domain N, +its own DDS partition ([how identity resolves](../robot/docker/robot_identity.md)): + +```text +ROBOT DOMAIN VEHICLE STACK ENTRY HOSTS SPAWN +robot_1 1 quad_default stacks/full_default stack - [-2, 0, 0.07] +robot_2 2 quad_default stacks/full_default stack - [2, 0, 0.07] +``` + +## 3. Launch the fleet + +```bash +airstack up --fleet my_fleet --sim isaac +airstack ready +``` + +One flag does everything: validates the file, derives `NUM_ROBOTS=2`, stamps +two robot containers, and swaps in the generic fleet spawner +(`fleet_spawn.py`), which reads spawn positions and the scene from your YAML. + +**Check:** `airstack status` lists both `airstack-robot-desktop-1` and +`airstack-robot-desktop-2`, and the Isaac Sim viewport shows two drones on +the ground 4 m apart. `airstack ready` reports both flight-ready. + +## 4. Observe both in Foxglove + +The GCS container renders its layout to match the fleet: Foxglove opens +already showing **AirStack default (2 robots)** — the single-robot template +replicated per robot, no manual import +([how seeding works](../gcs/foxglove.md)). + +**Check:** the 3D panel shows both drone meshes in one shared frame, and the +tab strip has a **robot 1** and a **robot 2** tab, each with that robot's own +camera and depth feeds. + +## 5. Command each robot + +Each per-robot tab contains its **own Robot Tasks panel**, pre-targeted by +the **Robot:** field at the top (`robot_1` in the robot 1 tab, `robot_2` in +robot 2's) — that field is what addresses the goal, sent as a ROS 2 action +onto `/robot_N/tasks/...` and relayed into that robot's DDS domain. + +In the **robot 1** tab, open **Takeoff**, keep the defaults +(`target_altitude_m` 10.0, `velocity_m_s` 1.0), click **Send** — then switch +to the **robot 2** tab and do the same. (The Robot: field is editable text, +so any panel can retarget any robot by name.) + +**Check:** both drones climb in the 3D panel and settle in a hover; each +panel streams feedback (`3.2 / 10.0 m`) only for its own robot. + +## 6. Land and shut down + +Land each robot from its tab's **Land** task, then run `airstack down`. + +**Check:** `airstack status` shows no running AirStack containers. + +## Congratulations + +You declared a deployment as one readable file, validated it before spending +a GPU-second, and flew two independently-commanded robots — identity and +placement in the fleet, topology in the stack. Next, one line each: + +- Three quads, three different brains: + `airstack up --fleet sim_three_mixed --sim isaac` + ([the reference heterogeneous fleet](../development/fleets.md)) +- Heterogeneous fleets need generated per-robot services: + `airstack fleet generate ` ([Fleets guide](../development/fleets.md)) +- Put a robot's global layer on a ground host — split stacks and `hosts:`: + [split stacks and bridge.yaml](../development/stacks.md#split-stacks-and-bridgeyaml) diff --git a/docs/getting_started/first_module.md b/docs/getting_started/first_module.md new file mode 100644 index 000000000..287a5720d --- /dev/null +++ b/docs/getting_started/first_module.md @@ -0,0 +1,161 @@ +# Write Your First Module + +A ~45 minute lesson. By the end you will have scaffolded an in-tree module, +implemented a small ROS 2 node inside it, built it in the robot container, +wired it into a stack of your own, and watched it appear in the stack's +observed wiring — the full life of an AirStack capability, in miniature. + +**Prerequisite:** you finished the +[Modular AirStack Walkthrough](modular_airstack.md), so +`airstack up --stack full_default --sim isaac` works on your machine. Our +module, `odom_echo`, logs the arrival rate of the robot's canonical odometry +topic — trivial on purpose, so the *boundary motions* stay in focus. + +## 1. Scaffold the module + +```bash +airstack module create --in-tree odom_echo +``` + +This scaffolds a complete module boundary at +`robot/ros_ws/src/modules/odom_echo/`: a stub `module.yaml` manifest +([field reference](../../common/module_schema/README.md)) and an +`ament_python` package skeleton whose launch file declares its topic endpoint +as an **arg defaulting to the canonical name** (never a `` — wiring +belongs to whoever includes you). Trunk gitignores this path, so in a fork +commit with `git add -f`. + +**Check:** `find robot/ros_ws/src/modules/odom_echo -type f | sort` prints +(paths relative to the module): + +```text +module.yaml README.md +odom_echo/package.xml odom_echo/setup.py +odom_echo/setup.cfg odom_echo/resource/odom_echo +odom_echo/odom_echo/__init__.py odom_echo/odom_echo/odom_echo_node.py +odom_echo/launch/odom_echo.launch.xml +odom_echo/test/test_import.py +``` + +## 2. Implement the node + +Open `robot/ros_ws/src/modules/odom_echo/odom_echo/odom_echo/odom_echo_node.py` +and replace the stub class with a rate logger (keep the generated `main()`): + +```python +import rclpy +from rclpy.node import Node +from nav_msgs.msg import Odometry + + +class OdomEchoNode(Node): + def __init__(self): + super().__init__("odom_echo") + self.declare_parameter("odometry_topic", "odometry") + topic = self.get_parameter("odometry_topic").value + self.count = 0 + self.create_subscription(Odometry, topic, self.on_odometry, 10) + self.create_timer(2.0, self.report) + + def on_odometry(self, msg): + self.count += 1 + + def report(self): + self.get_logger().info(f"odometry rate: {self.count / 2.0:.1f} Hz") + self.count = 0 +``` + +Two small edits alongside it: in `package.xml`, add +`nav_msgs` next to `rclpy` (every dependency gets +declared). In `launch/odom_echo.launch.xml`, delete the `robot_name` arg and +the node's `namespace="$(var robot_name)"` attribute — the stack dispatcher +already pushes `/$ROBOT_NAME` onto everything it includes, so keeping the +stub's namespace would nest your node at `/robot_1/robot_1/odom_echo`. + +**Check:** `python3 -m py_compile ` exits silently +(no syntax errors). + +## 3. Build it in the robot container + +All builds happen inside Docker — start the robot container without +autolaunch, then hop in with `airstack connect` (it attaches to the +container's tmux session; open a fresh shell window with ++ctrl+b++ then +++c++) and build just your package with the `bws` alias: + +```bash +airstack up robot-desktop --no-autolaunch +airstack connect robot-desktop +``` + +Then, inside the container: + +```bash +bws --packages-select odom_echo +sws && ros2 pkg list | grep odom_echo +``` + +**Check:** the last command prints `odom_echo` — colcon found the package +through the `ros_ws` bind mount, and the build persists across restarts. +Detach from the container with ++ctrl+b++ then ++d++ (or `exit`). + +## 4. Wire it into a stack of your own + +Never edit a reference stack — copy one, then add your include. Run +`airstack stack new full_default my_stack`, open +`stacks/my_stack/launch/stack.launch.xml`, and append one line before +`` (a bare include means "wired canonically"): + +```xml + +``` + +Relaunch on your stack (stack launch files are bind-mounted — no rebuild): + +```bash +airstack down +airstack up --stack my_stack --sim isaac +airstack ready +``` + +**Check:** the node is alive under the robot namespace, reporting a live rate. +Connect to the container (`airstack connect robot-desktop`, new window with +++ctrl+b++ ++c++) and run: + +```bash +ros2 node list | grep odom_echo +# /robot_1/odom_echo +``` + +Your node's log lines (`odometry rate ...`) are also visible from the host via +`docker logs airstack-robot-desktop-1` — tmux output is mirrored there. + +## 5. See it in the wiring + +Your stack has no `wiring.md` yet (`stack new` deliberately doesn't copy it — +that file is *observed*, never inherited). With the stack still running, +snapshot the live graph, then confirm there's no drift: + +```bash +airstack doctor --snapshot --stack my_stack +airstack doctor --live --stack my_stack +``` + +**Check:** `grep odom_echo stacks/my_stack/wiring.md` shows your node and its +odometry edge, and `--live` reports `graph matches wiring.md`. (The CI-grade +route that stamps verified provenance: +`airstack test -m wiring --stack my_stack --sim isaacsim --num-robots 1`.) + +## Congratulations + +You scaffolded a module boundary, built a node inside it, composed it into +your own stack, and captured the result as observed wiring — the loop every +AirStack capability grows through. Next, one line each: + +- Track extraction debt as your module grows: `airstack module doctor --drift` + ([AirStack Modules](../development/modules.md)) +- Graduate it to a standalone repo: the + [extract-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/extract-module/SKILL.md) +- Give it CI and register it: [Module CI](../development/module_ci.md) and the + [Module & Stack Catalog](../modules/index.md) +- Deeper stack authoring (splits, lints, `stack diff`): + [Creating a Custom Stack Topology](../development/creating_a_stack.md) diff --git a/docs/getting_started/fly_a_mission.md b/docs/getting_started/fly_a_mission.md new file mode 100644 index 000000000..b9d02d55d --- /dev/null +++ b/docs/getting_started/fly_a_mission.md @@ -0,0 +1,110 @@ +# Fly a Mission from the GCS + +A ~20 minute operator lesson. By the end you will have flown a complete +mission from the Ground Control Station: taken off, placed waypoints, drawn a +geofence, navigated a route, landed, and saved the mission set to disk for +next time. + +**Prerequisite:** you finished [Getting Started](index.md) and the stack is +running in Isaac Sim (`airstack up`, then `airstack ready` reports the drone +is ready to fly). Foxglove opened automatically with the AirStack layout. + +!!! note "What's in the scene" + + The default launch spawns a single drone in the Pegasus **Default + Environment** — a flat, open ground plane with no obstacles. Perfect for + a first mission. + +## 1. Orient yourself in the layout + +Look at the auto-loaded Foxglove layout ([tour](../gcs/usage/user_interface.md)): + +- **3D panel** — the fleet view: the drone's mesh, its live trajectory, and a + textured ground plane. +- **Robot Tasks panel** — the command panel, with one tab per task: + **Takeoff**, **Land**, **Navigate**, **Exploration**, **Coverage**, + **Semantic Search**, and **Fixed Trajectory**. +- **Per-robot tabs** — Image panels showing the robot's camera and depth feeds. + +**Check:** you can see the drone sitting on the ground plane in the 3D panel, +and a live camera feed in the robot's tab. + +## 2. Take off + +In the Robot Tasks panel, open the **Takeoff** tab. Keep the defaults +(`target_altitude_m` 10.0, `velocity_m_s` 1.0), pick your robot, and click +**Send**. + +**Check:** the panel streams feedback like `3.2 / 10.0 m` while the drone +climbs in the 3D panel, finishing in a hover at altitude. + +## 3. Place waypoints + +The **Waypoint Editor** is embedded in the **Navigate** tab: + +1. Open the **Navigate** tab and toggle **Enable click capture** on. +2. In the 3D panel toolbar (top right), click the **Publish** tool (▷ icon) + and switch its mode to **Publish 2D point (/clicked_point)**. +3. Set the **Default altitude** field to `10` so waypoints match your hover + altitude. +4. Click 2–3 spots in the 3D panel, a few tens of meters apart. + +**Check:** a numbered sphere appears at each click, in click order. (No +markers? In the 3D panel's Topics list, toggle on `/gcs/waypoints/markers`.) + +## 4. Draw a geofence + +The **Polygon Editor** works the same way, from the **Exploration** tab: + +1. Open the **Exploration** tab and toggle its **Enable click capture** on. +2. With the Publish tool still in **Publish 2D point** mode, click 3–4 + vertices surrounding your waypoints. + +**Check:** the vertices render as a closed loop around your route in the 3D +panel. This polygon is the geofence / `search_bounds` that area tasks like +Exploration and Coverage stay inside — details in +[Adding Waypoints and Geofences](../gcs/waypoints_and_geofences.md). + +## 5. Fly the route + +Back in the **Navigate** tab: in the **from:** dropdown pick **active** (the +waypoints currently in the editor), set `goal_tolerance_m` to `1.0`, pick +your robot, and click **Send**. + +**Check:** in the 3D panel the drone tracks through your waypoints in order +while the feedback's `distance_to_goal` shrinks; the task ends with +`Goal reached`. (Under the hood this is a +[NavigateTask action](../robot/autonomy/tasks.md) sent to the robot.) + +## 6. Land + +Open the **Land** tab, keep the default `velocity_m_s` (0.3), and click +**Send**. + +**Check:** the altitude readout falls and the drone settles onto the ground +plane in the 3D panel. + +## 7. Save your mission set + +Saves survive container restarts, so tomorrow's flight starts from here: + +1. In the Navigate tab's Waypoint Editor, type a name (e.g. `first_mission`) + into the **save name…** field and click **+ Add**. +2. Click **Save** on the new row — it changes to **✓ Saved**. +3. Repeat in the Exploration tab's Polygon Editor for your geofence. + +**Check:** on the host, the save files exist: + +```bash +ls ~/.airstack/gcs_waypoint_saves.json ~/.airstack/gcs_polygon_saves.json +``` + +Your saved set now also appears in the Navigate tab's **from:** dropdown — +next flight, pick it instead of **active** and hit **Send**. + +Congratulations! You flew a full mission — takeoff, waypoints, geofence, +navigation, landing — and saved it to disk. + +**Next:** [Change a Parameter and See the Effect](change_a_parameter.md) — your +first taste of the developer loop. For everything the operator interface can +do, read [Operating the GCS](../gcs/usage/user_interface.md). diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md index d46afce1a..6600cef6e 100644 --- a/docs/getting_started/index.md +++ b/docs/getting_started/index.md @@ -2,10 +2,11 @@ !!! tip "On Mac, Windows, or no GPU?" - This page assumes a Linux desktop with an NVIDIA GPU. If that's not you, - use [AirStack on OSMO](../tutorials/airstack_on_osmo.md) instead — you - only need an SSH key, the `osmo` CLI, and VS Code or Cursor. No local - Docker, no NVIDIA drivers, no `airstack install`. + This page assumes a Linux desktop with an NVIDIA GPU. If you don't have + one, [AirStack on OSMO](../tutorials/airstack_on_osmo.md) is the + recommended remote development path — you only need an SSH key, the + `osmo` CLI, and VS Code or Cursor. No local Docker, no NVIDIA drivers, + no `airstack install`. !!! warning "" @@ -22,12 +23,12 @@ By the end of this tutorial, you will have the autonomy stack running on your ma ## Requirements -You need at least 25GB free to install the Docker image. +The Docker images take about 25GB; we recommend at least 100GB of free disk space. Check the hardware requirements for the NVIDIA Isaac Sim [here](https://docs.isaacsim.omniverse.nvidia.com/latest/installation/requirements.html). -A GPU of GeForce RTX 4080 or higher is recommended for the best performance. +An NVIDIA RTX 3070 is the minimum GPU; a GeForce RTX 4080 or better is recommended for the best performance. -AirStack is primarily tested on Ubuntu 22.04. +AirStack is tested on Ubuntu 22.04 and 24.04. ## Clone ```bash @@ -97,19 +98,24 @@ Containers start immediately, but the ROS 2 workspace still builds and PX4 still airstack ready # or: airstack up --wait ``` -Useful variants (see `airstack help up`): +Useful variants: ```bash airstack up --sim airsim # MS AirSim instead of Isaac Sim airstack up --sim isaac --robots 3 # multi-robot (auto-selects the multi-drone scene script) ``` +The full set of launch flags is in the [CLI reference](../development/beginner/airstack-cli/index.md#airstack-up-flags) (or `airstack help up`). + ## Move Robot -Open Foxglove in the GCS to command the robot: import the layout `/root/airstack_layout_num_robots_1.json` (Layouts → Import from file…), connect to `ws://localhost:8765`, then press `Takeoff` in the Robot Tasks panel, then `Navigate` like in this video: +Foxglove opens automatically in the GCS with the AirStack layout already loaded — no manual import or connection needed (see [GCS Foxglove Visualization](../gcs/foxglove.md)). Press `Takeoff` in the Robot Tasks panel, then `Navigate`, like in this video: +!!! note + If you instead connect a Foxglove running on the host, the bridge is exposed on host port `8766` (container port `8765`): connect to `ws://localhost:8766`. + ## Shutdown diff --git a/docs/getting_started/modular_airstack.md b/docs/getting_started/modular_airstack.md index 796649e8b..0919a2c9c 100644 --- a/docs/getting_started/modular_airstack.md +++ b/docs/getting_started/modular_airstack.md @@ -10,14 +10,9 @@ images pulled or built). ## 1. Clone and set up -```bash -git clone --recursive -j8 git@github.com:castacks/AirStack.git -cd AirStack -./airstack.sh install # docker, compose, NVIDIA Container Toolkit -./airstack.sh setup # enables the `airstack` command -source ~/.bashrc # or ~/.zshrc -airstack images pull # or: airstack images build -``` +This is the base [Getting Started](index.md) setup — follow it there (clone, +`install`/`setup`, images pulled or built). Everything below assumes a working +`airstack` command in the repo root. ## 2. Fly a reference stack @@ -70,13 +65,13 @@ airstack module list # name, type, pin, targets, valid? `module add` records the pin in `modules.repos`, syncs the repo into the gitignored `modules/` dir, validates its `module.yaml`, places overlay symlinks, and regenerates a compose override that mounts the module into the -right containers. Then bring the stack up with the module mounts, selecting -one of the module's Isaac scene scripts: +right containers (`airstack up` includes that override automatically whenever +it exists). Then bring the stack up, selecting one of the module's Isaac scene +scripts: ```bash ISAAC_SIM_SCRIPT_NAME=modules/dfm2_disturbances/one_px4_pegasus_fan_force_field.py \ - airstack up --stack full_default --sim isaac \ - -f .airstack/generated/docker-compose.modules.yaml + airstack up --stack full_default --sim isaac ``` Full CLI reference (sync, remove, hooks, the pinning rule): diff --git a/docs/getting_started/tutorials_reference.md b/docs/getting_started/tutorials_reference.md index ae1d564bc..f2b80beb5 100644 --- a/docs/getting_started/tutorials_reference.md +++ b/docs/getting_started/tutorials_reference.md @@ -1,109 +1,37 @@ -# What's Next? Tutorials by Topic - -Now that you have AirStack running, explore these hands-on tutorials to learn specific workflows. Each section below contains focused tutorials for that area of AirStack. - -### 🎮 Simulation Tutorials - -Learn to work with Isaac Sim and create custom simulation scenarios. - -| Tutorial | Description | Level | -|----------|-------------|-------| -| [Spawning Drones](../simulation/isaac_sim/spawning_drones.md) | Launch scripts for one or many drones in Isaac Sim | Beginner | -| [Custom Scene Setup](../simulation/isaac_sim/scene_setup.md) | Create custom Isaac Sim environments | Intermediate | -| [Pegasus Scene Setup](../simulation/isaac_sim/pegasus_scene_setup.md) | Build scenes with Pegasus extension | Intermediate | - -### 🤖 Robot Autonomy Tutorials - -Understand the autonomy stack and configure different operation modes. - -| Tutorial | Description | Level | -|----------|-------------|-------| -| [Autonomy Modes](../robot/autonomy_modes.md) | Understand onboard vs offboard stack placement | Beginner | -| [System Architecture Overview](../robot/autonomy/system_architecture.md) | Deep dive into the layered architecture | Intermediate | -| [Module Integration](../robot/autonomy/integration_checklist.md) | Add custom modules to the stack | Advanced | - -### 🧩 Modular AirStack Tutorials - -Compose the autonomy software from modules, stacks, and fleets. - -| Tutorial | Description | Level | -|----------|-------------|-------| -| [Modular AirStack Walkthrough](modular_airstack.md) | Fly a reference stack, add a module, make your own stack, scale to a fleet | Beginner | -| [AirStack Modules](../development/modules.md) | Pull in pinned external capability repos with `airstack module add` | Intermediate | -| [AirStack Stacks](../development/stacks.md) | Self-contained topology folders with CI-observed wiring | Intermediate | -| [AirStack Fleets](../development/fleets.md) | Declare whole deployments (robots, vehicles, stacks) in one YAML file | Intermediate | - -### 📡 Ground Control Station Tutorials - -Learn to monitor and control robots from the GCS. - -| Tutorial | Description | Level | -|----------|-------------|-------| -| [User Interface Basics](../gcs/usage/user_interface.md) | Navigate the GCS interface | Beginner | -| [GCS Foxglove Visualization](../gcs/foxglove.md) | Visualize the fleet in Foxglove | Beginner | -| [Adding Waypoints & Geofences](../gcs/waypoints_and_geofences.md) | Interactive route and area editors | Intermediate | - -### 💻 Development Tutorials - -Customize AirStack and add your own algorithms. - -| Tutorial | Description | Level | -|----------|-------------|-------| -| [Fork Your Own Project](../development/beginner/fork_your_own_project.md) | Fork or template AirStack for your project | Beginner | -| [Development Environment Setup](../development/beginner/development_environment.md) | Configure your IDE and tools | Beginner | -| [AI-Assisted Development](../development/advanced/ai_agent_guide.md) | Use AI agents to accelerate development | Intermediate | - -### 🚁 Real World Deployment Tutorials - -Deploy AirStack to real hardware and fly autonomous missions. - -| Tutorial | Description | Level | -|----------|-------------|-------| -| [Deploying to Hardware](../real_world/deploying_to_hardware.md) | Install AirStack on Jetson or VOXL | Intermediate | -| [HITL Testing](../real_world/HITL/index.md) | Hardware-in-the-loop testing workflow | Intermediate | - ---- - -## Learning Paths - -Choose a path based on your goals: - -**🎯 I want to develop algorithms:** - -1. Complete Getting Started (you are here! ✓) -2. [Modular AirStack Walkthrough](modular_airstack.md) -3. [System Architecture](../robot/autonomy/system_architecture.md) -4. [AirStack Modules](../development/modules.md) and [AirStack Stacks](../development/stacks.md) -5. [Development Environment](../development/beginner/development_environment.md) -6. [AI-Assisted Development](../development/advanced/ai_agent_guide.md) - -**🚁 I want to deploy to hardware:** - -1. Complete Getting Started (you are here! ✓) -2. [Autonomy Modes](../robot/autonomy_modes.md) -3. [HITL Testing](../real_world/HITL/index.md) -4. [Deploying to Hardware](../real_world/deploying_to_hardware.md) - -**🎮 I want to create custom simulations:** - -1. Complete Getting Started (you are here! ✓) -2. [Spawning Drones](../simulation/isaac_sim/spawning_drones.md) -3. [Custom Scene Setup](../simulation/isaac_sim/scene_setup.md) -4. [Pegasus Scene Setup](../simulation/isaac_sim/pegasus_scene_setup.md) - -**👥 I want to coordinate multi-robot teams:** - -1. Complete Getting Started (you are here! ✓) -2. [AirStack Fleets](../development/fleets.md) -3. [AirStack Stacks](../development/stacks.md) (split onboard/offboard stacks) -4. [Ground Control Station Basics](../gcs/usage/user_interface.md) -5. [GCS Foxglove Visualization](../gcs/foxglove.md) - ---- - -## Additional Resources - -- **[Development Guide](../development/index.md)** - Comprehensive developer documentation -- **[System Architecture](../robot/autonomy/system_architecture.md)** - Understanding the autonomy stack -- **[Contributing Guide](../development/intermediate/contributing.md)** - How to contribute to AirStack -- **[About & FAQ](../about.md)** - Project information and frequently asked questions \ No newline at end of file +# What's Next? + +You've flown the stack — here's where to go next, organized by what you're trying to do. The docs are arranged in four kinds: **Tutorials** (guided lessons), **How-to Guides** (task recipes), **Reference** (look-up tables and specs), and **Concepts** (how and why the system is built this way). + +## Continue learning + +The tutorial sequence, in order: + +1. [Fly a Mission from the GCS](fly_a_mission.md) — take off, place waypoints, run a Navigate task, land, save your mission. +2. [Change a Parameter](change_a_parameter.md) — the edit → relaunch → observe development loop in ten minutes. +3. [Modular AirStack Walkthrough](modular_airstack.md) — fly a reference stack, read its wiring, add a module, make your own stack, launch a fleet. +4. [Write Your First Module](first_module.md) — scaffold a module, implement a node, wire it into your own stack, see it in the wiring. +5. [Your First Fleet](first_fleet.md) — a two-robot fleet file, both drones in Foxglove, tasks to each. +6. [Build and Fly Your Own Scene](build_your_own_scene.md) — author a stage, register it in the scene catalog, fly it. +7. [Deploy to Hardware](../real_world/deploying_to_hardware.md) — from bench-ready Jetson to first-flight checks. + +## By goal + +| I want to… | Go to | Kind | +|---|---|---| +| Set up my dev environment (local or remote) | [Development Environment Setup](../development/beginner/development_environment.md) · [Remote Development on OSMO](../tutorials/airstack_on_osmo.md) | How-to | +| Understand the big picture first | [Key Concepts](../development/beginner/key_concepts.md) · [System Architecture](../robot/autonomy/system_architecture.md) | Concepts | +| Build my own algorithm module | [Integration Checklist](../robot/autonomy/integration_checklist.md) · [Modules](../development/modules.md) | How-to / Concepts | +| Customize the simulation | [Scenes](../simulation/scenes.md) · [Spawning Drones](../simulation/isaac_sim/spawning_drones.md) | How-to | +| Operate the GCS | [Waypoints & Geofences](../gcs/waypoints_and_geofences.md) · [Foxglove Visualization](../gcs/foxglove.md) | How-to | +| Run more robots | [Fleets](../development/fleets.md) · [Robot Identity](../robot/docker/robot_identity.md) | Concepts | +| Deploy to real hardware | [Robot & Field overview](../real_world/index.md) | How-to | +| Look up a command, topic, or config value | [CLI Reference](../development/beginner/airstack-cli/index.md) · [Interface Conventions Spec](../robot/autonomy/interface_conventions.md) | Reference | +| Run or write tests | [Testing Overview](../development/intermediate/testing/index.md) | How-to | +| Contribute changes back | [Contributing Guide](../development/intermediate/contributing.md) | How-to | + +## Suggested paths + +- **Simulation user**: Get AirStack Flying → Scenes → Spawning Drones → Waypoints & Geofences. +- **Module developer**: Modular AirStack Walkthrough → Key Concepts → Integration Checklist → Interface Conventions Spec. +- **Robot deployer**: Key Concepts → Robot Identity → Robot & Field overview → Deployment Topologies. +- **Contributor**: Development Environment Setup → Testing Overview → Contributing Guide. diff --git a/docs/real_world/HITL/index.md b/docs/real_world/HITL/index.md index 1215e1df5..e993113a4 100644 --- a/docs/real_world/HITL/index.md +++ b/docs/real_world/HITL/index.md @@ -1,25 +1,70 @@ + + # Hardware-In-The-Loop Simulation -We configure a multi-machine HITL simulation, where a powerful desktop computer runs Isaac Simulator and rendering, and one/multiple jetson compute boards run robot-specific programs (planning, mapping, etc.). -## Requirement -A desktop computer configured according to [here](../../getting_started/index.md). One/multiple ORIN AGX/NX configured according to [here](../installation/index.md). -## Communication -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. +Hardware-in-the-loop (HITL) testing runs the real onboard compute — a Jetson flying the same `robot-l4t` container it will fly in the field — against a simulator on a desktop machine, before any propellers spin. The desktop runs Isaac Sim (and optionally the GCS); the Jetson runs the autonomy stack; they talk ROS 2 over the LAN. + +## Prerequisites + +- A desktop set up per [Getting Started](../../getting_started/index.md), with the Isaac Sim image built or pulled. +- One or more Jetson ORIN AGX/NX set up per the [Installation Guide](../installation/index.md), with the `robot-l4t` image pulled and the repo cloned. +- All machines on the same LAN (we test with everything wired into one router). Verify each machine can `ping` the others. +- Each Jetson's hostname set to the `robot-` convention (e.g. `hostnamectl set-hostname robot-1`) so [robot identity resolution](../../robot/docker/robot_identity.md) assigns `ROBOT_NAME=robot_1` / `ROS_DOMAIN_ID=1` — the fallback is silent, so check this first. + +## Desktop: simulator (+ GCS) + +The `hitl` compose profile selects `gcs-real` — the field variant of the GCS that runs with `network_mode: host` so its DDS participants sit directly on the LAN (see [GCS Docker Configuration](../../gcs/docker/index.md)). Combine it with the `isaac-sim` profile, overriding the `.env` default (`desktop,isaac-sim`) so no `robot-desktop` containers start on the desktop: + +```bash +COMPOSE_PROFILES="hitl,isaac-sim" airstack up +``` + +This starts `isaac-sim` and `gcs-real` only. If you don't want a ground station, use `COMPOSE_PROFILES="isaac-sim" airstack up` for the simulator alone. + +## Jetson: robot stack + +On each Jetson, use the `l4t` profile (same command as the [Installation Guide](../installation/index.md); on a Jetson clone, also set `COMPOSE_PROFILES=l4t` in `.env` so the desktop default `desktop,isaac-sim` profiles are not active): -### Run -On the desktop computer, under your AirStack folder, run ```bash -docker compose up isaac-sim +airstack --profile l4t up ``` -You should see the Isaac simulator being launched. -On the Jetson computer, run + +`robot-l4t` runs with `network_mode: host` and autolaunches `robot.launch.xml sim:=false` with the `full_default` stack. To pick a different [stack](../../development/stacks.md), pass `--stack` (e.g. `airstack --profile l4t up --stack lite_default`), or add `--no-autolaunch` to start the container idle and launch manually. + +## Networking: what has to line up + +The old failure modes here are all networking. Check each of these: + +- **Domain IDs.** Isaac Sim publishes each drone's topics on `ROS_DOMAIN_ID = N` (drone `domain_id` convention in the Pegasus launch scripts), and the Jetson's hostname must resolve to the same domain via the [robot name map](../../robot/docker/robot_identity.md) — `robot-1` → domain 1 matches sim drone 1. Verify inside the container: `docker exec airstack-robot-l4t-1 bash -c 'echo "$ROBOT_NAME / $ROS_DOMAIN_ID"'`. +- **DDS transport.** All AirStack containers load `common/fastdds.xml`, which forces plain UDP and disables shared memory — required for topics to cross container and machine boundaries at all. +- **Sim container is on a Docker bridge.** The `isaac-sim` container joins the internal `airstack_network` bridge at the fixed address `172.31.0.200`, while the Jetson containers are host-networked on the LAN. DDS multicast discovery does not traverse the desktop's Docker bridge NAT by itself, and the repo does not ship a peer configuration for this topology. If the Jetson does not see sim topics (`ros2 topic list` empty apart from local nodes), you will need to configure cross-machine discovery yourself — e.g. a Fast DDS initial-peers profile or a discovery server pointing at the desktop's LAN IP — and verify it; this step is not currently automated or tested in CI. +- **Flight controller connection.** `robot-l4t` defaults to a real flight controller on serial (`FCU_URL=/dev/ttyTHS4:115200`) — with a physical FCU wired to the Jetson, MAVROS talks to real hardware while sensors come from the sim. If instead you want MAVROS to reach the PX4 SITL instance inside Isaac Sim, `FCU_URL` is env-overridable (unset, `interface_bringup`'s `interface.launch.py` computes `udp://:<14540+N>@$SIM_IP:<14580+N>`), but note `SIM_IP`'s default `172.31.0.200` is only reachable from the desktop's own Docker network, and the compose files do not publish PX4's UDP ports to the LAN — this cross-machine SITL path is untested; expect to forward those ports yourself. +- **GCS visibility.** With the `full_default` stack, the robot runs a [DDS router](../../robot/autonomy/dds_router.md) that bridges an allowlist of its topics into domain 0, where the host-networked `gcs-real` listens. + +## Verification + ```bash -docker compose --profile l4t up robot-l4t +airstack status # containers up on each machine +docker exec airstack-robot-l4t-1 bash -c "ros2 node list" # stack nodes present +docker exec airstack-robot-l4t-1 bash -c "ros2 topic hz /robot_1/sensors/front_stereo/left/image_rect" ``` -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. -Screen record of desktop computer: +Once the scene is playing in Isaac Sim, sensor topics on the Jetson should tick at a steady rate. On the desktop, `gcs-real` launches Foxglove Studio (host networking: connect your own Foxglove to `ws://localhost:8765`) — sensor and odometry panels streaming live data confirm the LAN link end to end. See [GCS Foxglove Visualization](../../gcs/foxglove.md). Use `airstack logs robot-l4t` / `airstack connect robot-l4t` to debug the bringup session. + +!!! note "Tested configuration" + The demos below were recorded on an earlier AirStack release (raw `docker compose`, RViz-based verification). The commands on this page reflect the current CLI and compose profiles but this exact multi-machine topology is not covered by CI — treat the discovery and SITL caveats above as things to verify on your own network. + +## Demos + +Screen recording of the desktop machine: -Screen record of Jetson computer: - \ No newline at end of file +Screen recording of the Jetson: + diff --git a/docs/real_world/deploying_to_hardware.md b/docs/real_world/deploying_to_hardware.md index dd1cd89cf..26533bf0a 100644 --- a/docs/real_world/deploying_to_hardware.md +++ b/docs/real_world/deploying_to_hardware.md @@ -1,27 +1,140 @@ # Deploying to Hardware -!!! note "Coming Soon" - This tutorial is a placeholder. Content will be added in a future release. +!!! danger "Safety first" + This tutorial ends with a real drone spinning real propellers. **Keep propellers off + until Step 8**, and arm only after every check in this page has passed. Fly with a + safety pilot holding an RC transmitter with a working manual override / kill switch, + where you are permitted to fly, per local regulations. -## Overview +This tutorial takes one path from a bench-top Jetson to a first autonomous flight: a Jetson +Orin running the `l4t` profile with the `full_default` stack, connected to a PX4 flight +controller over serial. Other platforms (e.g. ModalAI VOXL2 via the `voxl` profile) follow +the same shape — see [Autonomy Modes](../robot/autonomy_modes.md). -This tutorial will cover how to configure and deploy the AirStack autonomy stack onto a physical robot — either a Jetson-based system (L4T profile) or a ModalAI VOXL device (VOXL profile). +## 1. Prerequisites -## Prerequisites +- A Jetson ORIN AGX/NX flashed with JetPack (L4T, Ubuntu 22.04) — the tested platform per the [Installation Guide](installation/index.md). +- A PX4-based flight controller wired to a Jetson UART — default `/dev/ttyTHS4` at + 115200 baud; *verify the UART and baud on your hardware, wiring varies by carrier board*. +- You have completed [Getting Started](../getting_started/index.md) in simulation. +- An RC transmitter bound to the flight controller for manual takeover. -- Completed the [Getting Started](../getting_started/index.md) tutorial. -- Physical robot hardware with a supported flight controller. -- Network access to the device (SSH or direct connection). +**Check:** on the Jetson, `ls -l /dev/ttyTHS4` shows the serial device (or note the device your FCU is actually wired to — you'll need it in Step 4). -## Steps +## 2. Install AirStack on the Jetson - +```bash +git clone --recursive -j8 git@github.com:castacks/AirStack.git +cd AirStack +./airstack.sh setup # adds `airstack` to PATH, installs Docker if needed +docker compose --profile l4t pull robot-l4t # pull the Jetson image +``` + +Also set `COMPOSE_PROFILES=l4t` in the repo's `.env` so the desktop defaults (`desktop,isaac-sim`) are not active on the Jetson. + +**Check:** `docker images | grep robot-l4t` lists the pulled image. + +## 3. Set the robot's identity + +Real-robot profiles resolve `ROBOT_NAME` and `ROS_DOMAIN_ID` from the **OS hostname** +via the [robot name map](../robot/docker/robot_identity.md). Name the device `robot-`: + +```bash +sudo hostnamectl set-hostname robot-1 +``` + +!!! warning "The fallback is silent" + A hostname that doesn't match the map does **not** error — the default map's catch-all + resolves it to `ROBOT_NAME=unknown_robot`, `ROS_DOMAIN_ID=0`, and the symptoms only + surface later (topics under `/unknown_robot`, the `zed-l4t` container on domain 1 + unable to see the stack). Check the mapping now. + +**Check:** the resolver maps your hostname as expected (re-verified in Step 6): + +```bash +python3 robot/docker/robot_name_map/resolve_robot_name.py $(hostname) \ + robot/docker/robot_name_map/default_robot_name_map.yaml # → ROBOT_NAME=robot_1 / ROS_DOMAIN_ID=1 +``` + +## 4. Configure the FCU connection + +The `robot-l4t` service (`robot/docker/docker-compose.yaml`) runs privileged with +`network_mode: host`, so the Jetson's serial devices are visible inside the container, +and it sets the MAVROS connection for you: `FCU_URL=${FCU_URL:-/dev/ttyTHS4:115200}` +and `TGT_SYSTEM=1`. If your FCU uses a different UART or baud rate, set `FCU_URL` in `.env` +(e.g. `FCU_URL=/dev/ttyTHS0:921600`) — a set `FCU_URL` environment variable is used directly by +`interface.launch.py` instead of deriving a simulation UDP URL (see [Robot Interface](../robot/autonomy/interface/index.md)). + +**Check:** `.env` reflects your serial device and baud rate if they differ from the default. MAVROS connectivity itself is verified in Step 6. + +## 5. Choose the stack (topology) + +The `l4t` profile defaults to the **`full_default`** stack — every autonomy module runs on +the Jetson, no ground station required. The alternative for compute-constrained vehicles is +the **`lite_offload_global`** split (lite modules onboard via `l4t_lite`, global planning on +a ground host via `offboard`) — see [Autonomy Modes](../robot/autonomy_modes.md). +**For a first flight, stay with `full_default`** — one machine, one container, nothing to bridge. + +**Check:** you have not set `AIRSTACK_STACK_DIR` or `--stack`, so the default applies. + +## 6. Bench test — PROPS OFF + +!!! danger "Propellers must be removed for this entire step" + +Power the FCU and bring the stack up on the Jetson, then verify in order: + +```bash +airstack --profile l4t up && airstack status # containers running +docker exec airstack-robot-l4t-1 bash -c 'echo "$ROBOT_NAME / $ROS_DOMAIN_ID"' # robot_1 / 1 +docker exec airstack-robot-l4t-1 bash -c "ros2 node list" # stack nodes present +docker exec airstack-robot-l4t-1 bash -c "ros2 topic echo /robot_1/interface/mavros/state --once" # connected: true +docker exec airstack-robot-l4t-1 bash -c "ros2 topic hz /robot_1/sensors/front_stereo/left/image_rect" # sensors ticking +``` + +The sensor topic above is the ZED front-stereo stream from the `zed-l4t` container — +substitute your own if your payload differs. Debug the bringup with `airstack logs robot-l4t` / +`airstack connect robot-l4t`. Finally, on a laptop on the same network, start the field GCS (`gcs-real`, host-networked): + +```bash +COMPOSE_PROFILES=deploy airstack up +``` + +**Check:** MAVROS reports `connected: true`, sensor topics tick at a steady rate, and the +robot appears in the GCS 3D view ([Operating the GCS](../gcs/usage/user_interface.md)). + +## 7. HITL rehearsal (recommended) + +Before flying, rehearse with the *real Jetson and real container* against a simulator: +[Hardware-In-The-Loop Simulation](HITL/index.md). HITL exercises the exact `robot-l4t` +image, identity resolution, networking, and GCS link you just configured — with zero flight risk. + +**Check:** the full Takeoff → Navigate → Land flow works end-to-end in HITL. + +## 8. First flight checks + +Only now, with every previous check green, install propellers. + +- **Safety monitor:** the `full_default` stack runs `drone_safety_monitor`, which watches + the state estimate and pauses the trajectory controller if it times out (it also accepts + `pause` / `resume` / `rewind` on its `command` topic). Confirm it is in `ros2 node list`. +- **Manual override:** confirm the safety pilot can take over and kill motors from the RC + transmitter at any time. *AirStack does not configure this — set up and test your FCU's + RC failsafe / kill switch per its documentation, and verify on your hardware.* +- **Telemetry:** GCS link live, odometry sane (a stationary drone shows a stationary pose), GPS fix acquired if you rely on it. +- **First command:** from the GCS Robot Tasks panel, send a **Takeoff** to a low altitude, + let it hover, then **Land**. Expand to Navigate missions only after a clean hover. + +## Congratulations + +Your robot has gone from a bench-top Jetson to an autonomous first flight. From here you can +grow the mission: waypoint routes and geofences from the GCS, split-stack topologies, and multi-robot fleets. + +## See Also + +- [Robot Identity](../robot/docker/robot_identity.md) — hostname → name/domain mapping in depth +- [Autonomy Modes](../robot/autonomy_modes.md) — profiles, stacks, and split topologies +- [HITL Testing](HITL/index.md) — the pre-flight rehearsal setup +- [Operating the GCS](../gcs/usage/user_interface.md) — commanding and monitoring from Foxglove +- [Data Offloading](data_offloading/index.md) — getting your flight data off the vehicle diff --git a/docs/real_world/supported_platforms.md b/docs/real_world/supported_platforms.md new file mode 100644 index 000000000..e734cc831 --- /dev/null +++ b/docs/real_world/supported_platforms.md @@ -0,0 +1,36 @@ +# Supported Platforms + +AirStack targets one compute platform per Docker Compose profile: each service in [`robot/docker/docker-compose.yaml`](https://github.com/castacks/AirStack/blob/main/robot/docker/docker-compose.yaml) extends the shared `robot_base` and pins the base image and platform toggles for its target. This page is the honest status matrix — what CI actually tests, what has been used in the field, and what merely exists as a profile. Deployment *topologies* (which autonomy stack runs where) are a separate axis, covered in [Autonomy Modes](../robot/autonomy_modes.md). + +## Platform Matrix + +| Platform | Compose profile → service(s) | Base image (from compose `build.args`) | Status | +| -------- | ---------------------------- | -------------------------------------- | ------ | +| x86-64 desktop/laptop (development + simulation) | `desktop` → `robot-desktop` (+ `gcs`) | `nvidia/cuda:13.0.2-base-ubuntu24.04` | **CI-tested (sim)**: `system-tests.yml` runs build, liveliness, sensor, and flight marks against this profile on ephemeral x86 GPU runners; `test_build_docker.py` builds the `robot-desktop`, `gcs`, `isaac-sim`, and `ms-airsim` images | +| x86-64 desktop, split-topology debugging | `desktop_split` → `robot-desktop-onboard` + `robot-offboard` (+ `gcs`) | Same image as `robot-desktop` | Profile exists; shares the CI-tested desktop image, but the split topology itself is not CI-exercised | +| x86-64 ground station (field, offboard half) | `offboard` → `robot-offboard` + `gcs-real` | Same image as `robot-desktop`; GCS from `osrf/ros:jazzy-desktop-full` | Profile exists for field use paired with `l4t_lite`/`voxl` vehicles; not CI-exercised | +| NVIDIA Jetson Orin AGX/NX (full stack onboard) | `l4t` → `robot-l4t` (+ `robot-l4t-stack-base`, `zed-l4t`) | `dustynv/ros:jazzy-ros-base-r36.4.0-cu128-24.04` via the intermediate `Dockerfile.l4t-stack-base` image | **Supported, field-used**: the [installation guide](installation/index.md) documents tested install and run on Jetson Orin AGX/NX with Ubuntu 22.04 (L4T / JetPack); not covered by CI | +| NVIDIA Jetson Orin, lite (global planning offloaded) | `l4t_lite` → `robot-l4t-onboard` | Same image as `robot-l4t` | Same support level as `l4t`; defaults to the `lite_default` stack | +| ModalAI VOXL 2 | `voxl` (alias `voxl_onboard`) → `robot-voxl-onboard` | `ubuntu:24.04` (aarch64, `REAL_ROBOT=true`, no CUDA) | Profile exists; docs in progress (per [About](../about.md#supported-platforms)); defaults to the `lite_default` stack (compute-constrained); do NOT assume test coverage | + +Companion services on the same profiles: `zed-l4t` (profile `l4t`, base `dustynv/ros:jazzy-desktop-r36.4.0-cu128-24.04`) runs only the ZED stereo camera driver next to `robot-l4t`; `simple-robot` (profile `simple`) and `robot-test` (profile `test`) are desktop-image variants for the lightweight simulator and colcon-test runs, not separate platforms. + +## OS / JetPack Requirements + +Only requirements actually stated by a doc or Dockerfile in this repo: + +| Platform | Stated requirement | Source | +| -------- | ------------------ | ------ | +| Development machine | Ubuntu 22.04 or 24.04, NVIDIA GPU (RTX 3070 minimum, RTX 4080+ recommended), 16GB+ RAM, ~100GB free storage | [About — FAQ](../about.md#faq) | +| Jetson Orin AGX/NX | Ubuntu 22.04 (L4T / JetPack); container stack pinned to L4T r36.4.0 | [Installation guide](installation/index.md); `r36.4.0` image tags in `robot/docker/docker-compose.yaml` | + +## What CI Does and Does Not Cover + +GPU simulation jobs (`system-tests.yml`) run on `[self-hosted, airstack-ephemeral]` x86 runners — every CI-verified result is the **desktop x86 simulation path only**. No CI job builds or runs the `l4t`, `l4t_lite`, or `voxl` images; their status above comes from the installation docs and profile definitions, not automated testing. + +## See Also + +- [Installation on Orin AGX/NX](installation/index.md) — the hardware install walkthrough +- [Autonomy Modes](../robot/autonomy_modes.md) — deployment topologies (which stack runs on which machine, per profile) +- [Docker Build Profiles](../development/intermediate/docker-build-profiles.md) — how compose build args map to image variants (including the L4T/Jetson build chain) +- [Docker Services](../robot/docker/index.md) — the full service hierarchy diff --git a/docs/release_notes/index.md b/docs/release_notes/index.md index c01e68a07..ad81d671c 100644 --- a/docs/release_notes/index.md +++ b/docs/release_notes/index.md @@ -19,6 +19,75 @@ its own notes. --> ## 0.20.0 (Unreleased) +**Documentation overhaul (Diátaxis restructuring).** The docs site was +audited against the [Diátaxis](https://diataxis.fr) framework and +reorganized; page URLs are preserved (moves are covered by redirects): + +- The nav is now organized by document kind — **Tutorials / How-to Guides / + Reference / Concepts** tabs — replacing the difficulty-tier + ("Beginner/Intermediate/Advanced Tutorials") buckets, which contained no + tutorials. The doc-authoring standards (Documentation Guide, mkdocs + skills) now prescribe the quadrant taxonomy and include a decision tree. +- 18 verified doc/code mismatches fixed, including: a phantom + `--recreate` flag and missing `--scene` in the CLI reference; phantom + `ROBOT_LAUNCH_PACKAGE`/`ROBOT_LAUNCH_FILE` env vars (real: `LAUNCH_PACKAGE`); + nonexistent `airstack_msgs/TrajectorySegment`/`TrajectoryOverride` types in + doc templates (real: `TrajectoryXYZVYaw`); the MS-AirSim tmux window + ordering (bridges launch before PX4) and MAVLink port math; the Getting + Started Foxglove step (layout now auto-seeds; manual import retired). +- Removed superseded pages with redirects: the Ascent-era scene-setup pair, + the pre-harness testing-frameworks page, the orphaned tutorials index and + two stale duplicate pages (development_environment, airstack-cli index). + The git-hooks docker-versioning READMEs are now deprecation notices — the + hook they described conflicts with the semver `check-version-increment` + gate (note: `airstack config git-hooks` still installs it; CLI removal is + a follow-up). +- Pages that documented never-built or fabricated behavior were rewritten + from the code: global planning (the unimplemented Global-Manager/ + PlanRequest protocol is gone), the robot interface page (state flows via + `odometry_conversion`), robot configuration, HITL (now uses the + `gcs-real` `hitl` profile and Foxglove verification), rosbags, and the + robot-side data-offloading page (now points at the storage-tools + workflow). +- Hybrid pages split by audience: `system_architecture.md` (explanation + core; drifted topic tables replaced with links into the interface + conventions spec), CI/CD (new **Using CI** how-to), GCS Foxglove (new + **Extending the Visualizer**), Isaac Sim docker (new **Container + Workflows**). Duplicated hot tables (topics, CLI flags, pytest marks, + requirements) now live in one canonical home each. +- Previously off-site references added to the nav: the `vehicle.yaml` and + `module.yaml` schemas, local calibration contract, the RViz tasks/waypoint + panel manuals, the LiDAR point-cloud filter README, and the OSMO lab-admin + guide. New reference pages: the complete `.env` schema, the + `airstack_msgs` interface reference, the trajectory-library YAML format, + and a supported-platform matrix. New/rewritten onboarding: Deploying to + Hardware and Operating the GCS. +- The Interface Conventions Specification was bumped to **v1.0.1**: §8 now + lists all eight `task_msgs` actions (added `tasks/coverage` and + `tasks/chat`, both defined with no shipped executor). +- Six new how-to guides: Adding a State Estimator, Adding a Planner, + Creating a Multi-Agent Coordination Algorithm (grouped under a new + How-to → Autonomy section), Creating a Custom Stack Topology, Adding a + Vehicle Type/Unit/Platform, and Getting the Most out of Your Coding + Agent (the feature-notebook workflow). Each presents the in-tree + package vs `airstack module create --in-tree` module-scaffolding + choice. The Concepts tab now sits directly after Tutorials, and the + UE→Isaac export tutorial was refreshed (new walkthrough video, export + as Z-up in meters, note that UE Decals — paint markings, dirt, + puddles — do not export). +- Five new beginner tutorials completing the learning path: Fly a Mission + from the GCS, Change a Parameter (the edit→relaunch loop; config YAML is + symlink-installed from the bind-mounted source, so no rebuild), Write + Your First Module (`airstack module create --in-tree` scaffold, with a + fix-it note for the scaffold's double-namespace stub), Your First Fleet + (two-robot fleet file, per-robot Foxglove tabs), and Build and Fly Your + Own Scene (GUI stage → scene catalog → `--scene` flight → baked + `*.scene.usd`). New how-to: Adding a Controller (verified + trajectory_controller → pid_controller → interface command chain); + Adding a Planner expanded into Adding a World Model and Planner + (local world-model/planner matched pairs vs the spec'd global map + interchange). + This release restructures AirStack from a monolith into **modules**, **stacks**, and **fleets**, implementing [RFC #379 (Modular AirStack)](https://github.com/castacks/AirStack/discussions/379) diff --git a/docs/robot/autonomy/adding_a_controller.md b/docs/robot/autonomy/adding_a_controller.md new file mode 100644 index 000000000..b23625ff8 --- /dev/null +++ b/docs/robot/autonomy/adding_a_controller.md @@ -0,0 +1,83 @@ +# Adding a Controller + +AirStack splits control into two roles ([Controls overview](local/controls/index.md)), and the first decision is which one you are replacing: + +- **Trajectory controller** — a pure-pursuit trajectory *manager*, not itself a feedback controller. It **owns the [trajectory group (spec §5)](interface_conventions.md#5-trajectory-group-the-trajectory-controllers-contract-onboard-only)**: it consumes `trajectory_controller/trajectory_segment_to_add` and `trajectory_override` (`airstack_msgs/msg/TrajectoryXYZVYaw`), serves the `set_trajectory_mode` service, and emits `tracking_point` and `look_ahead` (`airstack_msgs/msg/Odometry` — not `nav_msgs`). Reference: [Trajectory Controller](../../../robot/ros_ws/src/local/controls/trajectory_controller/README.md). +- **Feedback controller** — closes the loop between the tracking point and the vehicle's actual state and emits the [`control_setpoint` (spec §6)](interface_conventions.md#6-control_setpoint-controller-interface-command-onboard-only) command into the interface. Reference: `pid_controller` (`robot/ros_ws/src/local/controls/pid_controller` — no README; the cascaded position→velocity PID is described in the [Trajectory Controller README's Control Architecture section](../../../robot/ros_ws/src/local/controls/trajectory_controller/README.md#control-architecture)). + +The verified chain in every reference stack (`full_default`, `full_droan_cpu`, `full_macvo`, `lite_default`, `lite_offload_global` onboard) is: + +```text +trajectory_controller/tracking_point (airstack_msgs/Odometry, §5) + │ + odometry_conversion/odometry (§2) + ▼ +control/pid_controller ──► interface/cmd_roll_pitch_yawrate_thrust + (mav_msgs/RollPitchYawrateThrust, §6) ──► robot_interface → MAVROS/PX4 +``` + +**Both roles are onboard-only.** Spec §5 and §6 names may never appear in a split stack's `bridge.yaml` — `airstack doctor` hard-errors on it. The rationale is the spec's safety floor: command authority flows through the trajectory controller (arming, safety monitoring, takeover come for free to anything publishing `trajectory_override`), and that floor collapses if control crosses a link that can drop. A controller can never run offboard. + +This guide assumes you know the [layered architecture](index.md) and have flown a stack in sim. Link the [Interface Conventions Specification](interface_conventions.md) from your README instead of restating its tables. + +## Package or module? + +Decide early where the controller lives: + +- **In-tree package** — a package under `robot/ros_ws/src/local/controls/`, or a scaffolded module boundary in your fork via `airstack module create --in-tree `. +- **Module repo** — shareable, version-pinned, own CI and Docker dependency layer, added with `airstack module add --version `. See [AirStack Modules](../../development/modules.md) and the [create-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md). + +The wiring steps below are identical either way. + +## Path A: replace the feedback controller (the common case) + +Swap `pid_controller` for your own attitude/velocity controller. The §5 surface stays owned by the stock trajectory controller — you only consume its setpoint. + +Conventions worth copying from the reference (all in `pid_controller.cpp` / its launch file): + +- Runs as node `pid_controller` under the `control` namespace (node path `/robot_1/control/pid_controller`); gains load from a params YAML passed with `allow_substs="true"`. +- Gains are **dynamic parameters** (`airstack::dynamic_param` from `airstack_common`) — per-axis `p/i/d/ff/min/max/constant` plus a `_d_alpha` derivative filter — tunable at runtime with `ros2 param set`, no rebuild between tuning iterations. +- It exposes a `reset_integrators` subscription (`std_msgs/msg/Empty`, relative name in its namespace) so flight phases can clear integral windup; keep an equivalent if your controller integrates. +- It is control-rate agnostic: it computes on every `tracking_point` message (the trajectory controller ticks at 20 Hz) rather than running its own timer. + +### 1. Create the package + +Follow the [add-ros2-package skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-ros2-package/SKILL.md) and the [Module Integration Checklist](integration_checklist.md), under `robot/ros_ws/src/local/controls/`. Declare every topic endpoint as a launch argument defaulting to its canonical spec name — copy the pattern from `pid_controller/launch/pid_controller.launch.xml` (`pid_controller_odometry_topic`, `pid_controller_tracking_point_topic`, `pid_controller_command_topic`). + +**Verify:** `docker exec airstack-robot-desktop-1 bash -c "bws --packages-select "` exits cleanly. + +### 2. Conform to the interchange + +Inputs: `trajectory_controller/tracking_point` (§5, `airstack_msgs/msg/Odometry` — pose, velocity, acceleration, jerk along the trajectory) and `odometry_conversion/odometry` ([§2](interface_conventions.md#2-odometry-primary-state-estimate), `nav_msgs/msg/Odometry`). Output: one §6 command dialect — `interface/cmd_roll_pitch_yawrate_thrust` (`mav_msgs/msg/RollPitchYawrateThrust`, the blessed publisher slot the PID fills today) or the alternates `interface/cmd_pose` / `interface/cmd_velocity`. Never publish `tracking_point` or `look_ahead` yourself — that is impersonating the trajectory controller, and `doctor --live` flags it. + +**Verify:** with the node running under a full stack, `ros2 topic info /robot_1/interface/cmd_roll_pitch_yawrate_thrust` lists your node as the only publisher, and `ros2 topic info /robot_1/trajectory_controller/tracking_point` lists it as a subscriber. + +### 3. Wire it into a custom stack + +Controller variants are named stacks (see [Creating a Custom Stack Topology](../../development/creating_a_stack.md) and the [single-locus rule](../../development/stacks.md#the-single-locus-rule-and-its-lint)): `airstack stack new full_default full_my_controller`, then in `stacks/full_my_controller/launch/stack.launch.xml` replace the `pid_controller.launch.xml` include with your controller's include. Canonical arg defaults mean a conforming controller needs no include args. + +**Verify:** `airstack up --stack full_my_controller --sim isaac --robots 1 && airstack ready` succeeds and `ros2 node list` shows your controller in place of `control/pid_controller`. + +### 4. Verify with wiring and a flight + +1. `airstack test -m wiring --stack full_my_controller` regenerates `wiring.md`; `airstack stack diff full_default full_my_controller` should show exactly the controller swap. `airstack doctor --live --stack full_my_controller` must be clean. +2. Fly it: `airstack test -m takeoff_hover_land --sim isaacsim --num-robots 1 -v` — every command the vehicle receives in all four phases flows through your feedback controller, so this is the cheapest full-chain exercise (`waypoint_flight` adds planner behavior, not controller coverage). Then `airstack test -m autonomy --trajectory-types Circle,Figure8` for tracking *quality*: it flies fixed trajectories straight through the controllers and records cross-track error and path RMSE — the numbers your gains actually move. + +## Path B: replace the trajectory controller itself + +This is a much bigger lift: you take over the **entire §5 surface**, and every task server, the local planner, the feedback controller, and the safety monitor are your clients. Study the [Trajectory Controller README](../../../robot/ros_ws/src/local/controls/trajectory_controller/README.md) end-to-end before writing code. Your replacement must: + +- **Consume** `trajectory_controller/trajectory_segment_to_add` (appended segments from the local planner, stitched into the live trajectory near the current tracking position) and `trajectory_override` (complete replacement trajectories from takeoff/land and fixed-trajectory task servers). +- **Serve** `trajectory_controller/set_trajectory_mode` (`airstack_msgs/srv/TrajectoryMode`) with all five modes — `ROBOT_POSE`, `TRACK`, `ADD_SEGMENT`, `PAUSE`, `REWIND` — including the transition semantics in the README. Clients include `takeoff_landing_planner`, the fixed-trajectory task, the local planner, and `drone_safety_monitor`. +- **Publish** `tracking_point` and `look_ahead` (`airstack_msgs/msg/Odometry`), keeping `look_ahead` far enough ahead for the planner's cycle, plus `trajectory_completion_percentage` (`std_msgs/msg/Float32`), which task servers use to judge goal completion. +- **Broadcast** the four TF frames (`tracking_point`, `look_ahead_point`, and their `_stabilized` variants) the README documents. +- **Honor the safety integration**: the safety monitor commands `PAUSE`/`REWIND` through your mode service on state-estimate timeout — this path is why §5 is a spec, and it must work before anything else does. + +Wire it the same way as Path A step 3 (replace the `trajectory_controller.launch.xml` include; the `fixed_trajectory_task.launch.xml` include comes from the same package — replace or keep it deliberately), then verify as in Path A step 4 — but fly with `airstack test -m waypoint_flight --sim isaacsim --num-robots 1 -v`: its chain (takeoff → NavigateTask route → land) exercises the whole surface — `TRACK`/override for takeoff and landing, `ADD_SEGMENT` stitching under a continuously replanning local planner, and the mode transitions between them — where `takeoff_hover_land` never enters `ADD_SEGMENT`. + +## See also + +- [Interface Conventions Specification](interface_conventions.md) — §2 odometry, §5 trajectory group, §6 control_setpoint +- [Controls overview](local/controls/index.md) · [Trajectory Controller README](../../../robot/ros_ws/src/local/controls/trajectory_controller/README.md) +- [Creating a Custom Stack Topology](../../development/creating_a_stack.md) · [AirStack Stacks](../../development/stacks.md) +- [Module Integration Checklist](integration_checklist.md) +- Skills: [add-ros2-package](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-ros2-package/SKILL.md) · [create-module](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md) diff --git a/docs/robot/autonomy/adding_a_world_model_and_planner.md b/docs/robot/autonomy/adding_a_world_model_and_planner.md new file mode 100644 index 000000000..4ad0325c5 --- /dev/null +++ b/docs/robot/autonomy/adding_a_world_model_and_planner.md @@ -0,0 +1,118 @@ +# Adding a World Model and Planner + +AirStack has two planner slots — and behind each, a world-model slot. The first decision is which one you are filling: a local planner (Path A), a global planner (Path B), or a world model paired with a planner (Path C): + +- **Local planner** — a perpetual node plus a `NavigateTask` server that consumes the [`global_plan` (spec §4)](interface_conventions.md#4-global_plan-global-waypoint-path), a world model input (disparity or point clouds), and the trajectory controller's `look_ahead`/`tracking_point`, and emits short collision-free segments on the [trajectory-controller surface (spec §5)](interface_conventions.md#5-trajectory-group-the-trajectory-controllers-contract-onboard-only) (`trajectory_controller/trajectory_segment_to_add`, `airstack_msgs/msg/TrajectoryXYZVYaw`). Reference: [DROAN](../../../robot/ros_ws/src/local/planners/droan_local_planner/README.md) ([overview](local/planning/index.md)). Spec §5 is **onboard-only** — a local planner can never run offboard. +- **Global planner** — a [task executor](tasks.md): an action server at `tasks/` ([spec §8](interface_conventions.md#8-tasks-task-action-servers)) that plans only while a goal is active, publishes the coarse path on [`global_plan` (spec §4)](interface_conventions.md#4-global_plan-global-waypoint-path), and delegates flying to the local planner via `tasks/navigate`. Reference: [Random Walk](../../../robot/ros_ws/src/global/planners/random_walk/README.md) ([overview](global/planning/index.md)). `global_plan` is the one interchange that may cross a machine boundary, so a global planner may run offboard (`lite_offload_global`). + +- **World model** — the representation a planner plans against. This is *two different contracts* — read the next section before picking a lane. + +## World models: a matched pair locally, a spec'd interchange globally + +**Local world models** feed the local planner a fast short-range obstacle representation. The reference is the disparity pipeline ([overview](local/world_model/index.md)): [disparity_expansion](../../../robot/ros_ws/src/local/world_models/disparity_expansion/README.md) (C-space expansion of stereo disparity by the robot radius) → [disparity_graph](../../../robot/ros_ws/src/local/world_models/disparity_graph/README.md) (rolling window of expanded-disparity keyframes with camera poses) → [disparity_graph_cost_map](../../../robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md) (a `cost_map_interface` plugin the CPU DROAN planner loads via its `cost_map` parameter to score candidate trajectories). The GPU planner `droan_gl` does the expansion and graph internally on the GPU and consumes raw disparity directly. + +**Be honest about the local contract: there isn't a spec-level one.** Unlike `global_plan` or the trajectory group, the local world-model ↔ planner interface is **not** an interchange in the [Interface Conventions Specification](interface_conventions.md) — a local planner and its world model are a **matched pair**, wired together in the stack entry. Compare the reference stacks: `full_default` includes `droan_gl` alone (disparity in from `perception/stereo_image_proc/disparity`, world model internal), while `full_droan_cpu` includes `droan_local_planner` **plus** `disparity_expansion`, the planner consuming the expansion clouds by relative name in the shared `droan` namespace. Adding a new local world model therefore usually means adapting a planner to consume it (e.g. implementing the `cost_map_interface` plugin API) or bringing a paired planner with it. + +**Global world models** *are* spec'd: [`global_map` (spec §3)](interface_conventions.md#3-global_map-global-world-model) — today the [vdb_mapping_ros2](../../../robot/ros_ws/src/global/world_models/vdb_mapping_ros2/README.md) topics (`vdb_mapping/vdb_map_visualization` is the de-facto interchange the reference global planner consumes, plus the update-grid and point-cloud exports). A new global world model that produces the §3 surface drops in for the global planner without touching it. + +This guide assumes you know the [layered architecture](index.md) and have flown a stack in sim. Link the [Interface Conventions Specification](interface_conventions.md) from your README instead of restating its tables. + +## Package or module? + +Decide early where the new code lives: + +- **In-tree package** — fastest for trunk work: a package under `robot/ros_ws/src/local/planners/`, `robot/ros_ws/src/global/planners/`, or the matching `world_models/` directory, or a scaffolded module boundary in your fork via `airstack module create --in-tree ` (lands under `robot/ros_ws/src/modules/`). +- **Module repo** — shareable, version-pinned, own CI and Docker dependency layer, added with `airstack module add --version `. See [AirStack Modules](../../development/modules.md) (the [researcher fork → module workflow](../../development/modules.md#the-researcher-workflow-fork-module)) and the [create-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md); [asm_macvo](../../modules/macvo.md) is the worked precedent for a capability shipped this way. + +The wiring steps below are identical either way — a module's launch file is included by a stack entry file exactly like a trunk package's. + +## Path A: local planner + +### 1. Create the package + +Follow the [add-ros2-package skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-ros2-package/SKILL.md) and the [Module Integration Checklist](integration_checklist.md), under `robot/ros_ws/src/local/planners/`. Declare every topic endpoint as a launch argument defaulting to its canonical spec name — a conventional stack then includes you with zero remaps. + +**Verify:** `docker exec airstack-robot-desktop-1 bash -c "bws --packages-select "` exits cleanly. + +### 2. Conform to the interchange + +Inputs: `global_plan` (§4, `nav_msgs/Path`, `map` frame), your world model topic, `odometry_conversion/odometry` ([§2](interface_conventions.md#2-odometry-primary-state-estimate)), and the controller's `look_ahead` (§5 — plan from the look-ahead point, not the current pose). Output: `trajectory_controller/trajectory_segment_to_add` (§5). Serve `NavigateTask` at `tasks/navigate` (goal/feedback/result fields in [Task Executors → NavigateTask](tasks.md#navigatetask)); the [add-task-executor skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-task-executor/SKILL.md) covers the four action callbacks. Emitting `trajectory_segment_to_add` (rather than commanding the interface directly) is what buys you arming, safety monitoring, and takeover for free — the spec's safety floor. For candidate-trajectory generation, scoring helpers, and `TrajectoryXYZVYaw` conversion, use the [trajectory_library](../../../robot/ros_ws/src/local/planners/trajectory_library/README.md) instead of rolling your own. + +**Verify:** with the node running under a full stack, `docker exec airstack-robot-desktop-1 bash -c "sws && ros2 topic info /robot_1/trajectory_controller/trajectory_segment_to_add"` lists your node as a publisher and the trajectory controller as the subscriber, and `ros2 action list` shows `/robot_1/tasks/navigate`. + +### 3. Wire it into a stack + +Planner variants are named stacks, not launch arguments — the [single-locus rule](../../development/stacks.md#the-single-locus-rule-and-its-lint) puts every wiring deviation in the stack entry file. The worked swap example is [full_droan_cpu](../../../stacks/full_droan_cpu/README.md): byte-identical to `full_default` except the local-planner block, where `stacks/full_droan_cpu/launch/stack.launch.xml` replaces the single `droan_gl.launch.xml` include with two lines: + +```xml + + +``` + +Do the same for yours: `airstack stack new full_default full_my_planner`, then in `stacks/full_my_planner/launch/stack.launch.xml` replace the DROAN include with your planner's include (plus any world-model include it needs). Only deviations from canonical names appear as include args — see how `full_macvo` passes exactly one (`droan_gl_disparity_topic`). + +**Verify:** `airstack up --stack full_my_planner --sim isaac --robots 1 && airstack ready` succeeds and `ros2 node list` shows your planner in place of DROAN. + +### 4. Verify with wiring and a flight + +1. Snapshot and diff the wiring: `airstack test -m wiring --stack full_my_planner` regenerates `stacks/full_my_planner/wiring.md`; the diff against `full_default` should be exactly your planner block. `airstack stack diff full_default full_my_planner` compares the generated wiring directly. +2. Live check: `airstack doctor --live --stack full_my_planner` — doctor flags anything but the trajectory controller publishing `look_ahead`/`tracking_point`, and hard-errors if §5 names ever appear in a split stack's `bridge.yaml`. +3. Fly it: `airstack test -m waypoint_flight --sim isaacsim --num-robots 1 --stress-iterations 1 -v` drives a waypoint route through your `tasks/navigate` server and judges the odometry track. For a manual flight, take off from the GCS and send a `NavigateTask` goal (`ros2 action send_goal --feedback /robot_1/tasks/navigate task_msgs/action/NavigateTask ...` with a `global_plan` path and `goal_tolerance_m`), or use the [GCS waypoint editor](../../gcs/waypoints_and_geofences.md). (`airstack test -m autonomy` flies fixed trajectories straight through the controller — it checks the stack still flies, but never touches your planner.) + +## Path B: global planner + +### 1. Create the package + +Same as Path A step 1, under `robot/ros_ws/src/global/planners/`. Keep planning logic in a ROS-free class ([Global Planning](global/planning/index.md) explains why); the node wraps it. + +**Verify:** `bws --packages-select ` exits cleanly. + +### 2. Implement it as a task executor + +Follow [Adding a New Task Executor](tasks.md#adding-a-new-task-executor) and the [add-task-executor skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-task-executor/SKILL.md): pick or add a `.action` type in `task_msgs`, implement the four action callbacks, and remap the server to `tasks/` in your module launch file (§8). Subscribe to the map ([`global_map`, spec §3](interface_conventions.md#3-global_map-global-world-model) — today the VDB visualization topic) and odometry (§2); publish `global_plan` (§4, `nav_msgs/Path` in the `map` frame, last pose = goal) and delegate flying by sending `NavigateTask` goals to `tasks/navigate` while your goal is active — exactly the [random_walk cascade](../../../robot/ros_ws/src/global/planners/random_walk/README.md). + +**Verify:** `ros2 action list` shows `/robot_1/tasks/`, and `ros2 topic info /robot_1/global_plan` lists your node as publisher and the local planner as subscriber. + +### 3. Wire it into a stack + +Swapping the global planner is replacing one include: in your copy of the stack entry file (`airstack stack new full_default full_my_global`), replace the `random_walk_planner.launch.xml` include with yours — the same one-line swap the [exploration planner](global/planning/index.md#example-planners) documents. The node keeps its canonical names, so no include args are needed unless you deviate. + +**Verify:** `airstack up --stack full_my_global --sim isaac --robots 1 && airstack ready`; `ros2 node list` shows your planner and no `random_walk_node`. + +### 4. Verify with wiring and a flight + +1. `airstack test -m wiring --stack full_my_global` then `airstack stack diff full_default full_my_global` — the only delta is the global-planner swap; `airstack doctor --live --stack full_my_global` is clean. +2. Fly it: take off, then activate your task, e.g. for an exploration-type planner `ros2 action send_goal /robot_1/tasks/exploration task_msgs/action/ExplorationTask '{...}' --feedback` (a full goal example is in [Task Executors](tasks.md#explorationtask)), and watch `ros2 topic echo /robot_1/global_plan --once` update and the drone follow it. `airstack test -m takeoff_hover_land` confirms you have not disturbed the base flight chain. + +## Path C: new world model + +### 1. Create the package + +Same as Path A step 1, under `robot/ros_ws/src/local/world_models/` or `robot/ros_ws/src/global/world_models/`. + +**Verify:** `bws --packages-select ` exits cleanly. + +### 2. Produce the right surface + +- **Global:** produce the [`global_map` (spec §3)](interface_conventions.md#3-global_map-global-world-model) surface — at minimum the visualization-topic interchange the reference global planner consumes today — from your sensor input (the VDB map takes the filtered LiDAR cloud). Conform and the existing global planner needs no changes. +- **Local:** there is no spec surface to hit — produce the representation your **paired planner** consumes, and treat the disparity pipeline as the worked example of the pairing: `disparity_expansion` publishes expansion clouds the CPU DROAN planner reads by relative name, while the graph + cost map reach the planner as a `cost_map_interface` plugin selected by its `cost_map` parameter. If you keep DROAN, implementing that plugin API is the smallest integration; a different planner means adapting it to your representation (or writing one — Path A). + +**Verify (local):** with the pair running, `ros2 topic info` on your world-model output lists the planner as a subscriber (or the planner logs loading your cost-map plugin). **Verify (global):** `ros2 topic info /robot_1/vdb_mapping/vdb_map_visualization`-equivalent shows your node as publisher and the global planner as subscriber. + +### 3. Wire the pair into a stack + +A local world model and its planner are swapped **together** — [full_droan_cpu](../../../stacks/full_droan_cpu/README.md) is the template, replacing `full_default`'s single `droan_gl` include with the `droan_local_planner` + `disparity_expansion` pair (the two-line swap shown in Path A step 3). Do the same: `airstack stack new full_default full_my_wm`, then swap in your world-model include plus its paired planner's include in `stacks/full_my_wm/launch/stack.launch.xml`. For a global world model, replace the `vdb_mapping_ros2` include (and its `config` arg) with yours. + +**Verify:** `airstack up --stack full_my_wm --sim isaac --robots 1 && airstack ready`; `ros2 node list` shows the new pair. + +### 4. Verify with wiring and a flight + +Same as Path A step 4: `airstack test -m wiring --stack full_my_wm`, `airstack stack diff full_default full_my_wm` (the delta is exactly the pair swap), `airstack doctor --live --stack full_my_wm`, then `airstack test -m waypoint_flight` — a waypoint route through obstacles is what actually consults the world model. For a global world model, also confirm `ros2 topic echo /robot_1/global_plan --once` updates while an exploration-type task runs against your map. + +## See also + +- [Interface Conventions Specification](interface_conventions.md) — §2 odometry, §3 global_map, §4 global_plan, §5 trajectory group, §8 tasks +- [Module Integration Checklist](integration_checklist.md) — package structure, launch conventions, integration testing commands +- [Local Planning](local/planning/index.md) · [Global Planning](global/planning/index.md) · [Local World Model](local/world_model/index.md) — layer overviews and references +- [Task Executors](tasks.md) — action types and the task cascade +- Skills: [add-ros2-package](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-ros2-package/SKILL.md) · [add-task-executor](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-task-executor/SKILL.md) · [create-module](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md) diff --git a/docs/robot/autonomy/behavior/index.md b/docs/robot/autonomy/behavior/index.md index 170d40b04..eae6be9c1 100644 --- a/docs/robot/autonomy/behavior/index.md +++ b/docs/robot/autonomy/behavior/index.md @@ -1,19 +1,21 @@ # Behavior -The behavior module is responsible for the high-level decision making of the robot. This includes deciding what actions to take based on the current state of the robot and the world around it. The behavior module is responsible for coordinating the actions of the local and global modules to achieve the robot's goals. + +The behavior layer, as shipped, is a **safety executive**: it watches the health of the running stack and issues safety commands when something goes wrong. High-level mission sequencing does not live here — task goals (takeoff, land, explore, navigate) are sent by the operator from the GCS to the [task executors](../tasks.md) hosted in the global and local layers; see [System Architecture — Task Cascade](../system_architecture.md#task-cascade). ## Launch + Behavior modules ship their own canonical launch files and are composed by -the stack entry file, e.g. +the selected stack's entry launch 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 +- **`drone_safety_monitor`** (`robot/ros_ws/src/behavior/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). +## Key Interchanges +- [`safety` (§9)](../interface_conventions.md#9-safety-safety-executive-onboard-only) — the safety executive's onboard-only topics (`state_estimate_timed_out`, `command`); these may never cross a split-stack bridge +- [`tasks/*` (§8)](../interface_conventions.md#8-tasks-task-action-servers) — where mission-level goals actually enter the stack diff --git a/docs/robot/autonomy/coordination/creating_coordination_algorithms.md b/docs/robot/autonomy/coordination/creating_coordination_algorithms.md new file mode 100644 index 000000000..90f473849 --- /dev/null +++ b/docs/robot/autonomy/coordination/creating_coordination_algorithms.md @@ -0,0 +1,78 @@ +# Creating a Multi-Agent Coordination Algorithm + +Each robot runs on its own DDS domain ([robot N gets `ROS_DOMAIN_ID=N`](../../docker/robot_identity.md)), so robots cannot see each other's topics directly — a node on `robot_1` will never discover `/robot_2/odometry`. The supported cross-robot channel is the [coordination layer](index.md): each robot's `gossip_node` broadcasts a `PeerProfile` (GPS, heading, current waypoint, plus arbitrary payloads) on the shared gossip domain (default **99**), bridged from the robot's own domain by a dedicated DDS router. A coordination algorithm in AirStack is therefore: attach your state as a gossip payload, consume the peer registry, decide, and act through the stack's normal interfaces. + +Canonical names, types, and QoS for the gossip channel are in the [Interface Conventions Spec §10](../interface_conventions.md#10-gossip--multi-robot-coordination). + +## Step 1 — Decide what state must cross robots, and attach it as a payload + +The `PeerProfile` already carries GPS position, heading, and the current waypoint for free — many coordination schemes (spatial dispersion, follow-the-leader, deconfliction) need nothing more. If your algorithm needs additional per-robot state (a frontier map, a task bid, a mode string), publish it as a normal topic on the robot's own domain and declare it in `gossip_payloads.yaml` — the gossip node serializes the latest message onto every 1 Hz profile tick, transforming `MarkerArray`/`PointCloud2` payloads into global ENU on the way out. The full recipe is [Payloads & Foxglove Visualization](payloads.md) (or run the [attach-gossip-payload skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/attach-gossip-payload/SKILL.md)); don't re-derive it here. Keep payloads small — they ride inside every profile message at the gossip rate. + +**Verify:** `ros2 topic echo /gossip/peers --field payloads` shows an entry with your `payload_type` string. + +## Step 2 — Write the coordination node that consumes peer profiles + +Your node runs on the robot's **own** domain, like every other stack node. Two verified inputs: + +- **`/{robot_name}/coordination/peer_registry`** (`coordination_msgs/msg/PeerProfile`, RELIABLE + TRANSIENT_LOCAL) — the gossip node's latest-wins snapshot of every known peer, republished on the robot's domain each time a peer updates. TRANSIENT_LOCAL means a late-joining node receives the current registry immediately. This is the right input for in-stack consumers: no domain gymnastics, dedup and monotonic-timestamp filtering already done by `gossip_node`. +- **`/gossip/peers`** (same type, BEST_EFFORT) — the raw bus, but only visible on domain 99. Use it for out-of-stack tooling (this is what `peer_registry_monitor` subscribes to), not for stack nodes. + +Deserialize payloads with the helper API from `common/ros_packages/coordination/coordination_bringup/coordination_bringup/peer_profile.py`: + +```python +from coordination_msgs.msg import PeerProfile as PeerProfileMsg +from coordination_bringup.peer_profile import PeerProfile + +def on_peer(self, msg: PeerProfileMsg): + profile = PeerProfile.from_ros_msg(msg) + bid = profile.get_payload("std_msgs/msg/String") # by type + cloud = profile.get_payload_by_name("raw_frontiers") # by topic tail +``` + +For outputs, use the stack's existing verified command mechanisms rather than inventing a side channel: publish a `global_plan` (`nav_msgs/msg/Path` — [the interchange](../interface_conventions.md#4-global_plan--global-waypoint-path) the local planner consumes), or call a task action server under `tasks/*` (e.g. `tasks/navigate` / `task_msgs/action/NavigateTask`, [§8](../interface_conventions.md#8-tasks--task-action-servers)). To share your decision back to peers, attach it as another payload (Step 1). + +**Package or module?** The node can live in-tree as a normal ROS 2 package ([add-ros2-package skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-ros2-package/SKILL.md)), or ship as a standalone module repo pinned by whichever stacks use it — the same pattern as `asm_macvo`. See [AirStack Modules](../../../development/modules.md) and the [create-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md); `airstack module create --in-tree ` scaffolds the module boundary in a fork. + +**Verify:** with the stack running, `docker exec airstack-robot-desktop-1 bash -c "ros2 topic echo /robot_1/coordination/peer_registry --once"` returns a `PeerProfile`, and your node appears in `ros2 node list`. + +## Step 3 — Wire it into a stack + +The gossip layer itself is already included in every reference stack — each entry launch file includes `coordination_bringup`'s `gossip.launch.xml` (e.g. `stacks/full_default/launch/stack.launch.xml`, "Gossip coordination layer" block, passing `gossip_domain`/`gossip_publish_rate`). You do **not** add anything to make peer profiles flow. + +Your coordination node is a new module include in the stack entry file — one `` with your topic args, per the single-locus rule. If you're modifying a topology, make your own stack first: see [Creating a Custom Stack Topology](../../../development/creating_a_stack.md) and the [integrate-module-into-layer skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/integrate-module-into-layer/SKILL.md). Do not touch `coordination_bringup` or `autonomy_bringup`. + +**Verify:** `airstack up --stack --sim isaac` brings the node up (`ros2 node list` inside the robot container shows it alongside `gossip_node`). + +## Step 4 — Test with multiple robots + +```bash +airstack up --sim isaac --robots 3 # replicas: airstack-robot-desktop-1/-2/-3, domains 1/2/3 +# or, for heterogeneous fleets / split stacks: +airstack up --fleet --sim isaac # config/fleets/.yaml +airstack ready +``` + +`--robots N` also selects the multi-robot Isaac spawn script; fleet identity/placement details are in [AirStack Fleets](../../../development/fleets.md). + +Watch the peer registry converge — every robot should list every other: + +```bash +ROS_DOMAIN_ID=99 ros2 run coordination_bringup peer_registry_monitor # the shared bus view +ROS_DOMAIN_ID=1 ros2 run coordination_bringup peer_registry_monitor # what robot_1 actually receives +ros2 topic echo /gossip/peers # raw messages +``` + +(Run these inside a robot container via `docker exec ... bash -c "..."`.) Then exercise your algorithm and confirm the decisions land: the acted-on robot's `global_plan` changes, or the `tasks/*` action goal is accepted. + +**Verify:** the monitor shows all N robots with fresh timestamps and your `payload_type` listed per peer, and your node's output topic/action responds on each robot. + +## What the platform does not give you yet + +Read `common/ros_packages/coordination/README.md` before designing around these: + +- **No relay/multi-hop.** The `source`/`relay_hops` wire fields are reserved; relay logic is not active. Every robot must reach the shared gossip domain directly. +- **No payload delta suppression.** Payloads are re-serialized and re-sent on every publish tick even when unchanged (payload version hashing is a future plan) — budget bandwidth accordingly. +- **Delivery is BEST_EFFORT and at-least-once.** The seen-set (`(robot_name, stamp)` dedup, 50-entry FIFO) prevents re-processing, not loss; design decisions to be idempotent under a 1 Hz latest-wins stream. +- **Registry entries are never evicted.** A crashed peer stays in the registry until the node restarts — check profile timestamps yourself if liveness matters. + +Consensus, task allocation, and leader election are yours to build on top of this bus; the platform provides the state-sharing substrate, not the algorithms. diff --git a/docs/robot/autonomy/global/index.md b/docs/robot/autonomy/global/index.md index 0719bfe59..6d803d8bb 100644 --- a/docs/robot/autonomy/global/index.md +++ b/docs/robot/autonomy/global/index.md @@ -1,13 +1,22 @@ # Global Packages -The global packages include global world models and planners. +The **global** layer gives the robot memory and direction beyond sensor range: a persistent 3D map of everywhere it has been, and a global planner that reasons over that map to produce a coarse waypoint path. Its output — `global_plan` — is the handoff to the [local layer](../local/index.md), which refines it into collision-free trajectory segments. Because global planning is not flight-critical, it is the one part of the stack that may run off-vehicle: the `lite_offload_global` split stack moves it to a ground host. +## Sub-layers + +- [**World Model**](world_model/index.md) — the persistent VDB voxel map built from filtered sensor clouds +- [**Planning**](planning/index.md) — global planners (reference implementation: random walk exploration) ## Launch -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 -owns the cross-package VDB config files. +The global layer is composed by the selected stack's entry launch file (`stacks//launch/*.launch.xml`): the trunk stacks include `vdb_mapping_ros2.py` and `random_walk_planner.launch.xml` directly — see `stacks/full_default/launch/stack.launch.xml`. The `global_bringup` package (`robot/ros_ws/src/global/global_bringup`) owns the cross-package VDB config (`config/vdb_params.yaml`), which the stack entry file passes to the mapper. + +## Key Interchanges + +- [`global_map` (§3)](../interface_conventions.md#3-global_map-global-world-model) — the VDB map topics the global planner consumes +- [`global_plan` (§4)](../interface_conventions.md#4-global_plan-global-waypoint-path) — the waypoint path handed to the local planner; the one interchange that may cross a machine boundary in split stacks + +## See Also + +- [System Architecture — Global Layer](../system_architecture.md#global-layer) +- [Task Executors](../tasks.md) — the exploration task server lives in the global planner diff --git a/docs/robot/autonomy/global/planning/index.md b/docs/robot/autonomy/global/planning/index.md index 2fc678764..2da25a8e2 100644 --- a/docs/robot/autonomy/global/planning/index.md +++ b/docs/robot/autonomy/global/planning/index.md @@ -1,100 +1,113 @@ [//]: # "global" # Planning -![global_trajectory_diagram](global_trajectory.png) +Global planners output a high-level, coarse path for the robot to follow. -Global planners output a high level, coarse trajectory for the robot to follow. +The global planner should make a path that is collision-free according to the +global map ([`global_map`, spec §3](../../interface_conventions.md#3-global_map-global-world-model)). +However, avoiding fine obstacles is delegated to the local planner, which +operates at a faster rate. -A **trajectory** is a spatial path plus a schedule. -This means each waypoint in the trajectory has a time associated with it, indicating when the robot should reach that waypoint. -These timestamps are fed to the local planner and controller to determine velocity and acceleration. +For the structure of the package, the global planner node should not include +any logic to generate the path. This should be located in a separate logic +class, separated from ROS. This allows more modularity for testing and easy +interface changes. -If a waypoint's header timestamp is empty, the local planner should assume there's no time constraint and follow the trajectory at its own pace. - -The global planner should make a trajectory that is collision-free according to the global map. -However, avoiding fine obstacles is delegated to the local planner that operates at a faster rate. - -For the structure of the package, the global planner node should not include any logic to generate the path. This should be located in a seperate logic class and be seperated from ROS. This will allow more modularity in the future for testing and easy interface changes. - -We intend the global planners to be modular. _AirStack_ implements a basic Random Walk planner as a baseline. +We intend the global planners to be modular. _AirStack_ implements a basic +Random Walk planner as a baseline, plus a frontier-based Exploration planner. Feel free to implement your own through the following interfaces. ## ROS Interfaces -Global planners are meant to be modules that can be swapped out easily. -They can be thought of as different high level behaviors for the robot to follow. -Consider that multiple global planners may be run in parallel, for example by some ensemble planner node that chooses the best plan for the current situation. - -As such, the global planner should be implemented as a ROS2 node that accepts runtime mission parameters in a custom `PlanRequest.msg` and -publishes a plan to its local `~/global_plan` topic. - -The best global plan should then be forwarded or remapped to `/$(env ROBOT_NAME)/global_plan` for the local planner to follow. - -``` mermaid -sequenceDiagram - autonumber - Global Manager->>Global Planner: ~/plan_request (your_planner/PlanRequest.msg) - loop Planning - Global Planner-->>Global Manager: heartbeat feedback - end - Global Planner->>Global Manager: ~/global_plan (nav_msgs/Path.msg) - Global Manager->>Local Planner: /$ROBOT_NAME/global_plan_reference (nav_msgs/Path.msg) - Local Planner->>Global Manager: /$ROBOT_NAME/global_plan_eta (nav_msgs/Path.msg) -``` +Global planners are meant to be modules that can be swapped out easily. +They can be thought of as different high-level behaviors for the robot to +follow. -### Subscribe: Plan Request -Your custom `PlanRequest.msg` defines the parameters that your global planner needs to generate a plan. -It will be sent on the `~/plan_request` topic. +A global planner sits at the top of the [task cascade](../../tasks.md#task-cascade): +it is a **task executor** — a ROS 2 action server that only plans while a goal +is active — invoked by the operator from the GCS (Foxglove robot-commands +panel or RViz Tasks Panel), and it delegates navigation to the local planner. -Some common parameters may be the following: +```mermaid +graph TD + GCS[GCS operator] -->|"task goal, e.g. ExplorationTask (tasks/exploration)"| GP[Global planner] + GP -->|"global_plan (nav_msgs/Path, map frame)"| LP[Local planner] + GP -->|"NavigateTask goal (tasks/navigate)"| LP ``` -# PlanRequest.msg -std_msgs/Duration timeout # maximum time to spend planning -geometry_msgs/Polygon bounds # boundary that the plan must stay within -``` - - -### Publish: Global Plan -The global planner must publish a message of type `nav_msgs/Path` to `~/global_plan`. -The message defines high level waypoints to reach by a given time. -The `nav_msgs/Path` message type contains a `header` field and `poses` field. +### Task-executor action server -- The top level header of `nav_msgs/Path` message should contain the coordinate frame of the trajectory, and its timestamp should indicate when the trajectory was published. -- Within the `poses` field, each `geometry_msgs/PoseStamped`'s header should contain a timestamp that indicates when that waypoint should be reached +Each global planner exposes its action server at the canonical +`/{robot_name}/tasks/` name — see +[`tasks/*` — task action servers (spec §8)](../../interface_conventions.md#8-tasks-task-action-servers). +Action types live in the shared `task_msgs` package; the per-task goal, +feedback, and result fields are documented in +[Task Executors](../../tasks.md#task-action-types). +For example, the random walk planner serves +`tasks/exploration` (`task_msgs/action/ExplorationTask`) and, while the goal +is active, sends the generated path to the local planner as a +`NavigateTask` goal on `tasks/navigate`. -``` -nav_msgs/Path.msg - - std_msgs/Header header - - time stamp: when the trajectory was generated - - frame_id: the coordinate frame of the trajectory - - geometry_msgs/PoseStamped[] poses: the trajectory - - geometry_msgs/PoseStamped pose - - std_msgs/Header header - - time stamp: when the waypoint should be reached - - string frame_id: the coordinate frame of the waypoint - - geometry_msgs/Pose pose: the position and orientation of the waypoint -``` -### Publish: Heartbeat -For long-running global planners, it's recommended to publish a heartbeat message to `~/heartbeat`. This way the calling node can know that the global planner is still running and hasn't crashed. - -### Additional Subscribers -In general, the global planner needs to access components of the world model such as the map and drone state. - -The most common map is Occupancy Grids that is published by {==TODO==} node. - -The global planner can also access the robot's current state and expected state in the future. For example, if the global planner takes 20 seconds to plan a trajectory, -it can query where the robot expects to be in 20 seconds. This ROS2 service is available under {==TODO==}. +### Publish: Global Plan -The global planner can do whatever it wants internally with this information. +The global planner publishes its path as a `nav_msgs/Path` on its local +`~/global_plan` topic, remapped in the module launch file to the canonical +`/{robot_name}/global_plan` — see +[`global_plan` — global waypoint path (spec §4)](../../interface_conventions.md#4-global_plan-global-waypoint-path). +The path is in the `map` frame (ENU, meters), and its last pose is the +navigation goal. The local planner consumes it and handles fine obstacle +avoidance along the way. `global_plan` is the one interchange that may cross +a machine boundary, which is what makes the global-offload split stack +(`lite_offload_global`) possible. + +Where applicable, plan publication can be toggled at runtime: the exploration +planner exposes a `~/global_plan_toggle` service (`std_srvs/Trigger`, +remapped to `/{robot_name}/behavior/global_plan_toggle`) to turn planning on +and off from the RViz Tasks Panel or the GCS. + +### Subscribe: World Model and State + +In general, the global planner needs the global map and the robot state: + +- **Map:** today's de facto map interchange is the VDB map visualization + topic published by `vdb_mapping` — see + [`global_map` (spec §3)](../../interface_conventions.md#3-global_map-global-world-model). + The random walk planner collision-checks its segments against it. +- **State estimate:** the canonical odometry topic + `odometry_conversion/odometry` — see + [`odometry` (spec §2)](../../interface_conventions.md#2-odometry-primary-state-estimate). + +Both are declared as launch arguments (defaulting to the canonical names) in +the module launch file, so a stack entry file can rewire them without +touching the module. ## Example Planners ### Random Walk planner -The random walk planner replans when the robot is getting close to the goal. The random walk planner is a trivial planner that generates a plan by randomly selecting a direction to move in. The random walk planner is useful for testing the robot's ability to follow a plan. +The [random walk planner](../../../../../robot/ros_ws/src/global/planners/random_walk/README.md) +replans when the robot is getting close to the goal. It is a trivial planner +that generates a plan by randomly selecting a direction to move in, and is +useful for testing the robot's ability to follow a plan. It is the reference +task-executor implementation, serving `tasks/exploration`. ### Exploration planner -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). - +The [exploration planner](../../../../../robot/ros_ws/src/global/planners/exploration/README.md) +(`robot/ros_ws/src/global/planners/exploration`) is a frontier-based +geometric exploration planner — an alternative to `random_walk`. To use it, +swap the `random_walk_planner.launch.xml` include in your stack's entry +launch file for `exploration_launch.xml`, as described in its README. + +## Writing Your Own Global Planner + +1. Follow the [Module Integration Checklist](../../integration_checklist.md) + for package structure, launch-file conventions, and wiring. +2. Implement the planner as a task executor: see + [Adding a New Task Executor](../../tasks.md#adding-a-new-task-executor) + and the + [add-task-executor](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-task-executor/SKILL.md) + skill for the step-by-step action-server pattern. +3. Publish `global_plan` and delegate navigation to `tasks/navigate` + per the interfaces above, defaulting every endpoint to its canonical name + from the [Interface Conventions Specification](../../interface_conventions.md). diff --git a/docs/robot/autonomy/global/world_model/index.md b/docs/robot/autonomy/global/world_model/index.md index 7d1feb6cf..712cebbf6 100644 --- a/docs/robot/autonomy/global/world_model/index.md +++ b/docs/robot/autonomy/global/world_model/index.md @@ -1,6 +1,23 @@ [//]: # "global" # World Model -Global world models are responsible for maintaining a representation of the world that is used by the global planner to generate a plan. This representation is typically a map of the environment, but can also include other information such as the location of other robots, obstacles, and goals. +The global world model maintains a persistent 3D representation of everywhere the robot has sensed — the memory the [global planner](../planning/index.md) plans over. AirStack's shipped default is **VDB Mapping**: an OpenVDB-based voxel map, built in the `map` frame from the filtered LiDAR cloud, vendored in-tree with the config owned by `global_bringup`. -The current placeholder world model is a voxelized map representation called [VDB Mapping](https://github.com/fzi-forschungszentrum-informatik/vdb_mapping_ros2). \ No newline at end of file +## Packages + +- [**VDB Mapping ROS 2**](../../../../../robot/ros_ws/src/global/world_models/vdb_mapping_ros2/README.md) (`vdb_mapping_ros2`, in-tree) — ROS 2 wrapper around the FZI [VDB Mapping](https://github.com/fzi-forschungszentrum-informatik/vdb_mapping) library; upstream wrapper repo: [vdb_mapping_ros2](https://github.com/fzi-forschungszentrum-informatik/vdb_mapping_ros2) + +## Launch + +The mapper is composed by the selected stack's entry launch file (`stacks//launch/*.launch.xml`), which includes `vdb_mapping_ros2.py` with `global_bringup/config/vdb_params.yaml` — see `stacks/full_default/launch/stack.launch.xml`. + +## Key Interchanges + +All map topics are specified in [`global_map` (§3)](../../interface_conventions.md#3-global_map-global-world-model): `vdb_map_visualization` (today's de facto map interchange, consumed by the reference global planner), the `vdb_map_updates` / `_sections` / `_overwrites` grids for remote/split map synchronization, and the `vdb_map_pointcloud` export. The map lives in the `map` frame. + +In the `lite_offload_global` split stack, VDB mapping runs **offboard** on the ground host: the filtered sensor cloud crosses the bridge (per the stack's `bridge.yaml`) and the map is built where the global planner consumes it. + +## See Also + +- [Global layer overview](../index.md) +- [System Architecture — Global Layer](../../system_architecture.md#global-layer) diff --git a/docs/robot/autonomy/index.md b/docs/robot/autonomy/index.md index bfd8cf6c8..25d81b0fb 100644 --- a/docs/robot/autonomy/index.md +++ b/docs/robot/autonomy/index.md @@ -1,22 +1,32 @@ # Autonomy Modules -## Overview +The onboard autonomy stack is organized into **layers**: data flows from sensors through perception and world models into planners, then down through controllers to the hardware interface. Each layer is a set of swappable ROS 2 packages that meet at the narrow interchange points defined in the [Interface Conventions Specification](interface_conventions.md), so an individual module (a planner, a controller, a mapper) can be replaced without rewiring its neighbors. -The AirStack autonomy stack is organized into modular layers that work together to enable autonomous operation. Each layer has specific responsibilities and communicates with adjacent layers through well-defined ROS 2 interfaces. +## The Six Layers -## Modules +- [**Interface**](interface/index.md) — the bridge to the flight controller: command authority, arming, and MAVLink/MAVROS translation +- [**Sensors**](sensors/index.md) — driver/bridge topic normalization and robot-side preprocessing (e.g. LiDAR near-range filtering) +- [**Perception**](perception/index.md) — state estimation: the odometry every downstream module consumes +- [**Local**](local/index.md) — short-range world model, reactive local planner, and the trajectory + PID controllers +- [**Global**](global/index.md) — persistent 3D mapping (VDB) and coarse global planning +- [**Behavior**](behavior/index.md) — the onboard safety executive -- [**Interface**](interface/index.md) - Hardware interface and safety systems -- [**Sensors**](sensors/index.md) - Sensor integration and data processing -- [**Perception**](perception/index.md) - State estimation and environment understanding -- [**Local**](local/index.md) - Local planning, world models, and control -- [**Global**](global/index.md) - Global planning and mapping -- [**Behavior**](behavior/index.md) - High-level mission execution and decision making +Alongside the layers, [**Coordination**](coordination/index.md) lets robots gossip state to each other and the GCS. + +## Perpetual Nodes vs Task Executors + +Modules run in one of two styles. **Perpetual nodes** (state estimation, world models, controllers) run continuously from launch to shutdown. **Task executors** are action servers that only work when the operator sends a goal from the GCS — takeoff, land, navigate, explore — cascading from global-layer to local-layer executors. See [System Architecture — Node Types](system_architecture.md#node-types-perpetual-vs-task-executor) and [Task Executors](tasks.md). + +## Where Stacks Fit + +Which modules run, and how they are wired, is decided by the selected **stack**: each layer's modules are composed by the stack's entry launch file (`stacks//launch/*.launch.xml`, e.g. `stacks/full_default/launch/stack.launch.xml`). See [Stacks](../../development/stacks.md). ## Key Resources -- [**System Architecture**](system_architecture.md) - Detailed architecture diagrams and data flow -- [**Integration Checklist**](integration_checklist.md) - Guide for adding new modules +- [**System Architecture**](system_architecture.md) — architecture diagrams and data flow +- [**Interface Conventions Specification**](interface_conventions.md) — the versioned contract at every module boundary +- [**Integration Checklist**](integration_checklist.md) — guide for adding new modules ## System Diagram + ![AirStack System Diagram](../airstack_system_diagram.png) diff --git a/docs/robot/autonomy/integration_checklist.md b/docs/robot/autonomy/integration_checklist.md index b9c7e24b8..16abfd5ce 100644 --- a/docs/robot/autonomy/integration_checklist.md +++ b/docs/robot/autonomy/integration_checklist.md @@ -3,7 +3,7 @@ This document provides a comprehensive checklist and guidelines for integrating new modules into the AirStack autonomy stack. > **Canonical names, types, QoS, and frames live in the versioned -> [Interface Conventions Specification](interface_conventions.md)** (v1.0.0) — +> [Interface Conventions Specification](interface_conventions.md)** — > cite that spec for interchange-point contracts; this page remains the > step-by-step integration workflow. @@ -54,115 +54,34 @@ integration in one place. --- -## Standard Topic Patterns +## Standard Interfaces -AirStack uses standardized topic naming conventions to ensure consistent communication between modules. +Canonical topic/service/action names, message types, QoS profiles, and +frames for every interchange point are defined in the versioned +[Interface Conventions Specification](interface_conventions.md) — do not +copy its tables here. The sections you will cite most while integrating: -### Topic Naming Convention +- [State estimation (`odometry`)](interface_conventions.md#2-odometry-primary-state-estimate) +- [Trajectory controller surface](interface_conventions.md#5-trajectory-group-the-trajectory-controllers-contract-onboard-only) +- [Interface commands (`control_setpoint`)](interface_conventions.md#6-control_setpoint-controller-interface-command-onboard-only) + and [interface status](interface_conventions.md#7-interface_status-group-vehicle-state-out-of-the-interface-layer) +- [Task action servers (`tasks/*`)](interface_conventions.md#8-tasks-task-action-servers) -Topics follow this pattern: -``` -/[robot_name]/[layer]/[module]/[data_type] -``` - -Examples: - -- `/drone1/perception/macvo/odometry` (a module-provided topic — macvo comes from the `asm_macvo` module) -- `/drone1/local_planner/droan/trajectory` -- `/drone1/trajectory_controller/tracking_point` - -### Common Standard Topics - -These topics are used across multiple modules and should be used when applicable: - -| Topic | Type | Purpose | Layer | -|-------|------|---------|-------| -| `/[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/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 | - -### Layer-Specific Topic Patterns - -#### Interface Layer -- **Inputs:** Commands from control layer -- **Outputs:** Robot state, sensor raw data -- **Topics:** - - - `/[robot]/interface/mavros/state` - - `/[robot]/interface/mavros/local_position/pose` - - `/[robot]/interface/battery_state` - -#### Sensors Layer -- **Inputs:** Raw sensor data from interface -- **Outputs:** Processed sensor data -- **Topics:** - - - `/[robot]/sensors/[sensor_name]/[data_type]` - - Example: `/[robot]/sensors/front_stereo/left/image` - - Example: `/[robot]/sensors/front_stereo/disparity` - -#### Perception Layer -- **Inputs:** Sensor data -- **Outputs:** Odometry, environment understanding -- **Topics:** +What the checklist adds on top of the spec: - - `/[robot]/perception/[module]/odometry` - - `/[robot]/perception/[module]/depth` - - `/[robot]/odometry` (aggregated/primary odometry) +- **Every input/output topic must be remappable via launch arguments.** + Default each one to its canonical name from the spec so a conventional + stack needs zero remaps; only deviations belong in the stack entry file. +- **Task action servers** must be remapped to + `/{robot_name}/tasks/{task_name}` in the module's launch file: -#### Local Layer -- **Inputs:** Odometry, local sensor data, global plan -- **Outputs:** Local trajectories, cost maps -- **Topics:** - - World Models: `/[robot]/local/[module]/cost_map` - - Planners: `/[robot]/local/[module]/trajectory` - - Controllers: `/[robot]/trajectory_controller/cmd` - -#### Global Layer -- **Inputs:** Global map, robot pose, goal -- **Outputs:** Global plan, map updates -- **Topics:** - - Mapping: `/[robot]/global/[module]/map` - - Planning: `/[robot]/global_plan` - -#### Behavior Layer - -- **Inputs:** Mission commands, autonomy state -- **Outputs:** High-level commands, mode changes -- **Topics:** - - - `/[robot]/behavior/drone_safety_monitor/state_estimate_timed_out` - - `/[robot]/behavior/drone_safety_monitor/command` - -### Task Action Server Naming Convention - -All task action servers must be remapped to: - -```text -/{robot_name}/tasks/{task_name} -``` - -Examples: - -- `/{robot_name}/tasks/exploration` — ExplorationTask -- `/{robot_name}/tasks/navigate` — NavigateTask -- `/{robot_name}/tasks/coverage` — CoverageTask - -Add the remap in the module's launch file: - -```xml - -``` + ```xml + + ``` -The `~/` prefix expands to the node's private namespace at runtime, -making the action name configurable without hardcoding. + The `~/` prefix expands to the node's private namespace at runtime, + making the action name configurable without hardcoding. See [Task Executors](tasks.md) for the complete list of defined task action types. diff --git a/docs/robot/autonomy/interface/index.md b/docs/robot/autonomy/interface/index.md index 4a4a244ad..23c6bf700 100644 --- a/docs/robot/autonomy/interface/index.md +++ b/docs/robot/autonomy/interface/index.md @@ -3,8 +3,27 @@ The interface defines the communication between the autonomy stack running on the onboard computer and the robot's control unit. For example, for drones it converts the control commands from the autonomy stack into MAVLink messages for the flight controller. -==TODO: This is not our diagram, must replace.== -![Interface Diagram](https://404warehouse.net/wp-content/uploads/2016/08/softwareoverview.png?w=800) +```mermaid +graph LR + subgraph Autonomy stack + C[Controllers] + O["odometry_conversion node"] + end + subgraph Interface layer + RI["robot_interface node
(MAVROSInterface)"] + M[MAVROS] + end + FCU["Flight controller (FCU)
PX4 via MAVLink"] + + C -->|"interface/cmd_velocity, interface/cmd_pose,
interface/cmd_roll_pitch_yawrate_thrust, ... (spec §6)"| RI + RI --> M + M <--> FCU + M -->|"interface/mavros/* state (spec §7)"| C + M -->|interface/mavros/local_position/odom| O + O -->|"odometry_conversion/odometry (spec §2)"| C +``` + +Command topics are the [`control_setpoint` interchange (spec §6)](../interface_conventions.md#6-control_setpoint-controller-interface-command-onboard-only); vehicle state comes back out through the [`interface_status` group (spec §7)](../interface_conventions.md#7-interface_status-group-vehicle-state-out-of-the-interface-layer) and the canonical [`odometry` topic (spec §2)](../interface_conventions.md#2-odometry-primary-state-estimate). The code is located under `robot/ros_ws/src/interface/`. @@ -13,6 +32,7 @@ The code is located under `robot/ros_ws/src/interface/`. Launch files are under `robot/ros_ws/src/interface/interface_bringup/launch`. The main launch command is `ros2 launch interface_bringup interface.launch.py`. +It starts MAVROS (under the `interface` namespace), the `robot_interface` node, the position setpoint publisher, and the `odometry_conversion` node. ### FCU URL and Target System @@ -34,15 +54,16 @@ MAVROS is skipped entirely when `SIM_TYPE=simple`. ## RobotInterface Package `robot_interface` is a ROS2 node that interfaces with the robot's hardware. -The `RobotInterface` _gets robot state_ and forwards it to the autonomy stack, -and also _translates control commands_ from the autonomy stack into the command for the underlying hardware. +The `RobotInterface` _translates control commands_ from the autonomy stack into the command for the underlying hardware, and reports arming/control status back to the stack. Note the base class is unimplemented. Specific implementations should extend `class RobotInterface` in `robot_interface.hpp`, for example `class MAVROSInterface`. ### State -The `RobotInterface` class broadcasts the robot's pose as a TF2 transform. -It also publishes the robot's odometry as a `nav_msgs/Odometry` message to `$(env ROBOT_NAME)/interface/robot_interface/odometry`. +Vehicle state flows out of the interface layer on two paths: + +- The `robot_interface` node publishes `interface/is_armed` and `interface/has_control` (`std_msgs/Bool`), and MAVROS itself publishes the vehicle state topics under `interface/mavros/*` (e.g. `state`, `extended_state`, `global_position/global`) — see [spec §7](../interface_conventions.md#7-interface_status-group-vehicle-state-out-of-the-interface-layer). +- The canonical odometry is **not** produced by `RobotInterface` implementations. A separate `odometry_conversion` node (also in the `robot_interface` package) subscribes to `interface/mavros/local_position/odom`, republishes it as the canonical `odometry_conversion/odometry` (`nav_msgs/Odometry`, `map` frame — see [spec §2](../interface_conventions.md#2-odometry-primary-state-estimate)), and broadcasts the corresponding `map → base_link` TF (plus a stabilized variant). ### Commands @@ -68,30 +89,12 @@ The available implementation in AirStack is called `MAVROSInterface` implemented If you're using a different robot control unit with its own custom API, then you need to create an associated RobotInterface. Implementations should do the following: -### Broadcast State - -Implementations of `RobotInterface` should obtain the robot's pose and broadcast it as a TF2 transform. - -Should look something like: - -```c++ -// callback function triggered by some loop -void your_callback_function(){ - // ... - geometry_msgs::msg::TransformStamped t; - // populate the transform, e.g.: - t.header = // some header - t.transform.translation.x = // some value - t.transform.translation.y = // some value - t.transform.translation.z = // some value - t.transform.rotation = // some quaternion - // Send the transformation - this->tf_broadcaster_->sendTransform(t); - // ... -} -``` +### Provide State -==TODO: our code doesn't currently do it like this, it instead uses an external odometry_conversion node.== +Your interface (or its underlying driver, as MAVROS does) should publish the robot's native odometry. +Do not broadcast pose TF or publish the canonical odometry topic from the interface itself — that is the job of the existing `odometry_conversion` node. +Instead, point the `interface_odometry_in_topic` launch argument of `interface.launch.py` (default: `/$(env ROBOT_NAME)/interface/mavros/local_position/odom`) at your interface's odometry output. +The `odometry_conversion` node then produces the canonical `odometry_conversion/odometry` in the `map` frame and broadcasts the `map → base_link` transform, so every downstream consumer works unchanged. ### Override Command Handling diff --git a/docs/robot/autonomy/interface_conventions.md b/docs/robot/autonomy/interface_conventions.md index 80f8dcd35..1e5c3f7ea 100644 --- a/docs/robot/autonomy/interface_conventions.md +++ b/docs/robot/autonomy/interface_conventions.md @@ -1,6 +1,6 @@ # Interface Conventions Specification -**Spec version: v1.0.0** (semver — see [Versioning and deprecation](#versioning-and-deprecation)) +**Spec version: v1.0.1** (semver — see [Versioning and deprecation](#versioning-and-deprecation)) This is the versioned specification of AirStack's **interchange points** — the narrow waists where modules meet: canonical topic/service/action names, @@ -168,6 +168,8 @@ skill). All types come from `task_msgs`: | `tasks/fixed_trajectory` | `task_msgs/action/FixedTrajectoryTask` | trajectory_controller pkg (onboard) | | `tasks/exploration` | `task_msgs/action/ExplorationTask` | random_walk (global planner) | | `tasks/semantic_search` | `task_msgs/action/SemanticSearchTask` | (module-provided) | +| `tasks/coverage` | `task_msgs/action/CoverageTask` | (defined in `task_msgs`; no shipped executor) | +| `tasks/chat` | `task_msgs/action/ChatTask` | (defined in `task_msgs`; no shipped executor) | Related service: `takeoff_landing_planner/set_takeoff_landing_command` (`airstack_msgs/srv/TakeoffLandingCommand`) — the GCS-facing takeoff/land @@ -241,4 +243,5 @@ proposal process. | Spec | Date | Change | |---|---|---| +| v1.0.1 | 2026-08-25 | §8: added `tasks/coverage` and `tasks/chat` rows so the table covers all eight `task_msgs` actions; both are defined in `task_msgs` with no shipped executor. Documentation-only. | | 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/index.md b/docs/robot/autonomy/local/index.md index 050db46ef..011eea596 100644 --- a/docs/robot/autonomy/local/index.md +++ b/docs/robot/autonomy/local/index.md @@ -1,9 +1,24 @@ # Local Packages -The local module includes packages that are specific to the local autonomy of the robot. This includes local mapping, planning, and control. + +The **local** layer closes the robot's short-range sense-plan-act loop: a fast local world model built from live sensor data, a reactive local planner that avoids obstacles the global map is too slow or too coarse to capture, and the controllers that turn planned trajectories into commands for the [interface](../interface/index.md). The loop is coupled through the trajectory controller's **look-ahead point** — the local planner plans forward from where the controller will soon be, and streams trajectory segments back to it. + +## Sub-layers + +- [**World Model**](world_model/index.md) — disparity-based C-space obstacle representation for fast collision queries +- [**Planning**](planning/index.md) — the DROAN local planner: turns the global plan into short, collision-free trajectory segments +- [**Controls**](controls/index.md) — trajectory controller (tracking/look-ahead point management) and PID controller (attitude/thrust commands) ## Launch -Local modules ship their own canonical launch files and are composed flat by -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. +Local modules ship their own canonical launch files and are composed flat by the selected stack's entry launch file (`stacks//launch/*.launch.xml`) — the trunk stacks include `takeoff_landing_planner`, the trajectory controller, `droan_gl`, and the PID controller directly; see `stacks/full_default/launch/stack.launch.xml` for the composed wiring. + +## Key Interchanges + +- [`global_plan` (§4)](../interface_conventions.md#4-global_plan-global-waypoint-path) — the coarse path handed down from the global layer; the local planner's main input +- [`trajectory` group (§5)](../interface_conventions.md#5-trajectory-group-the-trajectory-controllers-contract-onboard-only) — the trajectory controller's onboard-only contract: `trajectory_segment_to_add`, `trajectory_override`, `tracking_point`, `look_ahead` +- [`control_setpoint` (§6)](../interface_conventions.md#6-control_setpoint-controller-interface-command-onboard-only) — the PID controller's command into the interface layer + +## See Also + +- [System Architecture — Local Layer](../system_architecture.md#local-layer) +- [Global](../global/index.md) — the upstream producer of `global_plan` diff --git a/docs/robot/autonomy/perception/adding_a_state_estimator.md b/docs/robot/autonomy/perception/adding_a_state_estimator.md new file mode 100644 index 000000000..3972b0901 --- /dev/null +++ b/docs/robot/autonomy/perception/adding_a_state_estimator.md @@ -0,0 +1,77 @@ +# Adding a State Estimator + +A state estimator in AirStack is any node that produces the robot's primary state estimate — the odometry surface every downstream consumer (safety monitor, PID controller, DROAN, random_walk, trajectory controller, task servers) subscribes to. The contract is [Interface Conventions §2](../interface_conventions.md#2-odometry-primary-state-estimate): `nav_msgs/Odometry` on `odometry_conversion/odometry` (RELIABLE QoS), `pose` in the `map` frame (ENU, meters), `twist` in the body frame (`child_frame_id`), plus the `map → base_link` TF. This guide assumes you have run the stack before and know the [layered architecture](../index.md); it swaps the estimator, not the consumers. + +Today's default estimator path is PX4's EKF: MAVROS publishes `/{robot_name}/interface/mavros/local_position/odom`, and the `odometry_conversion` node (from the `robot_interface` package, launched inside `robot/ros_ws/src/interface/interface_bringup/launch/interface.launch.py`) normalizes it onto the canonical surface — it restamps `frame_id`/`child_frame_id` to `map`/`base_link`, republishes on `odometry_conversion/odometry`, and broadcasts the `map → base_link` TF (`convert_odometry_to_transform: true`). Your estimator replaces the *input* to that node, not the node itself. + +## Package or module? + +Decide early where the estimator lives: + +- **In-tree package** — fastest for trunk work: a normal ROS 2 package under `robot/ros_ws/src/perception/`, or a scaffolded module boundary in your fork via `airstack module create --in-tree ` (lands under `robot/ros_ws/src/modules/`). +- **Module repo** — shareable, version-pinned, with its own CI and Docker dependency layer: a thin external repo added with `airstack module add --version `. See [AirStack Modules](../../../development/modules.md) (especially [the researcher workflow](../../../development/modules.md#the-researcher-workflow-fork-module)) and the [create-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md). The precedent for a state estimator shipped this way is [asm_macvo](../../../modules/macvo.md) — MAC-VO learned stereo visual odometry, consumed by the [full_macvo](../../../../stacks/full_macvo/README.md) stack. + +The steps below are the same either way; only step 5 differs. + +## Steps + +### 1. Create the package + +Follow the [add-ros2-package skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/add-ros2-package/SKILL.md) and the [Module Integration Checklist](../integration_checklist.md). Put it under `robot/ros_ws/src/perception/` (or use the module scaffold above). Declare every topic endpoint as a launch argument defaulting to its canonical name — that is what lets a stack include it with zero remaps. + +**Verify:** it builds inside the robot container — `docker exec airstack-robot-desktop-1 bash -c "bws --packages-select "` exits cleanly. + +### 2. Conform to the spec §2 surface + +Publish `nav_msgs/Odometry` with a RELIABLE publisher; link the [spec table](../interface_conventions.md#2-odometry-primary-state-estimate) from your README rather than restating it. Frames per the spec's [TF table](../interface_conventions.md#tf-frames-and-units): `pose` in `map` (ENU, meters), `twist` in the body frame named by `child_frame_id`, yaw right-handed about +Z. + +The recommended integration is to publish your estimate on your own namespaced topic (e.g. `perception//odometry`) and route it *through* `odometry_conversion` — `interface.launch.py` declares the `interface_odometry_in_topic` launch argument exactly for this. You then keep frame normalization and the `map → base_link` TF broadcast for free. If you instead bypass `odometry_conversion` and publish the canonical topic directly, **you** must broadcast `map → base_link` — `odometry_conversion` is the node that publishes it in the default graph, and it is launched unconditionally by `interface.launch.py`, so bypassing also means forking that launch file to avoid two publishers on the same surface. Route through it. + +**Verify:** with your node running, `docker exec airstack-robot-desktop-1 bash -c "sws && ros2 topic echo /$ROBOT_NAME/perception//odometry --once"` shows a sane pose and the frame ids you expect. + +### 3. Wire it into a stack + +The [single-locus rule](../../../development/stacks.md#the-single-locus-rule-and-its-lint): all wiring deviations live in one place — the stack entry launch file. Never edit `interface.launch.py` or another module's launch file to point at your estimator. Copy a reference stack and change the include lines: + +```bash +airstack stack new full_default full_my_estimator +``` + +Then in `stacks/full_my_estimator/launch/stack.launch.xml`, (a) include your estimator under the `perception` namespace, and (b) pass the interface's odometry-input arg. This is the same pattern [full_macvo](../../../../stacks/full_macvo/README.md) uses to add MAC-VO under `perception/` and rewire one consumer with a single include arg (`stacks/full_macvo/launch/stack.launch.xml` — there the deviation is DROAN's disparity input; here it is the interface's odometry input): + +```xml + + + + + + + + + + +``` + +**Verify:** `airstack up --stack full_my_estimator --sim isaac --robots 1`, then `airstack ready`, then `docker exec airstack-robot-desktop-1 bash -c "ros2 node list"` shows your estimator node alongside `odometry_conversion`. + +### 4. Verify the running graph + +1. Rate and content: `docker exec airstack-robot-desktop-1 bash -c "sws && ros2 topic hz /robot_1/odometry_conversion/odometry"` reports a `state`-class rate (~10–100 Hz), and `ros2 topic echo ... --once` shows `frame_id: map`, `child_frame_id: base_link`. +2. Snapshot the wiring and diff it: `airstack test -m wiring --stack full_my_estimator` regenerates `stacks/full_my_estimator/wiring.md`; review that the only deviations from `full_default`'s wiring are your estimator and the odometry input. +3. Live drift check: `airstack doctor --live --stack full_my_estimator` reports the running graph against the committed `wiring.md` with no unexplained diffs. + +A best-effort/reliable QoS mismatch here fails *silently* (consumers receive nothing) — the spec calls this out; `ros2 topic hz` from step 1 is the check that catches it. + +### 5. Optional: package it as a module + +If the estimator should be shareable and pinnable outside your fork, graduate it to a module repo following [AirStack Modules](../../../development/modules.md) and the [create-module skill](https://github.com/castacks/AirStack/blob/develop/.agents/skills/create-module/SKILL.md), with [asm_macvo](../../../modules/macvo.md) as the worked precedent (heavy deps in `Dockerfile.module`, a consuming reference stack, module CI). Your stack's `modules.repos` then pins it, exactly as `stacks/full_macvo/modules.repos` pins `asm_macvo`. + +**Verify:** `airstack module add --version ` followed by `airstack module doctor` passes, and `airstack up --stack full_my_estimator` brings the graph up from a clean checkout. + +## See also + +- [Interface Conventions Specification](../interface_conventions.md) — the citable contract (§2 odometry, TF frames) +- [Module Integration Checklist](../integration_checklist.md) — package-level integration steps +- [Perception Packages](index.md) — where estimators live in the layer +- [AirStack Stacks](../../../development/stacks.md) — stack anatomy, wiring.md, doctor diff --git a/docs/robot/autonomy/perception/index.md b/docs/robot/autonomy/perception/index.md index a0aed9b52..e1206760c 100644 --- a/docs/robot/autonomy/perception/index.md +++ b/docs/robot/autonomy/perception/index.md @@ -23,9 +23,7 @@ ros2 launch perception_bringup topic_keepalive.launch.xml ## Key Topics ### Outputs -- `/{robot_name}/odometry` - Best estimate of robot state (position, orientation, velocities) -- `/{robot_name}/pose` - Current robot pose -- `/{robot_name}/imu/data` - Processed IMU data +- `/{robot_name}/odometry_conversion/odometry` (`nav_msgs/Odometry`) - Best estimate of robot state (position, orientation, velocities). This is the v1 canonical state topic — see [Interface Conventions §2](../interface_conventions.md); plain `/{robot_name}/odometry` is the intended v2 name. ### Inputs - Raw sensor data from sensors layer (cameras, IMU, GPS, depth sensors) diff --git a/docs/robot/autonomy/system_architecture.md b/docs/robot/autonomy/system_architecture.md index ecdcfcfe0..ac8b1205b 100644 --- a/docs/robot/autonomy/system_architecture.md +++ b/docs/robot/autonomy/system_architecture.md @@ -1,6 +1,6 @@ # System Architecture -This document provides a comprehensive overview of the AirStack autonomy system architecture, data flow, and module interactions. +This document explains the AirStack autonomy architecture: how the layers relate, the two kinds of nodes the stack is built from, and how data and task goals flow through the system. ## Overview @@ -137,317 +137,124 @@ graph LR ## Detailed Layer Architecture -### Interface Layer - -**Purpose:** Abstract hardware/simulation and provide safety monitoring. +The exact topic names, message types, and QoS settings that connect the layers +are specified once, normatively, in the +[Interface Conventions Specification](interface_conventions.md) — the +subsections below describe each layer's *role* and link to its documentation +rather than restating that data. -```mermaid -graph TB - MAVROS[MAVROS Interface] - Safety[Safety Monitor] - RobotIF[Robot Interface] - - PX4[PX4 Flight Controller] --> MAVROS - MAVROS --> Safety - Safety --> RobotIF - - RobotIF -->|State| Perception - RobotIF -->|Safety Status| Behavior - - Control[Trajectory Controller] -->|Commands| MAVROS - MAVROS -->|Actuator Commands| PX4 -``` - -**Key Modules:** - -- `mavros_interface`: MAVLink communication with flight controller -- `drone_safety_monitor`: Safety checks and emergency handling -- `robot_interface`: High-level robot state abstraction - -**Topics:** +### Interface Layer -- **Published:** - - `/[robot]/interface/mavros/state` - - `/[robot]/interface/mavros/local_position/pose` - - `/[robot]/interface/battery_state` +The interface layer abstracts the flight controller behind a stable +command/status boundary: it converts controller setpoints into vehicle +commands (MAVROS ↔ PX4 in the trunk) and publishes vehicle state — arming, +flight mode, battery — back up to the rest of the stack. Nothing above this +layer talks to hardware directly, which is what makes simulation and real +vehicles interchangeable. -- **Subscribed:** - - `/[robot]/trajectory_controller/cmd_vel` +See the [Interface layer documentation](interface/index.md); the +command and status contracts are +[`control_setpoint` (§6)](interface_conventions.md#6-control_setpoint-controller-interface-command-onboard-only) +and the +[`interface_status` group (§7)](interface_conventions.md#7-interface_status-group-vehicle-state-out-of-the-interface-layer). ### Sensors Layer -**Purpose:** Process and calibrate sensor data. +The sensors layer wraps drivers and low-level processing (e.g. point-cloud +filtering) so that downstream layers consume calibrated, consistently named +streams instead of device-specific topics. -```mermaid -graph LR - Camera[Camera Sensors] --> ImgProc[Image Processing] - Stereo[Stereo Cameras] --> Disparity[Disparity Computation] - LiDAR[LiDAR] --> Filter[Point Cloud Filter] - - ImgProc --> Perception - Disparity --> Perception - Filter --> Perception -``` - -**Key Modules:** - -- `lidar_point_cloud_filter`: LiDAR point cloud filtering -- Sensor drivers and processors - -**Topics:** - -- **Published:** - - `/[robot]/sensors/[sensor_name]/image` - - `/[robot]/sensors/[sensor_name]/camera_info` - - `/[robot]/sensors/front_stereo/disparity` +See the [Sensors layer documentation](sensors/index.md) and the +[`sensors/*` naming convention (§1)](interface_conventions.md#1-sensors-sensor-naming-convention). ### Perception Layer -**Purpose:** Estimate robot state and understand environment. +The perception layer turns sensor streams into the robot's estimate of itself +and its surroundings: the primary state estimate (`odometry`) and depth / +point-cloud products consumed by the world models. The trunk default stereo +pipeline is `stereo_image_proc`; learned visual odometry (MAC-VO) is available +as the external [asm_macvo](https://github.com/castacks/asm_macvo) module +rather than in the trunk. -```mermaid -graph TB - subgraph "State Estimation" - VIO[Visual-Inertial Odometry] - Fusion[Sensor Fusion] - end - - subgraph "Environment Perception" - Depth[Depth Estimation] - Features[Feature Detection] - Tracking[Object Tracking] - end - - Sensors -->|Images + IMU| VIO - Sensors -->|Multi-sensor| Fusion - VIO --> Odometry[Odometry Output] - Fusion --> Odometry - - Sensors -->|Stereo| Depth - Sensors -->|Images| Features - Features --> Tracking - - Odometry --> Local - Odometry --> Global - Depth --> Local -``` - -**Key Modules:** - -- `stereo_image_proc`: Stereo disparity + point-cloud estimation (trunk default) -- `macvo_ros2`: MAC-VO learned visual odometry — **provided by the - [asm_macvo](https://github.com/castacks/asm_macvo) module** - (`airstack module add asm_macvo`), not trunk. When a stack includes its - launch file under the perception namespace it publishes - `/[robot]/perception/macvo/{odometry,point_cloud,disparity}`. - -**Topics:** - -- **Published:** - - `/[robot]/odometry` - Primary state estimate - - `/[robot]/perception/stereo_image_proc/disparity` - - `/[robot]/perception/stereo_image_proc/point_cloud` - -- **Subscribed:** - - `/[robot]/sensors/*/image` - - `/[robot]/sensors/*/camera_info` +See the [Perception layer documentation](perception/index.md) and the +[`odometry` contract (§2)](interface_conventions.md#2-odometry-primary-state-estimate). ### Local Layer -**Purpose:** Reactive obstacle avoidance and trajectory control. - -The local layer has three sub-layers: - -```mermaid -graph TB - subgraph "Local World Models" - Disparity[Disparity Expansion] - Graph[Disparity Graph] - CostMap[Cost Map] - - Disparity --> Graph - Graph --> CostMap - end - - subgraph "Local Planners" - DROAN[DROAN Planner] - TakeoffLanding[Takeoff/Landing] - - CostMap --> DROAN - end - - subgraph "Controllers" - TrajControl[Trajectory Controller] - PIDControl[PID Controller] - - DROAN --> TrajControl - TakeoffLanding --> TrajControl - TrajControl --> PIDControl - end - - Perception -->|Odometry| DROAN - Perception -->|Disparity| Disparity - Global -->|Global Plan| DROAN - - PIDControl -->|Commands| Interface -``` - -**Key Modules:** - -- **World Models:** - - - `disparity_expansion`: Obstacle detection from stereo - - `disparity_graph`: Graph-based obstacle representation - - `disparity_graph_cost_map`: Cost map generation - -- **Planners:** - - - `droan_local_planner`: DROAN obstacle avoidance - - `takeoff_landing_planner`: Specialized maneuvers - - `trajectory_library`: Trajectory generation utilities - -- **Controllers:** - - - `trajectory_controller`: Pure-pursuit tracking point / look-ahead management - - `pid_controller`: Cascaded PID producing attitude/thrust commands - -**Topics:** +The local layer is the reactive, short-horizon part of the stack, organized in +three sub-layers: **world models** (e.g. disparity expansion) maintain an +obstacle representation around the robot, **planners** (DROAN, takeoff/landing) +generate collision-free trajectories through it, and **controllers** +(trajectory controller, PID controller) track those trajectories at high rate +and hand setpoints to the interface layer. -- **Subscribed:** - - - `/[robot]/odometry` - - `/[robot]/global_plan` - - `/[robot]/sensors/front_stereo/disparity` - -- **Published:** - - - `/[robot]/trajectory_controller/trajectory_segment_to_add` - - `/[robot]/trajectory_controller/look_ahead` - - `/[robot]/trajectory_controller/tracking_point` - - `/[robot]/local/cost_map` +See the [Local layer documentation](local/index.md); the handoff between +planners and the trajectory controller is the +[`trajectory` group (§5)](interface_conventions.md#5-trajectory-group-the-trajectory-controllers-contract-onboard-only). ### Global Layer -**Purpose:** Strategic path planning and global mapping. - -```mermaid -graph TB - subgraph "Global World Models" - VDBMap[VDB Mapping] - Occupancy[Occupancy Grid] - end - - subgraph "Global Planners" - RandomWalk[Random Walk Explorer] - Exploration[Exploration Planner] - end - - Perception -->|Pose| VDBMap - Sensors -->|Point Clouds| VDBMap - VDBMap --> Occupancy - - Occupancy --> RandomWalk - Occupancy --> Exploration - GCS[GCS Task Goals] --> RandomWalk - GCS --> Exploration - - RandomWalk --> GlobalPlan[Global Plan] - Exploration --> GlobalPlan - GlobalPlan --> Local -``` - -**Key Modules:** - -- **World Models:** +The global layer is the strategic counterpart: it maintains a persistent 3D +map of everywhere the robot has been (VDB mapping) and decides where to go +next — exploration and global path planning — handing global plans down to the +local layer for execution. - - `vdb_mapping_ros2`: VDB-based 3D mapping - -- **Planners:** - - - `random_walk`: Random exploration planner (ExplorationTask executor) - - `exploration`: Frontier-based exploration planner (optional alternative) - -**Topics:** - -- **Subscribed:** - - - `/[robot]/odometry` - - `/[robot]/vdb_mapping/vdb_map_visualization` - -- **Published:** - - - `/[robot]/global_plan` - - `/[robot]/vdb_mapping/vdb_map_pointcloud` +See the [Global layer documentation](global/index.md) and the +[`global_plan` contract (§4)](interface_conventions.md#4-global_plan-global-waypoint-path). ### Behavior Layer -**Purpose:** Onboard safety supervision. Mission-level sequencing is driven +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 - Odom[State Estimate] --> SM[drone_safety_monitor] - SM -->|state_estimate_timed_out| Planners[Planners / Task Executors] - SM -->|safety command| Interface - - GCS[Ground Control Station] -->|Task goals| Tasks[Task Executors] - Tasks --> Global - Tasks --> Local -``` - -**Key Modules:** - -- `drone_safety_monitor`: Watches the state estimate for timeouts and issues - safety commands (onboard-only) +breaks (the `drone_safety_monitor` watches the state estimate and issues +safety commands when it times out). -**Topics:** - -- **Subscribed:** - - - `/[robot]/odometry` - -- **Published:** - - - `/[robot]/behavior/drone_safety_monitor/state_estimate_timed_out` - - `/[robot]/behavior/drone_safety_monitor/command` +See the [Behavior layer documentation](behavior/index.md) and the +[`safety` contract (§9)](interface_conventions.md#9-safety-safety-executive-onboard-only). ## Complete Data Flow ### Autonomous Flight Scenario -Here's the complete data flow for an autonomous flight with obstacle avoidance: +An autonomous flight is a [task cascade](tasks.md#task-cascade). The GCS +operator is an **action client**: they send a task goal (e.g. +`ExplorationTask`) to a global-layer task executor, which decides where to go +and delegates the flying to the local-layer task executor via `NavigateTask`. +The local planner feeds trajectory segments to the perpetual trajectory +controller, which produces setpoints for the interface layer. Each action +**result returns to the client that sent the goal** — `NavigateTask` results +to the global executor, and the top-level task result (with ~1 Hz feedback +along the way) to the GCS: ```mermaid sequenceDiagram - participant BEH as Behavior - participant GLO as Global Planner - participant LOC as Local Planner - participant CTL as Controller + participant GCS as GCS (action client) + participant GLO as Global Task Executor
(random_walk_planner) + participant LOC as Local Task Executor
(droan_gl) + participant CTL as Trajectory Controller participant IF as Interface participant HW as Hardware/Sim - Note over BEH: Mission: Navigate to waypoint - BEH->>GLO: Goal Position - GLO->>GLO: Plan global path - GLO->>LOC: Global Plan + GCS->>GLO: ExplorationTask goal + GLO->>GLO: Choose next goal point + GLO->>LOC: NavigateTask goal (global plan) loop Obstacle Avoidance - HW->>IF: Sensor Data - IF->>LOC: Disparity Image + HW->>LOC: Sensor data (via sensors + perception) LOC->>LOC: Detect obstacles LOC->>LOC: Generate local trajectory LOC->>CTL: Trajectory Segment - CTL->>CTL: Compute control commands - CTL->>IF: Velocity Commands + CTL->>CTL: Compute control setpoint + CTL->>IF: Control setpoint IF->>HW: Actuator Commands end - Note over CTL: Waypoint reached - CTL->>BEH: Completion notification - BEH->>BEH: Next waypoint or mission complete + Note over LOC: Goal reached + LOC-->>GLO: NavigateTask result + GLO->>GLO: Next goal point, or done + GLO-->>GCS: ExplorationTask result ``` ## Module Communication Patterns @@ -491,95 +298,19 @@ This enables: ## Coordinate Frames -### Frame Hierarchy - -```mermaid -graph TB - World[world] --> Map[map] - Map --> Odom[odom] - Odom --> BaseLink[base_link] - BaseLink --> BaseLinkStab[base_link_stabilized] - BaseLinkStab --> Camera[camera_link] - BaseLinkStab --> Lidar[lidar_link] - BaseLink --> LookAhead[look_ahead_point] -``` - -**Standard Frames:** - -- `world`: Fixed world frame -- `map`: Global map frame (may drift from world) -- `odom`: Odometry frame (continuous, may drift) -- `base_link`: Robot body frame -- `base_link_stabilized`: Stabilized body frame (yaw-only) -- `camera_link`: Camera sensor frame -- `look_ahead_point`: Trajectory tracking reference - -## Performance Characteristics - -### Typical Update Rates - -| Layer | Module | Rate | Latency | -|-------|--------|------|---------| -| Interface | MAVROS | 50 Hz | <5 ms | -| Sensors | Camera | 30 Hz | <10 ms | -| Sensors | Disparity | 15 Hz | <30 ms | -| Perception | VIO | 30 Hz | <20 ms | -| Local Planner | DROAN | 10 Hz | <50 ms | -| Local Controller | Trajectory | 50 Hz | <10 ms | -| Global Planner | Path | 1 Hz | <500 ms | -| Behavior | BT Tick | 10 Hz | <5 ms | - -### Resource Usage (Typical) - -| Component | CPU | Memory | GPU | -|-----------|-----|--------|-----| -| Full Stack | 60-80% | 4-6 GB | 20-40% | -| Perception | 15-20% | 500 MB | 10-20% | -| Local Planning | 10-15% | 300 MB | 5-10% | -| Global Planning | 5-10% | 200 MB | 0% | -| Simulation | 30-40% | 2-3 GB | 60-80% | - -## Module Integration Guidelines - -When adding a new module, follow these integration patterns: - -### 1. Determine Layer Placement - -Place module in appropriate layer based on its function: - -- Real-time obstacle avoidance? → Local planning -- State estimation? → Perception -- Path planning? → Global planning -- Mission logic? → Behavior - -### 2. Define Interfaces - -Specify input and output topics: - -- Use standard topics when available -- Create custom topics with appropriate namespaces -- Document expected message rates and latencies - -### 3. Configure Launch Integration - -Give the module its own canonical launch file and include it from the stack -entry file (`stacks//launch/*.launch.xml`) with: - -- Topic remapping (via launch arguments) -- Namespace configuration -- Parameter loading -- Conditional launching (if needed) - -### 4. Test Integration - -Verify: +The frame tree, units, and the ENU convention are specified normatively in +[Interface Conventions — TF frames and units](interface_conventions.md#tf-frames-and-units). +For the reasoning behind the conventions — including the NED↔ENU conversion at +the PX4/MAVROS boundary and Isaac Sim's FLU convention — see the +[Frame Conventions](../../development/intermediate/frame_conventions.md) +concept page. -- Topics connect correctly -- Data flows as expected -- Performance meets requirements -- Works with other modules +## Integrating a New Module -See [Integration Checklist](integration_checklist.md) for detailed steps. +When adding a new module to the stack — choosing its layer, defining its topic +interfaces, wiring it into a stack entry launch file, and verifying the +connections — follow the [Integration Checklist](integration_checklist.md), +which is the canonical step-by-step guide. ## Multi-Robot Architecture @@ -634,6 +365,7 @@ graph TB ## References +- [Interface Conventions Specification](interface_conventions.md) - Normative topic, action, and frame contracts - [Integration Checklist](integration_checklist.md) - Module integration guidelines - [AI Agent Guide](../../development/advanced/ai_agent_guide.md) - Guide for AI agents - [Layer Documentation](index.md) - Detailed layer descriptions diff --git a/docs/robot/autonomy/tasks.md b/docs/robot/autonomy/tasks.md index 75d15aecb..5ef549f92 100644 --- a/docs/robot/autonomy/tasks.md +++ b/docs/robot/autonomy/tasks.md @@ -41,6 +41,68 @@ geometry_msgs/Point current_position --- +### TakeoffTask + +**File:** `action/TakeoffTask.action` +**Action server:** `/{robot_name}/tasks/takeoff` +**Implemented by:** `takeoff_landing_planner` + +Take off to a target altitude. The goal is rejected if the robot is not armed, offboard control is not active, or the state estimate has timed out. + +#### Goal + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `target_altitude_m` | float32 | Altitude to climb to (m) | +| `velocity_m_s` | float32 | Ascent velocity (m/s) | + +#### Result + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `success` | bool | True if the target altitude was reached; false if rejected, canceled, or error | +| `message` | string | Completion reason | + +#### Feedback + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `status` | string | Task status string | +| `current_altitude_m` | float32 | Current altitude (m) | +| `target_altitude_m` | float32 | Target altitude (m) | + +--- + +### LandTask + +**File:** `action/LandTask.action` +**Action server:** `/{robot_name}/tasks/land` +**Implemented by:** `takeoff_landing_planner` + +Land the robot at its current position. + +#### Goal + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `velocity_m_s` | float32 | Descent velocity (m/s); `0.0` = use default from config | + +#### Result + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `success` | bool | True if the robot landed; false if canceled or error | +| `message` | string | Completion reason | + +#### Feedback + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `status` | string | Task status string | +| `current_altitude_m` | float32 | Current altitude (m) | + +--- + ### FixedTrajectoryTask **File:** `action/FixedTrajectoryTask.action` diff --git a/docs/robot/configuration/environment_variables.md b/docs/robot/configuration/environment_variables.md new file mode 100644 index 000000000..122c26157 --- /dev/null +++ b/docs/robot/configuration/environment_variables.md @@ -0,0 +1,90 @@ +# Environment Variable Reference (`.env`) + +The top-level [`.env`](https://github.com/castacks/AirStack/blob/main/.env) file sets Docker Compose **interpolation variables** — image tags, profile selection, replica counts, and per-container launch switches. These variables do **not** automatically become environment variables inside the containers: a variable only reaches a container if a compose file forwards it through an `environment:` entry (see each *Consumed by* column). `airstack up` reads `.env` automatically; launch-intent flags (`--sim`, `--robots`, `--stack`, ...) override it by exporting the same variables before compose runs. This page is the complete schema; the [Docker guide](../docker/index.md#environment-variables) summarizes the subset forwarded into robot containers, and the [Configuration overview](index.md) explains where non-compose configuration lives. + +## Project & Images + +These variables assemble every image tag as `${PROJECT_DOCKER_REGISTRY}/${PROJECT_NAME}:v${VERSION}_` (robot images additionally append `_${DOCKER_IMAGE_BUILD_MODE}`). + +| Variable | Purpose | Default | Consumed by | +| -------- | ------- | ------- | ----------- | +| `PROJECT_NAME` | Repository name for Docker images and part of every image tag | `"airstack"` | `image:` tags in all compose files (`robot/docker/`, `gcs/docker/`, `simulation/*/docker/`) | +| `VERSION` | Semver image version; bumped per release, so the value in `.env` is always the current release (e.g. `0.20.0-alpha.15`) | current release semver | `image:` tags in all compose files; CI version gate (`check-version-increment.yml`) | +| `DOCKER_IMAGE_BUILD_MODE` | Image-tag discriminator **only** — no Dockerfile consumes it. Keep `dev` (mounted code, built live); a real `prebuilt` workspace-baked stage is future work | `"dev"` | Tag suffix of the robot images (`robot/docker/docker-compose.yaml`) | +| `PROJECT_DOCKER_REGISTRY` | Registry to push/pull images from | `"airlab-docker.andrew.cmu.edu/airstack"` | `image:` tags in all compose files | +| `COMPOSE_PROFILES` | Default compose profiles when none are passed explicitly | `"desktop,isaac-sim"` | Docker Compose profile selection; rewritten by `airstack up --sim ` (swaps the simulator profile) and `--fleet` (heterogeneous fleets swap `desktop` for `fleet`) | + +## Launch Behavior + +| Variable | Purpose | Default | Consumed by | +| -------- | ------- | ------- | ----------- | +| `AUTOLAUNCH` | If `false`, containers spawn idle with no launch command (tmux session still created) | `"true"` | Container `command:` of every robot service, `isaac-sim`, `ms-airsim`, and `gcs` (each gates its tmux autolaunch on it) | +| `NUM_ROBOTS` | Number of robot containers to launch (compose replicas) | `"1"` | `deploy.replicas` of `robot-desktop`/`robot-offboard`; forwarded into `isaac-sim`, `ms-airsim`, and `gcs` containers (drone spawn count / peer list). Overridden by `airstack up --robots N`; derived from the fleet file with `--fleet` | +| `RECORD_BAGS` | Start the bag recorder node with the stack (see [Rosbags](../logging/rosbags.md)) | `"false"` | `robot_base` and `gcs` `environment:` → `bag_recorder_pid` via the logging bringup | + +## Isaac Sim + +All four are forwarded into (or read by the `command:` of) the `isaac-sim` service in `simulation/isaac-sim/docker/docker-compose.yaml`. + +| Variable | Purpose | Default | Consumed by | +| -------- | ------- | ------- | ----------- | +| `ISAAC_SIM_GUI` | USD scene path for the **non-standalone** launch path | `/isaac-sim/AirStack/simulation/isaac-sim/assets/scenes/simple_pegasus.scene.usd` | `ros2 launch isaacsim run_isaacsim.launch.py gui:=...` branch of the `isaac-sim` command (only when `ISAAC_SIM_USE_STANDALONE` is not `true`) | +| `ISAAC_SIM_USE_STANDALONE` | `true` = launch Isaac Sim via a standalone Python script; `false` = load the `ISAAC_SIM_GUI` USD file via `run_isaacsim.launch.py` | `"true"` | Branch selector in the `isaac-sim` container command | +| `ISAAC_SIM_SCRIPT_NAME` | Standalone launch script, resolved under `/AirStack/simulation/isaac-sim/launch_scripts/`. The default spawns exactly **one** drone; multi-robot needs `example_multi_px4_pegasus_launch_script.py` (auto-selected by `airstack up --robots N>1`) and fleets use `fleet_spawn.py` (auto-selected by `--fleet`) | `"example_one_px4_pegasus_launch_script.py"` | Standalone branch of the `isaac-sim` container command | +| `PLAY_SIM_ON_START` | Start the sim **playing** instead of paused (`airstack up --no-play` to come up paused) | `"true"` | `isaac-sim` `environment:` → the Pegasus launch scripts (`pegasus_app.py` and the example scripts); passed as `play_sim_on_start:=` in the USD launch path | + +## Robot Identity & Description + +| Variable | Purpose | Default | Consumed by | +| -------- | ------- | ------- | ----------- | +| `ROBOT_NAME_MAP_CONFIG_FILE` | Mapping file (in `robot/docker/robot_name_map/`) that resolves each container to `ROBOT_NAME` + `ROS_DOMAIN_ID` — see [Robot Identity](../docker/robot_identity.md) | `"default_robot_name_map.yaml"` | `robot_base` `environment:` → `resolve_robot_name.py`, run by `robot/docker/.bashrc` at container startup | +| `URDF_FILE` | Robot description, relative to the workspace `robot_descriptions/` install. Swapped automatically by `airstack up --sim isaac\|airsim` to the matching sensor URDF | `robot_descriptions/iris/urdf/iris_with_sensors.pegasus.robot.urdf` | `robot_base` `environment:` → `autonomy_bringup/launch/robot.launch.xml` (robot state publisher) | +| `DEBUG_RVIZ` | If `true`, launches RViz alongside the robot | `"false"` | `robot_base` `environment:` → `desktop_bringup/launch/robot.launch.xml` | + +## Ports + +| Variable | Purpose | Default | Consumed by | +| -------- | ------- | ------- | ----------- | +| `OFFBOARD_BASE_PORT` | Base UDP port for offboard (API-out) MAVLink streams; offset per robot so multi-agent FCU communication doesn't collide | `14540` | `robot_base` `environment:` → `interface_bringup/launch/interface.launch.py` (MAVROS `fcu_url` calculation) | +| `ONBOARD_BASE_PORT` | Base UDP port for onboard MAVLink streams, offset per robot | `14580` | Same as above | + +## Variables Exported by `airstack up` (Not Set in `.env`) + +`airstack up` parses its launch-intent flags (`parse_launch_intent` / `apply_launch_intent` in `airstack.sh`) and exports these before invoking compose. Set them by flag, not by editing `.env` — though explicit env / `--env-file` values take precedence (leaf-value precedence, with an override banner for fleet conflicts). + +| Variable | Set by | Purpose | Consumed by | +| -------- | ------ | ------- | ----------- | +| `AIRSTACK_STACK_DIR` | `--stack ` (always exported; no stack = `/root/AirStack/stacks/full_default`) | Container path of the stack folder whose entry launch file defines the autonomy topology — see [Stacks](../../development/stacks.md) | `robot_base` `environment:` → `autonomy_bringup/launch/robot.launch.xml` dispatch | +| `AIRSTACK_STACK_ENTRY` | `--stack :` (default `stack`) | Entry launch file name: `launch/.launch.xml` (split stacks use `onboard`/`offboard`) | Same as above | +| `FLEET_CONFIG_FILE` | `--fleet ` | Container path of the fleet file (`/root/AirStack/config/fleets/...`); empty = legacy `robot_name_map` resolution — see [Fleets](../../development/fleets.md) | `robot_base` and `isaac-sim` `environment:` → `robot/docker/.bashrc` (per-container identity/stack via `tools/fleet/resolve_fleet.py`) and `fleet_spawn.py` | +| `NUM_ROBOTS` | `--robots N`, or derived from the fleet file with `--fleet` (mutually exclusive flags) | Overrides the `.env` value above | As in Launch Behavior | +| `PLAY_SIM_ON_START` | `--play` / `--no-play` | Overrides the `.env` value above | As in Isaac Sim | +| `AUTOLAUNCH` | `--no-autolaunch` | Overrides the `.env` value above (sets `false`) | As in Launch Behavior | +| `ISAAC_SIM_HEADLESS` | `--headless` | Run Isaac Sim without a window | `isaac-sim` `environment:` (default `false`; forced `true` by the `isaac-sim-livestream` service) | +| `MS_AIRSIM_HEADLESS` | `--headless` | Run the UE4 binary off-screen | `ms-airsim` `environment:` | +| `QT_QPA_PLATFORM` | `--headless` (sets `offscreen`) | Keeps Qt tools (RViz etc.) from requiring a display | `robot_base` `environment:` | +| `COMPOSE_PROFILES` | `--sim isaac\|airsim\|simple` (swaps the simulator profile; `simple` also drops `desktop`), `--fleet` (heterogeneous: swaps `desktop` for `fleet`) | Overrides the `.env` value above | Docker Compose profile selection | +| `URDF_FILE` | `--sim isaac\|airsim` | Swaps to the simulator-matched sensor URDF | As in Robot Identity & Description | +| `ISAAC_SIM_SCRIPT_NAME` | `--robots` (one ↔ multi example script) and `--fleet` (→ `fleet_spawn.py`); an explicit env value always wins | Overrides the `.env` value above | As in Isaac Sim | +| `ISAAC_SIM_SCENE`, `ISAAC_SIM_STAGE_SCALE`, `MS_AIRSIM_SCENE` | `--scene ` via `simulation/resolve_scene.py` (`simulation/scenes.yaml` catalog) | Scene selection for the active simulator — see [Scenes](../../simulation/scenes.md) | `isaac-sim` / `ms-airsim` `environment:` → the launch scripts / `entrypoint.sh` | + +## Notable Optional Variables + +These appear commented-out in `.env` (or are read by compose with a built-in default) and are documented in full on their own pages. + +| Variable | Purpose | Documented in | +| -------- | ------- | ------------- | +| `ISAAC_SIM_SCENE`, `ISAAC_SIM_STAGE_SCALE` | Direct scene override / stage scale for cm-authored stages (normally set via `--scene`) | [Scenes](../../simulation/scenes.md) | +| `MS_AIRSIM_SCENE`, `MS_AIRSIM_ENV_DIR`, `MS_AIRSIM_BINARY_PATH`, `MS_AIRSIM_HEADLESS`, `MS_AIRSIM_PX4_START_DELAY` | UE4 scene fetching/selection, binary override, off-screen rendering, PX4 start delay | [MS AirSim Docker](../../simulation/ms-airsim/docker.md) | +| `BAG_STORAGE_PATH` | Host directory mounted at `/bags` on the `l4t` profile (default `/media/airlab/Storage/airstack_collection`) | [Rosbags](../logging/rosbags.md) | +| `LOG_CONFIG` | Bag recorder topic-selection file in `logging_bringup/config/` (default `log.yaml`) | [Rosbags](../logging/rosbags.md) | +| `ISAAC_SIM_FOLLOW_CAM`, `ISAAC_SIM_FOLLOW_CAM_OFFSET`, `ISAAC_SIM_FOLLOW_CAM_LIGHT` | Viewport follow-camera: drone domain id to chase, world-frame offset, headlight for unlit interiors | [Isaac Sim](../../simulation/isaac_sim/index.md) (read by `pegasus_app.py`) | +| `SIM_IP` | Address robot containers use to reach the simulator (default `172.31.0.200`; every sim service binds this fixed address) | `robot/docker/docker-compose.yaml`, `simulation/isaac-sim/docker/docker-compose.yaml` | +| `FCU_URL` | MAVROS flight-controller URL on real hardware (default `/dev/ttyTHS4:115200` on `l4t`); desktop/sim derive it from the base ports instead | `robot/docker/docker-compose.yaml` (`robot-l4t`) | +| `CACHE_TAG` | Floating Docker-layer-cache image tag (default `cache`) — CI only | [CI/CD](../../development/intermediate/testing/ci_cd.md) | + +## See Also + +- [Docker Services — Environment Variables](../docker/index.md#environment-variables) — the subset forwarded into robot containers +- [Robot Configuration](index.md) — where non-compose configuration lives (stacks, module parameters, fleets) +- [AirStack CLI](../../development/beginner/airstack-cli/index.md) — the `airstack up` flags that export these variables diff --git a/docs/robot/configuration/index.md b/docs/robot/configuration/index.md index 8a00eea96..9a591348a 100644 --- a/docs/robot/configuration/index.md +++ b/docs/robot/configuration/index.md @@ -1,48 +1,66 @@ # Robot Configuration -Configure robot-specific parameters, sensor calibrations, and system settings for AirStack deployment. +Configure robot identity, stack selection, and module parameters for AirStack deployment. -## Overview +## Where Configuration Actually Lives -Robot configuration includes: +There is no single configuration file — settings live at the level they affect: -- **Robot Identity**: Unique identification (name, ROS_DOMAIN_ID) -- **Sensor Configuration**: Calibration parameters and topic mappings -- **Network Settings**: Communication and connectivity -- **Hardware Parameters**: Platform-specific settings (Jetson, VOXL) -- **Autonomy Parameters**: Behavior and performance tuning +| Level | Location | What it controls | +| ----- | -------- | ---------------- | +| Compose / containers | top-level `.env` | Image tags, `NUM_ROBOTS`, `AUTOLAUNCH`, sim selection, bag recording | +| Stack (launch topology) | `stacks//launch/*.launch.xml` | Which modules run and how their topics are wired (launch args, remaps) | +| Module parameters | each package's `config/*.yaml` (`robot/ros_ws/src//...//config/`) | Algorithm-specific ROS 2 parameters | +| Fleet era (RFC #380) | `config/vehicles/` and `config/fleets/` | Vehicle definitions; who exists, which vehicle, which stack, which ground hosts | +| Robot identity | `robot/docker/robot_name_map/` | Container/hostname → `ROBOT_NAME` + `ROS_DOMAIN_ID` mapping | -## Configuration Files +## Stack Selection -### Environment Variables +The autonomy topology is selected by a **stack** — stacks are the only dispatch +(the legacy `AUTONOMY_ROLE` role dispatch was removed). `airstack up --stack +[:]` exports `AIRSTACK_STACK_DIR` (the container path of the stack +folder, `/root/AirStack/stacks/`) and `AIRSTACK_STACK_ENTRY` (the entry +launch file name, default `stack`). Unset, the trunk reference stack +`full_default` is used. -Key environment variables configured in the top-level `.env` file: +See [Stacks](../../development/stacks.md) for the reference stacks and how to +create your own. + +## Environment Variables + +Key variables in the top-level `.env` file (compose-level configuration): ```bash # Launch Configuration -AUTOLAUNCH=true -# 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 +AUTOLAUNCH="true" # false = spawn idle containers with no launch command # Multi-robot -NUM_ROBOTS=1 +NUM_ROBOTS="1" # Number of robot containers (compose replicas) # Robot identity mapping (name → ROBOT_NAME + ROS_DOMAIN_ID) -ROBOT_NAME_MAP_CONFIG_FILE=default_robot_name_map.yaml +ROBOT_NAME_MAP_CONFIG_FILE="default_robot_name_map.yaml" + +# Logging +RECORD_BAGS="false" # Start the bag recorder node (see Logging docs) ``` +Stack selection (`AIRSTACK_STACK_DIR`, `AIRSTACK_STACK_ENTRY`) and fleet +selection (`FLEET_CONFIG_FILE`) are exported by `airstack up --stack` / +`--fleet` rather than set by hand in `.env`. + `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 +The full table of variables forwarded into the robot containers is in the +[Docker guide](../docker/index.md#environment-variables). -Module-specific parameters in YAML files: +## ROS 2 Parameters -**Location**: each module package's own `config/` directory -(`robot/ros_ws/src//...//config/`); stacks override them via -launch arguments in their entry files (`stacks//launch/*.launch.xml`). +Module-specific parameters live in YAML files in each module package's own +`config/` directory (`robot/ros_ws/src//...//config/`); stacks +override them via launch arguments in their entry files +(`stacks//launch/*.launch.xml`). **Example** (`robot/ros_ws/src/sensors/lidar_point_cloud_filter/config/lidar_point_cloud_filter.yaml`): ```yaml @@ -62,308 +80,17 @@ See: [Robot Identity Guide](../docker/robot_identity.md) **Key Settings**: -- **ROBOT_NAME**: Namespace for all topics (`/robot1/...`) +- **ROBOT_NAME**: Namespace for all topics (`/robot_1/...`) - **ROS_DOMAIN_ID**: Isolate ROS 2 communication (0-101) -- **Hostname**: Unique network identifier - -## Sensor Configuration - -### Camera Configuration - -Configure camera parameters: - -```yaml -camera: - ros__parameters: - frame_id: camera_link - width: 1280 - height: 720 - fps: 30 - encoding: rgb8 -``` - -### IMU Configuration - -IMU calibration and orientation: - -```yaml -imu: - ros__parameters: - frame_id: imu_link - accel_stddev: 0.01 - gyro_stddev: 0.005 - orientation_covariance: [0.01, 0, 0, - 0, 0.01, 0, - 0, 0, 0.01] -``` - -### Depth Sensor Configuration - -Depth camera/stereo parameters: - -```yaml -depth_camera: - ros__parameters: - frame_id: depth_camera_link - min_range: 0.5 - max_range: 10.0 - fov_horizontal: 87.0 - fov_vertical: 58.0 -``` - -## Autonomy Configuration - -### Local Planning Parameters - -Tune local planner behavior: - -```yaml -local_planner: - ros__parameters: - planning_horizon: 5.0 - max_velocity: 2.0 - max_acceleration: 1.0 - obstacle_margin: 0.5 -``` - -### Global Planning Parameters - -Configure global planner: - -```yaml -global_planner: - ros__parameters: - planning_rate: 1.0 - goal_tolerance: 0.5 - path_resolution: 0.1 -``` - -### Controller Parameters - -Trajectory controller tuning: - -```yaml -trajectory_controller: - ros__parameters: - kp_position: 1.0 - kd_position: 0.5 - kp_velocity: 0.8 - max_thrust: 20.0 -``` - -## Network Configuration - -### WiFi Configuration - -For onboard computer (Jetson/VOXL): - -```bash -# /etc/netplan/01-netcfg.yaml -network: - version: 2 - wifis: - wlan0: - dhcp4: yes - access-points: - "YourSSID": - password: "YourPassword" -``` - -### Static IP (Optional) - -For reliable communication: - -```yaml -network: - version: 2 - ethernets: - eth0: - addresses: [192.168.1.100/24] - gateway4: 192.168.1.1 - nameservers: - addresses: [8.8.8.8, 8.8.4.4] -``` - -## Platform-Specific Configuration - -### NVIDIA Jetson - -Power mode settings: - -```bash -# Maximum performance -sudo nvpmodel -m 0 -sudo jetson_clocks - -# Balanced mode -sudo nvpmodel -m 2 -``` - -Configure fan control: - -```bash -# /etc/systemd/system/jetson-fan.service -[Unit] -Description=Jetson Fan Control - -[Service] -Type=simple -ExecStart=/usr/bin/jetson_fan.py - -[Install] -WantedBy=multi-user.target -``` - -### ModalAI VOXL - -VOXL-specific configuration via `voxl-configure-*` tools: - -```bash -# Configure cameras -voxl-configure-cameras - -# Configure MPA -voxl-configure-mpa - -# Configure vision -voxl-configure-vision -``` - -## Parameter Tuning Workflow - -### 1. Baseline Configuration - -Start with default parameters from reference implementation. - -### 2. Simulation Testing - -Test parameter changes in Isaac Sim: - -```bash -# Launch with custom parameters -ros2 launch my_module_bringup my_module.launch.xml param_file:=config/tuned_params.yaml -``` - -### 3. HITL Validation - -Validate on hardware-in-the-loop setup before field deployment. - -See: [HITL Testing](../../real_world/HITL/index.md) - -### 4. Field Tuning - -Fine-tune based on real-world performance: - -- Monitor performance metrics -- Adjust parameters incrementally -- Document changes and rationale -- Test thoroughly after each change - -## Configuration Management - -### Version Control - -Track configuration files in Git: - -```bash -git add robot/ros_ws/src/*/config/*.yaml -git commit -m "Tune planner parameters for outdoor operation" -``` - -### Robot-Specific Configs - -For multiple robots with different configurations: - -``` -robot/ros_ws/src/my_module/config/ -├── default.yaml # Default parameters -├── robot1.yaml # Robot 1 overrides -├── robot2.yaml # Robot 2 overrides -└── outdoor.yaml # Environment-specific -``` - -Load appropriate config: - -```xml - - - -``` - -### Configuration Validation - -Validate configuration before deployment: - -```python -#!/usr/bin/env python3 -import yaml - -def validate_config(config_file): - with open(config_file) as f: - config = yaml.safe_load(f) - - # Check required parameters exist - assert 'max_velocity' in config - assert 'planning_horizon' in config - - # Check parameter ranges - assert 0 < config['max_velocity'] <= 5.0 - assert config['planning_horizon'] > 0 - - print(f"✓ Configuration {config_file} is valid") - -if __name__ == "__main__": - validate_config("config/my_params.yaml") -``` - -## Dynamic Reconfiguration - -Some parameters can be changed at runtime without restart: - -```bash -# Get current parameter value -ros2 param get /my_node my_parameter - -# Set new parameter value -ros2 param set /my_node my_parameter 2.5 - -# Dump all parameters -ros2 param dump /my_node > current_params.yaml -``` - -## Troubleshooting - -**Parameter changes not taking effect**: - -- Verify parameter file path in launch file -- Check for typos in parameter names -- Rebuild package if C++ parameters changed -- Restart nodes after parameter changes - -**Invalid parameter values**: - -- Check parameter validation in node code -- Review error messages for allowed ranges -- Verify YAML syntax (indentation, types) - -**Configuration conflicts**: - -- Check for multiple parameter files being loaded -- Verify launch file parameter precedence -- Use `ros2 param dump` to see actual loaded values - -## Best Practices -- **Document parameters**: Add comments in YAML files -- **Use reasonable defaults**: Safe, conservative values -- **Validate inputs**: Check parameter ranges in code -- **Version control**: Track configuration changes -- **Test incrementally**: Change one parameter at a time -- **Keep backups**: Save known-good configurations +Both are resolved at container startup by +`robot/docker/robot_name_map/resolve_robot_name.py` from the mapping file +selected by `ROBOT_NAME_MAP_CONFIG_FILE`. ## See Also - [Robot Identity](../docker/robot_identity.md) - Configuring robot identification +- [Stacks](../../development/stacks.md) - Stack folders and entry launch files +- [Fleets](../../development/fleets.md) - Fleet files, vehicles, and placement - [Autonomy Modes](../autonomy_modes.md) - Different operation modes -- [HITL Testing](../../real_world/HITL/index.md) - Testing configuration on hardware - [Integration Checklist](../autonomy/integration_checklist.md) - Module configuration requirements diff --git a/docs/robot/docker/index.md b/docs/robot/docker/index.md index 781b38aeb..c775625e5 100644 --- a/docs/robot/docker/index.md +++ b/docs/robot/docker/index.md @@ -42,7 +42,7 @@ robot_base (robot-base-docker-compose.yaml) | **ROS workspace** | `common/ros_packages` is mounted into the ROS 2 workspace `src/common` | | **Shell config** | `.bashrc` and `inputrc` are bind-mounted so the developer experience is consistent across rebuilds | | **Bags** | `robot/bags/` is mounted at `/bags` for recording and playback | -| **Launch variables** | All `*_LAUNCH_PACKAGE` / `*_LAUNCH_FILE` environment variables are forwarded from the host `.env` file | +| **Launch variables** | `LAUNCH_PACKAGE` is set per service in the compose files; the launch file is `robot.launch.xml` (hardcoded in the compose command) | ## Platform Profiles @@ -55,7 +55,7 @@ Select a profile by passing `--profile ` to `docker compose` (or via the ` This is the DEFAULT profile as specified by `COMPOSE_PROFILES=desktop` in the root level `.env` file. It will run by default if no profile is passed or with `airstack up --profile desktop`. Use this profile when developing or running simulations on an x86-64 Linux workstation. - **Image:** `...:v_robot-x86-64_` -- **Base image:** `nvidia/cuda:13.0.2-base-ubuntu22.04` +- **Base image:** `nvidia/cuda:13.0.2-base-ubuntu24.04` - **Network:** isolated `airstack_network` bridge (prevents conflicts with other developers on the same LAN) - **SSH:** host ports `2223–2243` forwarded to port `22` in each container, one port per robot replica - **Scaling:** `NUM_ROBOTS` env var controls the number of replicas (default: 1) @@ -71,7 +71,7 @@ Runs with `airstack up --profile simple`. Extends `desktop` with `SIM_TYPE=simpl 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) +- **Base image:** `ubuntu:24.04` (no CUDA; VOXL has its own compute stack) - **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` @@ -81,7 +81,7 @@ Runs with `airstack up --profile voxl` (service `robot-voxl-onboard`). Use this Runs with `airstack up --profile l4t`. Use this profile when deploying on an NVIDIA Jetson device running L4T. - **Image:** `...:v_robot-l4t_` -- **Base image:** `nvcr.io/nvidia/l4t-jetpack:r36.4.0` +- **Base image:** `dustynv/ros:jazzy-ros-base-r36.4.0-cu128-24.04` (via `Dockerfile.l4t-stack-base`) - **Network:** `host` - **IPC:** `host` (needed for shared-memory DDS transports on Jetson) - **Storage:** `/media/airlab/Storage/airstack_collection` mounted at `/bags` @@ -105,7 +105,7 @@ Key variables are set in the project's `.env` file and forwarded into the contai | `PROJECT_NAME` | Project / image name | | `NUM_ROBOTS` | Number of robot replicas (desktop only, default `1`) | | `AUTOLAUNCH` | Whether to auto-start the ROS 2 stack on container start (default `true`) | -| `ROBOT_LAUNCH_PACKAGE` / `ROBOT_LAUNCH_FILE` | Top-level ROS 2 launch target | +| `LAUNCH_PACKAGE` | Top-level ROS 2 launch package: `desktop_bringup` (adds RViz; desktop/sim) or `autonomy_bringup` (real robots / headless). The launch file is always `robot.launch.xml`, hardcoded in the compose command | | `OFFBOARD_BASE_PORT` / `ONBOARD_BASE_PORT` | MAVLink UDP port base values (desktop/sim only) | | `ROBOT_NAME_MAP_CONFIG_FILE` | YAML mapping config used to resolve a name to `ROBOT_NAME` and `ROS_DOMAIN_ID` (default: `default_robot_name_map.yaml`) | | `DEBUG_RVIZ` | If `true`, launches RViz alongside the robot via `desktop_bringup/robot.launch.xml` (default: `false`) | diff --git a/docs/robot/index.md b/docs/robot/index.md index 3446dc08f..0030c64bf 100644 --- a/docs/robot/index.md +++ b/docs/robot/index.md @@ -100,19 +100,19 @@ airstack up --profile voxl # ModalAI VOXL ## Common Topics -Standard ROS 2 topics used across the autonomy stack: +The canonical topic/service/action names, message types, and QoS profiles for every interchange point live in the versioned [Interface Conventions Specification](autonomy/interface_conventions.md). A few examples (types per the spec): -| Topic | Type | Description | +| Topic (example) | Type | Description | |-------|------|-------------| -| `/$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/TrajectoryXYZVYaw | Direct trajectory commands | -| `/$ROBOT_NAME/trajectory_controller/look_ahead` | airstack_msgs/Odometry | Look-ahead point for planning | +| `/$ROBOT_NAME/odometry_conversion/odometry` | [nav_msgs/msg/Odometry](https://docs.ros.org/en/rolling/p/nav_msgs/interfaces/msg/Odometry.html) | Primary state estimate | +| `/$ROBOT_NAME/global_plan` | [nav_msgs/msg/Path](https://docs.ros.org/en/rolling/p/nav_msgs/interfaces/msg/Path.html) | Global waypoint path | +| `/$ROBOT_NAME/trajectory_controller/trajectory_override` | airstack_msgs/msg/TrajectoryXYZVYaw | Direct trajectory commands | **See also:** +- [Interface Conventions Specification](autonomy/interface_conventions.md) - The full topic/service/action reference - [System Architecture](autonomy/system_architecture.md) - Complete data flow diagrams -- [Integration Checklist](autonomy/integration_checklist.md) - Full topic reference +- [Integration Checklist](autonomy/integration_checklist.md) - Step-by-step module integration ## Next Steps diff --git a/docs/robot/logging/data_offloading.md b/docs/robot/logging/data_offloading.md index ac09ea510..0187a037d 100644 --- a/docs/robot/logging/data_offloading.md +++ b/docs/robot/logging/data_offloading.md @@ -1,323 +1,23 @@ # Data Offloading -Automatic transfer of ROS bags, logs, and other data from robots to ground stations or storage servers. Critical for managing limited onboard storage and enabling post-mission analysis. +The supported workflow for offloading ROS bags and logs from robots is the +storage-tools pair — [storage_tools_server](https://github.com/castacks/storage_tools_server) +on the receiving machine and storage_tools_device on the robot. Setup and usage +are documented in the [Real World Data Offloading guide](../../real_world/data_offloading/index.md). -## Overview +## Quick Manual Copy -Data offloading in AirStack: +For a one-off manual transfer, plain rsync works. Bags land in `robot/bags/` +on the host (mounted at `/bags` in the robot container); on Jetson (`l4t` +profile) they land in `${BAG_STORAGE_PATH}` (default +`/media/airlab/Storage/airstack_collection`): -- **Automatic synchronization** when robot connects to network -- **Bandwidth-aware transfers** to avoid interfering with operations -- **Compression** to reduce transfer time -- **Verification** to ensure data integrity -- **Storage management** to free onboard space after successful transfer - -## Architecture - -```mermaid -graph LR - A[Robot Onboard Storage] -->|WiFi/Cellular| B[Ground Station] - A -->|SSH/rsync| C[Storage Server] - B --> D[Archive Storage] - C --> D -``` - -Data flows from robot to either: - -1. **Ground Control Station** during or after mission -2. **Storage Server** for long-term archival -3. **Cloud Storage** for team-wide access - -## Quick Start - -### Basic Offload - -Manual offload via rsync: -```bash -# From robot to ground station -rsync -avz --progress /opt/airstack/bags/ user@groundstation:/data/robot1/ -``` - -### Automatic Offload - -Configure automatic offloading by setting up: - -1. **SSH key authentication** (no password required) -2. **Offload script** that runs on network connection -3. **Cron job** or systemd timer for periodic sync - -## Configuration - -### Setting Up SSH Keys - -On robot: -```bash -ssh-keygen -t ed25519 -f ~/.ssh/id_offload -ssh-copy-id -i ~/.ssh/id_offload.pub user@groundstation -``` - -### Offload Script - -Create `/opt/airstack/scripts/offload_data.sh`: - -```bash -#!/bin/bash -# Offload data from robot to ground station - -ROBOT_NAME=${ROBOT_NAME:-"robot1"} -GROUND_STATION="user@groundstation" -REMOTE_DIR="/data/${ROBOT_NAME}" -LOCAL_BAGS="/opt/airstack/bags" -LOCAL_LOGS="/opt/airstack/logs" - -# Check if ground station is reachable -if ! ping -c 1 -W 5 groundstation > /dev/null 2>&1; then - echo "Ground station not reachable, skipping offload" - exit 0 -fi - -# Sync bags -echo "Syncing bags..." -rsync -avz --progress --remove-source-files \ - ${LOCAL_BAGS}/ \ - ${GROUND_STATION}:${REMOTE_DIR}/bags/ - -# Sync logs -echo "Syncing logs..." -rsync -avz --progress \ - ${LOCAL_LOGS}/ \ - ${GROUND_STATION}:${REMOTE_DIR}/logs/ - -echo "Offload complete" -``` - -Make executable: -```bash -chmod +x /opt/airstack/scripts/offload_data.sh -``` - -### Automatic Scheduling - -**Option 1: Cron (periodic)** -```bash -# Run every hour -0 * * * * /opt/airstack/scripts/offload_data.sh >> /var/log/offload.log 2>&1 -``` - -**Option 2: Systemd (on network up)** - -Create `/etc/systemd/system/airstack-offload.service`: -```ini -[Unit] -Description=AirStack Data Offload -After=network-online.target -Wants=network-online.target - -[Service] -Type=oneshot -ExecStart=/opt/airstack/scripts/offload_data.sh -User=airstack -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target -``` - -Enable: -```bash -sudo systemctl enable airstack-offload.service -sudo systemctl start airstack-offload.service -``` - -## Storage Management - -### Monitoring Disk Space - -Check available space: -```bash -df -h /opt/airstack -``` - -Monitor during mission: -```bash -watch -n 10 "df -h /opt/airstack | tail -1" -``` - -### Automatic Cleanup - -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 "*.mcap" -mtime +7 -delete -``` - -### Storage Quotas - -On Jetson/VOXL with limited storage: - -- **Reserve 10GB minimum** free space for system -- **Set bag size limits** in recording configuration -- **Prioritize critical topics** over full recording -- **Enable automatic offload** to prevent filling disk - -## Bandwidth Optimization - -### Compression - -Compress before transfer: -```bash -# Compress bags -cd /opt/airstack/bags -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/ -``` - -### Transfer Scheduling - -Avoid transferring during active operations: - -- **Pre-flight**: Offload before mission -- **Post-flight**: Offload after mission completes -- **Off-hours**: Schedule large transfers overnight -- **Bandwidth limiting**: Use `rsync --bwlimit=1000` (KB/s) - -### Delta Sync - -Only transfer new/changed files: -```bash -rsync -avz --update --progress /opt/airstack/bags/ user@groundstation:/data/robot1/bags/ -``` - -## Security Considerations - -- **Use SSH keys** instead of passwords -- **Restrict key permissions**: `chmod 600 ~/.ssh/id_offload` -- **Limit SSH key scope** using `command=` in authorized_keys -- **Use VPN** for remote offloading over internet -- **Encrypt sensitive data** before transfer - -## Multi-Robot Scenarios - -For multiple robots offloading to same ground station: - -### Unique Robot Directories - -```bash -ROBOT_NAME="robot1" -REMOTE_DIR="/data/${ROBOT_NAME}" -rsync -avz /opt/airstack/bags/ user@groundstation:${REMOTE_DIR}/bags/ -``` - -### Coordinated Transfers - -Prevent bandwidth saturation: - -```bash -# Robot 1: offload immediately after landing -# Robot 2: offload 10 minutes after Robot 1 -# Robot 3: offload 10 minutes after Robot 2 -``` - -Use file locks to serialize: -```bash -flock /var/lock/offload.lock /opt/airstack/scripts/offload_data.sh -``` - -## Ground Station Setup - -### Receiving Data - -On ground station, create directory structure: -```bash -sudo mkdir -p /data/{robot1,robot2,robot3}/{bags,logs} -sudo chown -R user:user /data -``` - -### Archive Management - -Organize by date and mission: -```bash -/data/ -├── robot1/ -│ ├── bags/ -│ │ ├── 2024-03-17_mission1/ -│ │ ├── 2024-03-18_mission2/ -│ │ └── ... -│ └── logs/ -└── robot2/ - └── ... -``` - -Automated archival script: -```bash -#!/bin/bash -# Archive and compress old mission data - -SOURCE="/data/robot1/bags" -ARCHIVE="/archive/robot1" -DAYS_OLD=30 - -find ${SOURCE} -name "*.mcap" -mtime +${DAYS_OLD} -exec tar -czf {}.tar.gz {} \; -delete -mv ${SOURCE}/*.tar.gz ${ARCHIVE}/ -``` - -## Troubleshooting - -**Transfer fails with SSH error**: - -- Verify SSH keys are set up correctly -- Test manual SSH connection: `ssh user@groundstation` -- Check network connectivity - -**Transfer is too slow**: - -- Use compression: `tar -czf` before transfer -- Check network bandwidth and latency -- Use `--bwlimit` to avoid saturating connection -- Transfer during off-peak hours - -**Disk full on robot**: - -- Manually offload immediately -- Delete old/unnecessary bags -- Reduce recording topic list -- Increase offload frequency - -**Data corruption during transfer**: - -- Use rsync's built-in checksums -- Verify file sizes after transfer -- Use `--checksum` flag for rsync -- Implement post-transfer validation script - -## Monitoring and Alerts - -### Check Offload Status - -View offload logs: -```bash -journalctl -u airstack-offload.service -f -``` - -### Disk Space Alerts - -Alert when disk is >80% full: ```bash -#!/bin/bash -USAGE=$(df /opt/airstack | tail -1 | awk '{print $5}' | sed 's/%//') -if [ $USAGE -gt 80 ]; then - echo "WARNING: Disk usage at ${USAGE}% on $(hostname)" | mail -s "Disk Alert" ops@example.com -fi +rsync -avz --progress /media/airlab/Storage/airstack_collection/ user@groundstation:/data/robot_1/bags/ ``` ## See Also +- [Real World Data Offloading](../../real_world/data_offloading/index.md) - The supported storage-tools workflow +- [Logging Overview](index.md) - Where bags land and how recording is configured - [ROS Bags](rosbags.md) - Recording data -- [Logging Overview](index.md) - AirStack logging infrastructure -- [Real World Data Offloading](../../real_world/data_offloading/index.md) - Field-specific offload procedures -- [Robot Configuration](../configuration/index.md) - Configuring robot identity and network \ No newline at end of file diff --git a/docs/robot/logging/rosbags.md b/docs/robot/logging/rosbags.md index 73e4c135a..2877d4f11 100644 --- a/docs/robot/logging/rosbags.md +++ b/docs/robot/logging/rosbags.md @@ -2,43 +2,15 @@ ROS bags are the primary method for recording data during robot operation. They capture ROS 2 topics for later analysis, debugging, and algorithm development. -## Overview +## Managed Recording -ROS bag recording in AirStack: - -- **Automatic recording** via bag_recorder_pid package -- **Selective topic recording** to manage storage -- **Integration with logging infrastructure** -- **Support for onboard and offboard recording** - -## Quick Start - -### Manual Recording - -Record specific topics: -```bash -ros2 bag record /robot1/odometry /robot1/camera/image_raw -``` - -Record all topics: -```bash -ros2 bag record -a -``` - -Record with storage limit: -```bash -ros2 bag record -a --max-bag-size 1000000000 # ~1 GB per file (bytes) -``` - -### Managed Recording - -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. +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 (`std_msgs/Bool`, in the robot namespace). See the [Logging overview](index.md) for the full workflow. ## Configuration ### Topic Selection -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: +Choose topics based on mission objectives. The recorder's config file (selected with `LOG_CONFIG`, in `logging_bringup/config/`, default `log.yaml`) groups topics into named **sections**; relative topic names are prefixed with the robot namespace: **Minimal set** (state and commands): ```yaml @@ -63,14 +35,9 @@ sections: - /tf_static ``` -### Storage Management - -On resource-constrained platforms (Jetson, VOXL): - -- **Limit bag size**: Use `--max-bag-size` to split large bags -- **Selective recording**: Only record topics needed for mission -- **Compression**: Use compressed image topics when available -- **Automatic offload**: Configure [data offloading](data_offloading.md) to free space +Section `args` are passed through to `ros2 bag record`, so storage limits like +`-b 4000000000` (split at ~4 GB) or `--max-cache-size` go there — see the +shipped `log.yaml` for a working example. ## Storage Locations @@ -79,53 +46,23 @@ On resource-constrained platforms (Jetson, VOXL): - Persists across container restarts ### Hardware Deployment -- Jetson (`l4t` profile): `/media/airlab/Storage/airstack_collection` on the device is mounted at `/bags` +- Jetson (`l4t` profile): `${BAG_STORAGE_PATH}` on the device (default `/media/airlab/Storage/airstack_collection`) is mounted at `/bags` - The recorder's target directory is its `output_dir` parameter (set in `logging.launch.xml`, default `/bags`) -## Playback and Analysis +## Manual Recording and Playback -### Basic Playback +For one-off captures the standard CLI works as usual inside the robot container — see the [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) for the full reference: -Play back a recorded bag: ```bash +ros2 bag record /robot_1/odometry_conversion/odometry /robot_1/global_plan ros2 bag play path/to/bagfile -``` - -Play at different speed: -```bash -ros2 bag play path/to/bagfile --rate 0.5 # Half speed -``` - -Play in loop: -```bash -ros2 bag play path/to/bagfile --loop -``` - -### Bag Information - -Get bag metadata: -```bash ros2 bag info path/to/bagfile ``` -Example output: -``` -Files: state_20260317_143000/state_20260317_143000_0.mcap -Bag size: 1.2 GB -Storage id: mcap -Duration: 300.5s -Start: Mar 17 2024 14:30:00.123 -End: Mar 17 2024 14:35:00.623 -Messages: 45123 -Topic information: - Topic: /robot1/odometry | Type: nav_msgs/msg/Odometry | Count: 3005 | Serialization Format: cdr - Topic: /robot1/camera/image_raw/compressed | Type: sensor_msgs/msg/CompressedImage | Count: 1500 | Serialization Format: cdr - ... -``` - -### Extract Specific Topics +Topics are namespaced by robot name (`/robot_1/...` with the default robot +name map). -Convert to a new bag with only specific topics using `ros2 bag convert` with an output spec: +To extract specific topics into a new bag, use `ros2 bag convert` with an output spec: ```bash ros2 bag convert -i input_bag -o out_spec.yaml ``` @@ -137,74 +74,13 @@ output_bags: topics: [/robot_1/odometry_conversion/odometry, /robot_1/global_plan] ``` -## Common Workflows - -### Debug Mission Issues - -1. Record full topic set during mission -2. Play back locally in simulation -3. Analyze behavior with rviz or custom tools -4. Iterate on algorithms offline - -### Algorithm Development - -1. Record sensor data in real environment -2. Play back during development -3. Test new algorithms against real data -4. Validate before hardware deployment - -### Performance Analysis - -1. Record timestamped topics -2. Analyze latencies and frequencies -3. Identify bottlenecks -4. Optimize performance - -## Best Practices - -- **Test recording setup** before important missions -- **Monitor disk space** during operation -- **Use compression** for image topics -- **Document bag contents** with descriptive names -- **Archive important bags** with mission metadata -- **Regular cleanup** of old/unnecessary bags - -## Troubleshooting - -**Bag recording fails to start**: - -- Check disk space availability -- Verify write permissions to bag directory -- Check if bag_recorder_pid is running - -**Bags too large**: - -- Use topic filtering to record only necessary data -- Enable compression for image topics -- Use `--max-bag-size` to split files -- Consider reducing sensor publishing rates - -**Playback issues**: - -- Ensure ROS 2 version matches recording system -- Check topic names and types match expectations -- Verify clock synchronization settings - -**Missing data in bags**: - -- Verify topics were being published during recording -- Check bag info to confirm topics recorded -- Ensure recording started before mission began - ## Integration with Data Offloading -For automatic transfer of bags from robot to ground station or storage server: - -See: [Data Offloading Guide](data_offloading.md) +For transferring bags from robot to a storage server, see the [Data Offloading Guide](data_offloading.md). ## See Also - [bag_recorder_pid Package](../../../common/ros_packages/logging/bag_recorder_pid/README.md) - Automatic recording package - [Data Offloading](data_offloading.md) - Transfer bags from robot - [Logging Overview](index.md) - AirStack logging infrastructure -- [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) \ No newline at end of file +- [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) diff --git a/docs/robot/static_transforms/index.md b/docs/robot/static_transforms/index.md index 4748c2e3a..33bb3a95a 100644 --- a/docs/robot/static_transforms/index.md +++ b/docs/robot/static_transforms/index.md @@ -1,10 +1,22 @@ # 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. +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` frame — so that every module can transform data into a common frame without per-module configuration. On the robot they come from two places, both set up by the launch preamble in [`autonomy_bringup/launch/robot.launch.xml`](https://github.com/castacks/AirStack/blob/develop/robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml), which runs for every stack. -## Frame Conventions +## The Frame Tree -Each robot has its own **map** frame that represents the starting position of the robot. -The **map** frame is expected to be in ENU (East-North-Up) convention. +- **`world`** — fixed origin +- **`map`** — child of `world` (static identity); ENU, meters — the planning/state frame +- **`base_link`** — the body frame, positioned by the state estimate; sensor frames hang off it via the URDF -The robot is in the **base_link** frame. +## `world` → `map` + +The preamble publishes a **static identity transform** `world → map` via `tf2_ros static_transform_publisher` (node `world_to_map_broadcaster`). `world` is the fixed origin; `map` is the ENU planning/state frame in which odometry pose, the global plan, and the map live. The dynamic `map → base_link` relationship comes from the state estimate, not a static transform. + +## Sensor Extrinsics (URDF) + +Sensor and body-frame extrinsics are published by `robot_state_publisher` from the robot URDF: the preamble includes `robot_descriptions/launch/robot_state_publisher.launch.py` with the `urdf_file` launch argument, which defaults to the `URDF_FILE` environment variable. `URDF_FILE` is set in the top-level `.env` (default: `robot_descriptions/iris/urdf/iris_with_sensors.pegasus.robot.urdf`) and passed into the robot container by `robot/docker/robot-base-docker-compose.yaml`; fleet files can override it per robot via their vehicle definition. + +## See Also + +- [Interface Conventions — TF frames and units](../autonomy/interface_conventions.md#tf-frames-and-units) — the canonical frame table (`world`, `map`, `base_link`) and unit conventions +- [Frame Conventions](../../development/intermediate/frame_conventions.md) — the concept page explaining the frame tree diff --git a/docs/simulation/index.md b/docs/simulation/index.md index feea93260..3378258e5 100644 --- a/docs/simulation/index.md +++ b/docs/simulation/index.md @@ -74,7 +74,6 @@ Isaac Sim is our primary simulation platform, offering: - [Isaac Sim Overview](isaac_sim/index.md) - [Pegasus Scene Setup](isaac_sim/pegasus_scene_setup.md) -- [Ascent SITL Extension](isaac_sim/ascent_sitl_extension.md) - [Export from Unreal Engine](isaac_sim/export_stages_from_unreal.md) ### Microsoft AirSim (legacy) (Unreal Engine) diff --git a/docs/simulation/isaac_sim/ascent_node.png b/docs/simulation/isaac_sim/ascent_node.png deleted file mode 100644 index bb3020b90..000000000 Binary files a/docs/simulation/isaac_sim/ascent_node.png and /dev/null differ diff --git a/docs/simulation/isaac_sim/ascent_sitl_extension.md b/docs/simulation/isaac_sim/ascent_sitl_extension.md deleted file mode 100644 index 1f759b85e..000000000 --- a/docs/simulation/isaac_sim/ascent_sitl_extension.md +++ /dev/null @@ -1,23 +0,0 @@ -# AirLab AirStack Extension - -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) - -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 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: - -![Ascent OmniGraph Node](ascent_node.png) - -## 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 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: - -![TMUX Panel](tmux_panel.png) \ No newline at end of file diff --git a/docs/simulation/isaac_sim/container_workflows.md b/docs/simulation/isaac_sim/container_workflows.md new file mode 100644 index 000000000..da4bd1db1 --- /dev/null +++ b/docs/simulation/isaac_sim/container_workflows.md @@ -0,0 +1,272 @@ +# Isaac Sim Container Workflows + +Working procedures for the Isaac Sim container: launching it in different modes, accessing it, iterating on scenes and scripts, managing images, and troubleshooting. + +**Reference for this container:** [Isaac Sim Docker Configuration](docker.md) — file structure, service architecture, launch configuration, environment variables, networking, GPU access, and volume mounts. + +## Launch Modes + +Isaac Sim supports multiple launch modes: + +### 1. Standard Launch (ROS 2 Integration) + +Default mode with ROS 2 bridge: + +```bash +airstack up isaac-sim +``` + +**What happens:** + +- Launches Isaac Sim with ROS 2 bridge +- Runs the launch script named by `ISAAC_SIM_SCRIPT_NAME` (or, with `ISAAC_SIM_USE_STANDALONE=false`, opens the USD in `ISAAC_SIM_GUI`) +- Publishes sensor topics to ROS 2 +- Optionally auto-plays simulation (via `PLAY_SIM_ON_START`) + +### 2. Standalone Python Launch + +Launch with standalone Python script: + +```bash +ISAAC_SIM_USE_STANDALONE=true ISAAC_SIM_SCRIPT_NAME=my_script.py airstack up isaac-sim +``` + +**Use cases:** + +- Custom simulation logic +- Advanced scene setup +- Programmatic control + +### 3. GUI-Only Mode (`isaac-sim-gui`) + +The `isaac-sim-gui` compose service opens Isaac Sim's **full GUI editor** +(`runapp.sh` — no Pegasus launch script, no drones) for USD/scene editing on +any asset: + +```bash +airstack up --profile isaac-sim-gui isaac-sim-gui +``` + +(The service sits behind its own `isaac-sim-gui` profile, so both the +`--profile` flag and the service name are needed; it does not conflict with +the one-active-simulator rule.) + +**Use cases:** + +- Authoring/editing USD scenes and assets (see [Pegasus Scene Setup](pegasus_scene_setup.md)) +- Inspecting assets without bringing up the robot stack + +**Not for flying:** the service is deliberately **not** on `airstack_network` +(`networks: !reset null` in `simulation/isaac-sim/docker/docker-compose.yaml`), +so no DDS traffic reaches the robot containers and no PX4 is launched. To fly, +use `airstack up --sim isaac`. + +### Example overrides + +```bash +# Launch without a window (headless) +airstack up --sim isaac --headless + +# Don't auto-play simulation +airstack up isaac-sim --no-play + +# Launch with standalone script +ISAAC_SIM_USE_STANDALONE=true ISAAC_SIM_SCRIPT_NAME=custom_scene.py airstack up isaac-sim +``` + +See the [environment variables table](docker.md#environment-variables) for the full list of configuration variables. + +## Omniverse Credentials + +Isaac Sim requires NVIDIA Omniverse credentials. + +### Setup + +1. **Create credentials file:** + ```bash + cp simulation/isaac-sim/docker/omni_pass_TEMPLATE.env simulation/isaac-sim/docker/omni_pass.env + ``` + +2. **Edit with your credentials:** + ```bash + # omni_pass.env + OMNI_USER=your_username + OMNI_PASS=your_password + ``` + +3. **File is git-ignored** (don't commit credentials!) + +### Getting Credentials + +1. Create account at [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/) +2. Use your NVIDIA account credentials +3. Required for downloading assets and extensions + +## Accessing Isaac Sim + +### Via GUI (Default) + +If `DISPLAY` is configured: + +```bash +airstack up isaac-sim +# Isaac Sim GUI opens on host display +``` + +### Via tmux Session + +Connect to the container and attach to tmux: + +```bash +# Connect to container +airstack connect isaac-sim + +# Attach to Isaac Sim tmux session +tmux a -t isaac +``` + +**Useful tmux commands:** + +- `Ctrl-b d` - Detach from session +- `Ctrl-b [` - Scroll mode (arrow keys to scroll logs) +- `Ctrl-c` - Stop Isaac Sim + +### Via Streaming (Headless) + +For remote access, use Isaac Sim streaming: + +**WebRTC streaming:** +```bash +# Inside container +./runheadless.webrtc.sh +``` + +Access via web browser. + +## Development Workflow + +### Iterating on Scenes + +1. **Edit scene files** on host (they're mounted): + ``` + simulation/isaac-sim/assets/scenes/my_scene.usd + ``` + +2. **Reload in Isaac Sim:** + - File → Open + - Or restart container with new scene + +3. **Changes persist** (files on host) + +### Testing Standalone Scripts + +1. **Create script:** + ``` + simulation/isaac-sim/launch_scripts/test_script.py + ``` + +2. **Launch:** + ```bash + ISAAC_SIM_USE_STANDALONE=true ISAAC_SIM_SCRIPT_NAME=test_script.py airstack up isaac-sim + ``` + +3. **View output:** + ```bash + airstack logs isaac-sim # tmux pane output is mirrored to docker logs + airstack connect isaac-sim # attach to the tmux session interactively + ``` + +### Debugging + +**Enable debug logging:** + +Edit `user.config.json` to increase log verbosity. + +**View logs:** + +```bash +# Container logs +airstack logs isaac-sim + +# Isaac Sim logs +ls $HOME/docker/isaac-sim/logs/ +``` + +## Image Management + +### Pulling Pre-built Images + +```bash +# Login to AirLab registry +docker login airlab-docker.andrew.cmu.edu + +# Pull Isaac Sim image +docker compose -f simulation/isaac-sim/docker/docker-compose.yaml pull +``` + +### Building from Source + +```bash +# Build Isaac Sim image +docker compose -f simulation/isaac-sim/docker/docker-compose.yaml build + +# Build with no cache +docker compose -f simulation/isaac-sim/docker/docker-compose.yaml build --no-cache +``` + +**Note:** Isaac Sim base image is large (~20GB). Initial build takes time. + +## Troubleshooting + +**Isaac Sim won't start:** + +- Check GPU: `nvidia-smi` on host +- Verify NVIDIA Container Toolkit: `docker run --rm --gpus all nvidia/cuda:11.8.0-base-ubuntu22.04 nvidia-smi` +- Check disk space: `df -h` (need 25GB+ free) +- Review logs: `airstack logs isaac-sim` + +**GUI not displaying:** + +- Check `DISPLAY`: `echo $DISPLAY` (should be `:0` or `:1`) +- Allow X11: `xhost +local:docker` +- Verify X11 socket mounted: Check docker-compose volumes + +**ROS 2 topics not visible:** + +- Verify containers on same network: `docker network inspect airstack_network` +- Check ROS 2 domain IDs match +- Inspect DDS: `fastdds.xml` configuration +- Test connection: `ros2 topic list` in Isaac Sim container + +**`rclpy` / `_rclpy_pybind11` warnings when starting Kit with `python.sh`:** + +!!! note "Why this happens: Kit-Python vs system Python ABI" + Jazzy’s `setup.bash` puts **Python 3.12** ROS packages on `PYTHONPATH`. Isaac’s `python.sh` uses **Kit Python (~3.10)**. Importing system `rclpy` from the wrong interpreter causes ABI errors in the log (topics from Omnigraph may still work). + + Standalone launch uses `PYTHONPATH="$ISAAC_SIM_PYTHONPATH"` in the **tmux** command (`$$ISAAC_SIM_PYTHONPATH` in `docker-compose.yaml` so Compose does not treat it as a host variable). See container `.bashrc` and `docker-compose.yaml`: it drops `lib/python3.12/site-packages` and appends the bridge’s internal `rclpy` path. + +**Performance issues:** + +- Reduce scene complexity +- Lower physics timestep +- Disable raytracing (Settings → Rendering) +- Close other GPU-intensive applications + +**Omniverse login fails:** + +- Verify credentials in `omni_pass.env` +- Check network connectivity +- Ensure NVIDIA account is active + +**Extension not loading:** + +- Verify `user.config.json` enables extension +- Check extension path in volume mounts +- Review Isaac Sim logs for extension errors + +## See Also + +- [Isaac Sim Docker Configuration](docker.md) - Container reference: files, services, environment variables, mounts +- [Isaac Sim Overview](index.md) - Isaac Sim capabilities and features +- [Pegasus Scene Setup](pegasus_scene_setup.md) - Creating custom scenes +- [Docker Workflow](../../development/beginner/airstack-cli/docker_usage.md) - General Docker operations diff --git a/docs/simulation/isaac_sim/content_browser.png b/docs/simulation/isaac_sim/content_browser.png deleted file mode 100644 index 656c85624..000000000 Binary files a/docs/simulation/isaac_sim/content_browser.png and /dev/null differ diff --git a/docs/simulation/isaac_sim/docker.md b/docs/simulation/isaac_sim/docker.md index c65710f80..486aa32b1 100644 --- a/docs/simulation/isaac_sim/docker.md +++ b/docs/simulation/isaac_sim/docker.md @@ -1,6 +1,8 @@ # Isaac Sim Docker Configuration -Isaac Sim runs in a Docker container with NVIDIA GPU support and full integration with the AirStack ecosystem. +Isaac Sim runs in a Docker container with NVIDIA GPU support and full integration with the AirStack ecosystem. This page is the **reference** for the container: file structure, service architecture, launch configuration, environment variables, networking, GPU access, and volume mounts. + +**Working procedures:** [Isaac Sim Container Workflows](container_workflows.md) — launch modes, credentials setup, accessing Isaac Sim, development workflow, image management, and troubleshooting. ## File Structure @@ -30,77 +32,17 @@ The Isaac Sim service is defined in `simulation/isaac-sim/docker/docker-compose. | **ROS 2 Bridge** | Native ROS 2 topic publishing/subscribing | | **GPU Acceleration** | NVIDIA GPU for rendering and physics | -## Launch Modes - -Isaac Sim supports multiple launch modes: - -### 1. Standard Launch (ROS 2 Integration) - -Default mode with ROS 2 bridge: - -```bash -airstack up isaac-sim -``` - -**What happens:** - -- Launches Isaac Sim with ROS 2 bridge -- Runs the launch script named by `ISAAC_SIM_SCRIPT_NAME` (or, with `ISAAC_SIM_USE_STANDALONE=false`, opens the USD in `ISAAC_SIM_GUI`) -- Publishes sensor topics to ROS 2 -- Optionally auto-plays simulation (via `PLAY_SIM_ON_START`) - -### 2. Standalone Python Launch - -Launch with standalone Python script: - -```bash -ISAAC_SIM_USE_STANDALONE=true ISAAC_SIM_SCRIPT_NAME=my_script.py airstack up isaac-sim -``` - -**Use cases:** - -- Custom simulation logic -- Advanced scene setup -- Programmatic control - -### 3. GUI-Only Mode (`isaac-sim-gui`) - -The `isaac-sim-gui` compose service opens Isaac Sim's **full GUI editor** -(`runapp.sh` — no Pegasus launch script, no drones) for USD/scene editing on -any asset: - -```bash -airstack up --profile isaac-sim-gui isaac-sim-gui -``` - -(The service sits behind its own `isaac-sim-gui` profile, so both the -`--profile` flag and the service name are needed; it does not conflict with -the one-active-simulator rule.) - -**Use cases:** - -- Authoring/editing USD scenes and assets (see [Scene Setup](scene_setup.md)) -- Inspecting assets without bringing up the robot stack - -**Not for flying:** the service is deliberately **not** on `airstack_network` -(`networks: !reset null` in `simulation/isaac-sim/docker/docker-compose.yaml`), -so no DDS traffic reaches the robot containers and no PX4 is launched. To fly, -use `airstack up --sim isaac`. - ## Launch Configuration -The container command in docker-compose.yaml: +The container command in `simulation/isaac-sim/docker/docker-compose.yaml` (excerpt — see the compose file for the full command): ```yaml command: > bash -c " tmux new -d -s isaac; if [ $$AUTOLAUNCH = 'true' ]; then - if [ \"${ISAAC_SIM_USE_STANDALONE}\" = 'true' ]; then - tmux send-keys -t isaac 'PYTHONPATH="$$ISAAC_SIM_PYTHONPATH" /isaac-sim/python.sh /isaac-sim/AirStack/simulation/isaac-sim/launch_scripts/${ISAAC_SIM_SCRIPT_NAME} --ext-folder ~/.local/share/ov/data/documents/Kit/shared/exts' ENTER - else - tmux send-keys -t isaac 'ros2 launch isaacsim run_isaacsim.launch.py install_path:=/isaac-sim gui:=\"${ISAAC_SIM_GUI}\" play_sim_on_start:=\"${PLAY_SIM_ON_START}\"' ENTER - fi + ... # standalone: python.sh + ISAAC_SIM_SCRIPT_NAME + # otherwise: ros2 launch isaacsim run_isaacsim.launch.py fi; sleep infinity" ``` @@ -112,6 +54,8 @@ command: > 3. Chooses standalone or ROS 2 mode based on `ISAAC_SIM_USE_STANDALONE` 4. Keeps container alive with `sleep infinity` +For the launch mode recipes (standard, standalone script, GUI-only editor), see [Container Workflows → Launch Modes](container_workflows.md#launch-modes). + ## Environment Variables Key variables for Isaac Sim configuration: @@ -131,18 +75,7 @@ Key variables for Isaac Sim configuration: `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 a window (headless) -airstack up --sim isaac --headless - -# Don't auto-play simulation -airstack up isaac-sim --no-play - -# Launch with standalone script -ISAAC_SIM_USE_STANDALONE=true ISAAC_SIM_SCRIPT_NAME=custom_scene.py airstack up isaac-sim -``` +For example command-line overrides of these variables, see [Container Workflows → Launch Modes](container_workflows.md#launch-modes). ## Networking @@ -180,6 +113,20 @@ deploy: nvidia-smi ``` +### Multi-GPU Setup + +For multi-GPU systems: + +```yaml +deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0', '1'] # Use specific GPUs + capabilities: [gpu] +``` + ## Volume Mounts Isaac Sim container mounts several directories: @@ -216,7 +163,7 @@ Enables GUI display on host. - ../extensions/PegasusSimulator/extensions/pegasus.simulator:/isaac-sim/.local/share/ov/data/documents/Kit/shared/exts/pegasus.simulator/:rw ``` -Mounts the Pegasus multi-rotor simulator extension. +Mounts the Pegasus multi-rotor simulator extension. Custom Isaac Sim extensions follow the same pattern: place the extension in `simulation/isaac-sim/extensions/`, mount it in docker-compose.yaml, and enable it in `user.config.json`. ### AirStack Code @@ -235,232 +182,11 @@ Mounts entire AirStack repository for access to scenes, scripts, and launch file **user.config.json:** Enables Pegasus extension and other custom settings. -## Omniverse Credentials - -Isaac Sim requires NVIDIA Omniverse credentials. - -### Setup - -1. **Create credentials file:** - ```bash - cp simulation/isaac-sim/docker/omni_pass_TEMPLATE.env simulation/isaac-sim/docker/omni_pass.env - ``` - -2. **Edit with your credentials:** - ```bash - # omni_pass.env - OMNI_USER=your_username - OMNI_PASS=your_password - ``` - -3. **File is git-ignored** (don't commit credentials!) - -### Getting Credentials - -1. Create account at [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/) -2. Use your NVIDIA account credentials -3. Required for downloading assets and extensions - -## Accessing Isaac Sim - -### Via GUI (Default) - -If `DISPLAY` is configured: - -```bash -airstack up isaac-sim -# Isaac Sim GUI opens on host display -``` - -### Via tmux Session - -Connect to the container and attach to tmux: - -```bash -# Connect to container -airstack connect isaac-sim - -# Attach to Isaac Sim tmux session -tmux a -t isaac -``` - -**Useful tmux commands:** - -- `Ctrl-b d` - Detach from session -- `Ctrl-b [` - Scroll mode (arrow keys to scroll logs) -- `Ctrl-c` - Stop Isaac Sim - -### Via Streaming (Headless) - -For remote access, use Isaac Sim streaming: - -**Native streaming:** -```bash -# Inside container -./runheadless.native.sh -``` - -Connect with [Omniverse Streaming Client](https://docs.omniverse.nvidia.com/streaming-client/latest/user-manual.html). - -**WebRTC streaming:** -```bash -# Inside container -./runheadless.webrtc.sh -``` - -Access via web browser. - -## Development Workflow - -### Iterating on Scenes - -1. **Edit scene files** on host (they're mounted): - ``` - simulation/isaac-sim/assets/scenes/my_scene.usd - ``` - -2. **Reload in Isaac Sim:** - - File → Open - - Or restart container with new scene - -3. **Changes persist** (files on host) - -### Testing Standalone Scripts - -1. **Create script:** - ``` - simulation/isaac-sim/launch_scripts/test_script.py - ``` - -2. **Launch:** - ```bash - ISAAC_SIM_USE_STANDALONE=true ISAAC_SIM_SCRIPT_NAME=test_script.py airstack up isaac-sim - ``` - -3. **View output:** - ```bash - airstack logs isaac-sim # tmux pane output is mirrored to docker logs - airstack connect isaac-sim # attach to the tmux session interactively - ``` - -### Debugging - -**Enable debug logging:** - -Edit `user.config.json` to increase log verbosity. - -**View logs:** - -```bash -# Container logs -airstack logs isaac-sim - -# Isaac Sim logs -ls $HOME/docker/isaac-sim/logs/ -``` - -## Image Management - -### Pulling Pre-built Images - -```bash -# Login to AirLab registry -docker login airlab-docker.andrew.cmu.edu - -# Pull Isaac Sim image -docker compose -f simulation/isaac-sim/docker/docker-compose.yaml pull -``` - -### Building from Source - -```bash -# Build Isaac Sim image -docker compose -f simulation/isaac-sim/docker/docker-compose.yaml build - -# Build with no cache -docker compose -f simulation/isaac-sim/docker/docker-compose.yaml build --no-cache -``` - -**Note:** Isaac Sim base image is large (~20GB). Initial build takes time. - -## Troubleshooting - -**Isaac Sim won't start:** - -- Check GPU: `nvidia-smi` on host -- Verify NVIDIA Container Toolkit: `docker run --rm --gpus all nvidia/cuda:11.8.0-base-ubuntu22.04 nvidia-smi` -- Check disk space: `df -h` (need 25GB+ free) -- Review logs: `airstack logs isaac-sim` - -**GUI not displaying:** - -- Check `DISPLAY`: `echo $DISPLAY` (should be `:0` or `:1`) -- Allow X11: `xhost +local:docker` -- Verify X11 socket mounted: Check docker-compose volumes - -**ROS 2 topics not visible:** - -- Verify containers on same network: `docker network inspect airstack_network` -- Check ROS 2 domain IDs match -- Inspect DDS: `fastdds.xml` configuration -- Test connection: `ros2 topic list` in Isaac Sim container - -**`rclpy` / `_rclpy_pybind11` warnings when starting Kit with `python.sh`:** - -- Jazzy’s `setup.bash` puts **Python 3.12** ROS packages on `PYTHONPATH`. Isaac’s `python.sh` uses **Kit Python (~3.10)**. Importing system `rclpy` from the wrong interpreter causes ABI errors in the log (topics from Omnigraph may still work). -- Standalone launch uses `PYTHONPATH="$ISAAC_SIM_PYTHONPATH"` in the **tmux** command (`$$ISAAC_SIM_PYTHONPATH` in `docker-compose.yaml` so Compose does not treat it as a host variable). See container `.bashrc` and `docker-compose.yaml`: it drops `lib/python3.12/site-packages` and appends the bridge’s internal `rclpy` path. - -**Performance issues:** - -- Reduce scene complexity -- Lower physics timestep -- Disable raytracing (Settings → Rendering) -- Close other GPU-intensive applications - -**Omniverse login fails:** - -- Verify credentials in `omni_pass.env` -- Check network connectivity -- Ensure NVIDIA account is active - -**Extension not loading:** - -- Verify `user.config.json` enables extension -- Check extension path in volume mounts -- Review Isaac Sim logs for extension errors - -## Advanced Configuration - -### Custom Extensions - -Add custom Isaac Sim extensions: - -1. Place extension in `simulation/isaac-sim/extensions/` -2. Mount in docker-compose.yaml -3. Enable in `user.config.json` - -### Multi-GPU Setup - -For multi-GPU systems: - -```yaml -deploy: - resources: - reservations: - devices: - - driver: nvidia - device_ids: ['0', '1'] # Use specific GPUs - capabilities: [gpu] -``` - -### Persistent Nucleus Server - -For team collaboration, set up persistent Nucleus server: - -Edit `omniverse.toml` with your Nucleus server URL. +**omniverse.toml:** Omniverse settings. For team collaboration with a persistent Nucleus server, edit it with your Nucleus server URL. ## See Also +- [Isaac Sim Container Workflows](container_workflows.md) - Working procedures: launch modes, access, development, troubleshooting - [Isaac Sim Overview](index.md) - Isaac Sim capabilities and features - [Pegasus Scene Setup](pegasus_scene_setup.md) - Creating custom scenes - [Simulation Overview](../index.md) - Main simulation documentation diff --git a/docs/simulation/isaac_sim/export_stages_from_unreal.md b/docs/simulation/isaac_sim/export_stages_from_unreal.md index 273022e8d..37d5ec880 100644 --- a/docs/simulation/isaac_sim/export_stages_from_unreal.md +++ b/docs/simulation/isaac_sim/export_stages_from_unreal.md @@ -9,7 +9,10 @@ Generally, Unreal Engine environments can be found on Epic Games' [Fab Marketpla The below video explains how to export an Unreal Engine environment to an Isaac Sim stage. - + + +!!! warning "Two settings to change from the video" + Unlike what the video shows, set the export **Up Axis to Z-up** and the **scale to meters**. AirStack and Isaac Sim work in Z-up meters (see [Frame Conventions](../../development/intermediate/frame_conventions.md)); exporting this way avoids the unit/orientation fix-ups later on this page. You can save this file as `[YOUR_ENVIRONMENT_NAME].stage.usd`. @@ -24,9 +27,11 @@ Omniverse doesn't perform well with large amounts of vegetation. Anything with c That said you can still achieve photorealism by substituting complex geometries for high quality textures. Isaac seems to do fine with high quality textures. -**Optimization:** After exporting, edit the file with [USD Composer](https://docs.omniverse.nvidia.com/composer/latest/index.html) and run the [Scene Optimizer extension](https://docs.omniverse.nvidia.com/extensions/latest/ext_scene-optimizer.html) for faster performance. USD Composer can be installed via [Omniverse Launcher](https://docs.omniverse.nvidia.com/launcher/latest/index.html). +**Decals don't export:** Unreal Engine Decals do not export well to USD, so any decorations applied as decals — paint markings, road lines, dirt, puddles, and similar surface details — won't come across. If those details matter for your scene, bake them into the surface textures instead. + +**Optimization:** After exporting, edit the file with [USD Composer](https://docs.omniverse.nvidia.com/composer/latest/index.html) and run the [Scene Optimizer extension](https://docs.omniverse.nvidia.com/extensions/latest/ext_scene-optimizer.html) for faster performance. USD Composer / USD Explorer can be installed from NVIDIA's current distribution channel (the Omniverse Launcher has been discontinued). -**Verify the Scale:** The Omniverse exporter exports in centimeters, but Isaac Sim natively works in meters. For consistency, follow these steps to [change the scene units to be meters](https://forums.developer.nvidia.com/t/how-to-change-units-of-the-grid-from-centimeters-to-meters/301285#:~:text=Find%20the%20%E2%80%9CMeters%20Per%20Unit%E2%80%9D%20property%20and%20set%20it%20to%201%20for%20meters). +**Verify the Scale:** Isaac Sim natively works in meters. If you set the export scale to meters as instructed above, this is already correct — but if a stage was exported in centimeters (the exporter's old default), follow these steps to [change the scene units to be meters](https://forums.developer.nvidia.com/t/how-to-change-units-of-the-grid-from-centimeters-to-meters/301285#:~:text=Find%20the%20%E2%80%9CMeters%20Per%20Unit%E2%80%9D%20property%20and%20set%20it%20to%201%20for%20meters). To check the scale of the scene, you can add a cube in Isaac Sim and compare it to the exported scene. The cube is 1m x 1m x 1m. @@ -35,4 +40,4 @@ To check the scale of the scene, you can add a cube in Isaac Sim and compare it Adding physics to the stage is as simple as adding a `Physics` property with the "Colliders Preset", as described in the [Isaac docs](https://docs.omniverse.nvidia.com/isaacsim/latest/gui_tutorials/tutorial_intro_simple_objects.html#adding-physics-properties). Then save the scene as `[YOUR_ENVIRONMENT_NAME].scene.usd` to clarify that it's a physics-enabled scene. -You're now ready to add robots to the scene on the next page. \ No newline at end of file +You're now ready to add robots to the scene — see [Spawning Drones](spawning_drones.md). \ No newline at end of file diff --git a/docs/simulation/isaac_sim/omnigraph_config.png b/docs/simulation/isaac_sim/omnigraph_config.png deleted file mode 100644 index 874d098f0..000000000 Binary files a/docs/simulation/isaac_sim/omnigraph_config.png and /dev/null differ diff --git a/docs/simulation/isaac_sim/pegasus_scene_setup.md b/docs/simulation/isaac_sim/pegasus_scene_setup.md index 6f7a640b4..b4be1262d 100644 --- a/docs/simulation/isaac_sim/pegasus_scene_setup.md +++ b/docs/simulation/isaac_sim/pegasus_scene_setup.md @@ -22,9 +22,9 @@ Through this approach, AirStack leverages Pegasus to create a flexible, reusable 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" +ISAAC_SIM_GUI="/isaac-sim/AirStack/simulation/isaac-sim/assets/scenes/simple_pegasus.scene.usd" # Set to "true" to launch Isaac Sim using a standalone Python script instead of a USD file -ISAAC_SIM_USE_STANDALONE="false" # "true" or "false" +ISAAC_SIM_USE_STANDALONE="true" # "true" or "false" # Script name (must be in /AirStack/simulation/isaac-sim/launch_scripts/) ISAAC_SIM_SCRIPT_NAME="example_one_px4_pegasus_launch_script.py" PLAY_SIM_ON_START="true" # honored in both modes; `airstack up --no-play` overrides @@ -52,15 +52,7 @@ Example scripts are provided in `simulation/isaac-sim/launch_scripts/`. They are **Location:** `simulation/isaac-sim/utils/scene_prep.py` -`scene_prep.py` provides helpers that are shared across all example launch scripts: - -| Function | Purpose | -|----------|---------| -| `scale_stage_prim(stage, prim_path, scale_factor)` | Applies a uniform XYZ scale transform to the prim at `prim_path`, clearing any existing xform ops first. Use `0.01` for Nucleus assets authored in centimeters; use `1.0` for assets already in meters. | -| `add_colliders(stage_prim)` | Recursively walks every child of `stage_prim` and applies `UsdPhysics.CollisionAPI` to each `UsdGeom.Mesh`. **Must be called or drones fall through the floor.** Skips prims that already have the API. | -| `add_dome_light(stage, intensity=3500, exposure=-3)` | Adds a hemisphere light at `/World/DomeLight` (or updates it if it already exists). Pass `intensity` / `exposure` keyword arguments to override the defaults. | -| `save_scene_as_contained_usd(source_usd_url, output_dir)` | Copies the stage and all its dependencies (textures, MDL materials) from a Nucleus `omniverse://` URL into a local directory via `omni.kit.usd.collect.Collector`. Set `SAVE_SCENE_TO = None` in your script to skip this step. | -| `get_stage_meters_per_unit(stage)` | Returns `(meters_per_unit, scene_scale_factor)`. Multiply metric coordinates by `scene_scale_factor` to convert them into stage-space units. Useful for computing drone spawn heights when `STAGE_SCALE != 1.0`. | +`scene_prep.py` provides helpers that are shared across all example launch scripts — scaling, colliders, dome lighting, self-contained saving, and stage-unit conversion. The full per-function reference lives in [Spawning Drones → Scene prep helpers](spawning_drones.md#scene-prep-helpers). #### Loading `scene_prep` diff --git a/docs/simulation/isaac_sim/scene_setup.md b/docs/simulation/isaac_sim/scene_setup.md deleted file mode 100644 index 7373a1848..000000000 --- a/docs/simulation/isaac_sim/scene_setup.md +++ /dev/null @@ -1,52 +0,0 @@ -# AirStack Scene Setup - -## Creating a New Scene with Robots -The easiest way to create a scene is to copy and customize an existing scene. - -Example scenes are located on the AirLab Nucleus Server under [Projects > AirStack](https://airlab-nucleus.andrew.cmu.edu/omni/web3/omniverse://airlab-nucleus.andrew.cmu.edu/Projects/AirStack/). -This can be opened in Isaac's Content Browser: -![Image of content browser](content_browser.png) - -For example, `simple_tree_one_drone.scene.usd` looks like this: -![scene setup](scene_setup.png) - -The example scenes are setup with the following: - -- A "World" prim, which is the root of the scene -- The Root layer is set to use meters as the unit of length -- Prims that make up the scene. Scene prims should have collision physics enabled with Colliders Preset (Property > Add > Physics > Collider Preset) -- Robot instances, added to the scene as a reference to the robot USD file. Currently this file is [Library > Assets > Ascent_Aerosystems > TEMPLATE_spirit_uav.robot.usd](https://airlab-nucleus.andrew.cmu.edu/omni/web3/omniverse://airlab-nucleus.andrew.cmu.edu/Library/Assets/Ascent_Aerosystems/Spirit_UAV/TEMPLATE_spirit_uav.robot.usd) - - The robot has default sensors added, including a LiDAR and stereo cameras - - Sensors publish to ROS using the attached ActionGraph - - Robot dynamics are controlled by the [AirStack Extension](ascent_sitl_extension.md) - - -### Configure Robot Name and ROS_DOMAIN_ID - -Under the Spirit drone prim is an `ActionGraph` component, which is an [Omnigraph](https://docs.omniverse.nvidia.com/extensions/latest/ext_omnigraph.html). This component is used to configure the ROS publishers for the robot. The `ActionGraph` component has the following fields to configure: - -- `ROBOT_NAME`: The name of the robot. This is used as the top-level namespace for ROS topics. -- `ROS_DOMAIN_ID`: The ROS domain ID. This sets the `ROS_DOMAIN_ID` environment variable for DDS networking. - -The Omnigraph has subgraphs for each ROS publisher type. For example, TFs, Images, and PointClouds. The top-level `robot_name` and `domain_id` fields get fed into the subgraphs. - -To create a new robot, duplicate the drone prim instance and adjust the `ROBOT_NAME` and `ROS_DOMAIN_ID` fields to be unique. - - - -### Customizing the Omnigraph - -Common pre-built graphs for ROS may be added through the top menu bar: `Isaac Utils > Common OmniGraphs`. -This is helpful for creating various sensor publishers. - -We recommend organizing your work into sub-graphs. -Copy your omnigraph template them into the top-level `Omnigraph` component, named "ActionGraph". Connect the `robot_name` and `domain_id` fields to your workflow. Then, select all the nodes in your workflow, right-click, and create a subgraph. - - -## Frame Conventions -Isaac Sim uses Forward-Left-Up (FLU) coordinate frame conventions. However, MAVROS and AirStack use East-North-Up (ENU). - -To address this, the origin of the robot lives under a prim called `map_FLU`. Then AirStack publishes a static transform from `map_FLU` to `map` (published inline by `autonomy_bringup/launch/robot.launch.xml`), which is in ENU. The transform is a 90 degree rotation about the Z-axis. - -The resulting TF tree looks like this: -![Image of tf tree](tf_tree.png) \ No newline at end of file diff --git a/docs/simulation/isaac_sim/scene_setup.png b/docs/simulation/isaac_sim/scene_setup.png deleted file mode 100644 index 5bee41a7b..000000000 Binary files a/docs/simulation/isaac_sim/scene_setup.png and /dev/null differ diff --git a/docs/simulation/isaac_sim/spawning_drones.md b/docs/simulation/isaac_sim/spawning_drones.md index 3f7714287..e23d58c10 100644 --- a/docs/simulation/isaac_sim/spawning_drones.md +++ b/docs/simulation/isaac_sim/spawning_drones.md @@ -104,7 +104,7 @@ set_gps_origins(DRONE_CONFIGS, world_origin=(40.4433, -79.9436, 280.0)) # Pitts The anchor only affects the geographic location reported via GPS; nothing in the scene moves. Pick something close to where you want the drones to "be" — Foxglove's Map panel will center on it, and any GPS-referenced inputs to your stack will be relative to it. -## Scene prep helpers — `scene_prep.py` +## Scene prep helpers — `scene_prep.py` {#scene-prep-helpers} `simulation/isaac-sim/utils/scene_prep.py` is the small toolbox of stage preparation helpers `example_multi_drone_scene_import.py` uses inside its post-load callback (after the stage is loaded, before drones spawn). The full file has more — what's documented here is what you'll reach for in 95% of scenes. diff --git a/docs/simulation/isaac_sim/tf_tree.png b/docs/simulation/isaac_sim/tf_tree.png deleted file mode 100644 index 3be9699d4..000000000 Binary files a/docs/simulation/isaac_sim/tf_tree.png and /dev/null differ diff --git a/docs/simulation/isaac_sim/tmux_panel.png b/docs/simulation/isaac_sim/tmux_panel.png deleted file mode 100644 index 3550fd7aa..000000000 Binary files a/docs/simulation/isaac_sim/tmux_panel.png and /dev/null differ diff --git a/docs/simulation/ms-airsim/docker.md b/docs/simulation/ms-airsim/docker.md index 1ceb936b8..118cf83d8 100644 --- a/docs/simulation/ms-airsim/docker.md +++ b/docs/simulation/ms-airsim/docker.md @@ -39,32 +39,28 @@ The Microsoft AirSim (legacy) service is defined in `simulation/ms-airsim/docker ### Starting Microsoft AirSim (legacy) -Microsoft AirSim (legacy) is gated behind a Docker Compose profile: +For the user-facing launch path (scene selection + `airstack up --sim airsim`), see the [Quick Start in the overview](index.md#quick-start). At the container level, the service is gated behind a Docker Compose profile: ```bash -# Start alongside the robot stack (intent flag: selects profiles + matching URDF) -airstack up --sim airsim - -# Equivalent explicit-profile form +# Explicit-profile form (equivalent to `airstack up --sim airsim`) airstack up --profile ms-airsim --profile desktop - -# Build the image first -airstack images build --profile ms-airsim ``` Alternatively, set `COMPOSE_PROFILES=ms-airsim,desktop` in `.env` and run `airstack up`. ### What happens on startup -The container runs `entrypoint.sh`, which: +The container runs `entrypoint.sh` (`simulation/ms-airsim/docker/entrypoint.sh`), which: 1. Generates `settings.json` from the Jinja2 template using current environment variables -2. Creates a tmux session named `ms-airsim` -3. Builds the ROS 2 bridge workspace (`colcon build`) -4. In the `airsim` window: if `MS_AIRSIM_BINARY_PATH` is unset, runs `fetch_scene.sh blocks` to download + extract the default scene, then launches the UE4 binary as the `ms-airsim` user (UE4 refuses to run as root) -5. Launches one bridge node per robot, each with `ROS_DOMAIN_ID=` -6. Waits for the AirSim API to become available (TCP port 41451) -7. Spawns one PX4 SITL instance per robot, each in its own tmux window +2. Creates a tmux session named `ms-airsim` with a first window named `airsim` +3. Resolves the scene: an explicit `MS_AIRSIM_BINARY_PATH` wins (and must exist); otherwise `MS_AIRSIM_SCENE` (default `blocks`) selects a `fetch_scene.sh` key +4. Builds the ROS 2 bridge workspace (`colcon build`) +5. In the `airsim` window: auto-fetches the selected scene if it isn't downloaded yet (so progress is visible), then launches the UE4 binary as the `ms-airsim` user (UE4 refuses to run as root) +6. Creates one bridge window per robot (`robot__bridge`), each running the bridge node with `ROS_DOMAIN_ID=` +7. Waits for the AirSim API to become available (TCP port 41451) +8. Sleeps `MS_AIRSIM_PX4_START_DELAY` seconds (default 3) so AirSim sensors settle before PX4's EKF snapshots a local origin +9. Creates one PX4 SITL window per robot (`robot__px4`), each running `px4 ... -i ` ## Environment Variables @@ -74,22 +70,12 @@ The container runs `entrypoint.sh`, which: | `MS_AIRSIM_BINARY_PATH` | _(unset → auto-fetch Blocks)_ | Path to UE4 binary inside container. If unset, the entrypoint fetches Blocks into the mounted scenes dir and points at it. | | `MS_AIRSIM_ENV_DIR` | `../assets/scenes` | Host path to extracted UE4 scenes | | `MS_AIRSIM_HEADLESS` | `false` | Run UE4 without a window (`-RenderOffScreen -nosound`) | +| `MS_AIRSIM_SCENE` | _(empty → `blocks`)_ | Scene shortname (a `fetch_scene.sh` key, set by `airstack up --scene `); ignored when `MS_AIRSIM_BINARY_PATH` is set | | `MS_AIRSIM_PX4_START_DELAY` | `3` | Seconds to wait after AirSim becomes ready before starting PX4, so sensors settle before the EKF snapshots a local origin | | `NUM_ROBOTS` | `1` | Number of vehicles and PX4 SITL instances | | `SIM_IP` | `172.31.0.200` | Simulator IP on `airstack_network` | -**Camera template variables** (override in `.env` to regenerate `settings.json`): - -| Variable | Default | Description | -|----------|---------|-------------| -| `AIRSIM_CAM_WIDTH` | `480` | Camera image width (px) | -| `AIRSIM_CAM_HEIGHT` | `300` | Camera image height (px) | -| `AIRSIM_CAM_FOV` | `90` | Camera horizontal FOV (degrees) | -| `AIRSIM_CAM_X` | `0.4` | Camera X offset from body center (m) | -| `AIRSIM_CAM_Y` | `0.06` | Camera Y half-baseline (m) | -| `AIRSIM_CAM_Z` | `0.0` | Camera Z offset from body center (m) | -| `AIRSIM_CAM_PITCH` | `0.0` | Camera pitch angle (degrees) | -| `AIRSIM_SPAWN_SPACING` | `3.0` | Y-axis spacing between robots (m) | +The camera template variables (`AIRSIM_CAM_*`) and their defaults are documented in the [camera configuration reference](index.md#cameras); `AIRSIM_SPAWN_SPACING` (default `3.0`) sets the Y-axis spacing between spawned robots in meters. **Example overrides:** @@ -134,9 +120,9 @@ NUM_ROBOTS=2 python3 generate_settings.py | Port | Protocol | Purpose | |------|----------|---------| | 41451 | TCP | AirSim Python API | -| 4561–456N | TCP | PX4 lockstep (one per robot, N = robot index) | -| 24541–2454N | UDP | MAVLink offboard (one per robot) | -| 24581–2458N | UDP | MAVLink onboard (one per robot) | +| `4560 + i` | TCP | PX4 lockstep (`TcpPort`, one per robot `i` = 1..N) | +| `24540 + i` | UDP | AirSim MAVLink control channel, local (`ControlPortLocal`, one per robot) | +| `24580 + i` | UDP | AirSim MAVLink control channel, remote (`ControlPortRemote`, one per robot) | ## GPU Access @@ -213,13 +199,13 @@ airstack connect ms-airsim tmux a -t ms-airsim ``` -**Tmux windows layout:** +**Tmux windows layout** (`1 + 2*NUM_ROBOTS` windows, in creation order — see [What happens on startup](#what-happens-on-startup)): -| Window | Contents | -|--------|---------| -| 0 | AirSim UE4 binary | -| 1..N | PX4 SITL instance for robot 1..N | -| N+1..2N | ROS 2 bridge node for robot 1..N | +| Window | Name | Contents | +|--------|------|----------| +| 0 | `airsim` | Scene fetch (if needed) + AirSim UE4 binary | +| 1..N | `robot__bridge` | ROS 2 bridge node for robot `i` = 1..N | +| N+1..2N | `robot__px4` | PX4 SITL instance for robot `i` = 1..N (created only after the AirSim API is ready + `MS_AIRSIM_PX4_START_DELAY`) | **Useful tmux commands:** @@ -301,39 +287,23 @@ AIRSIM_CAM_FOV=120 airstack up --sim airsim - Verify `DISPLAY` is set and X11 socket is mounted: `echo $DISPLAY`, `xhost +local:docker` - Check disk space: pre-built environments are 3–10 GB -**Bridge can't connect to AirSim API:** - -- 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 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:** -- Confirm `settings.json` was generated with correct `TcpPort` values +- Confirm `settings.json` was generated with correct `TcpPort` values (`4560 + i`) - Check AirSim console for "Waiting for TCP connection" messages - Verify the PX4 lockstep port is not blocked by a firewall -**MAVROS won't connect (robot container):** - -- Verify `SIM_IP=172.31.0.200` is set in `.env` -- Ensure PX4 SITL has started (look for `[mavlink]` output in the PX4 tmux window) -- Check MAVLink ports match: offboard `24541+i`, onboard `24581+i` - -**No depth images on ROS 2 topics:** - -- 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` - **ROS 2 topics not visible from robot container:** - Confirm both containers are on `airstack_network`: `docker network inspect airstack_network` - Check `ROS_DOMAIN_ID` is consistent between containers - Verify DDS multicast is working: `ros2 topic list` from inside each container +For user-facing issues (bridge can't connect to AirSim, no depth images, MAVROS won't connect), see the [overview → Troubleshooting](index.md#troubleshooting). + ## See Also -- [Microsoft AirSim (legacy) Overview](index.md) — capabilities, configuration, and architecture +- [Microsoft AirSim (legacy) Overview](index.md) — quick start, settings/camera/bridge configuration, published topics - [Simulation Overview](../index.md) — Choosing between simulators - [Isaac Sim Docker](../isaac_sim/docker.md) — Isaac Sim container reference - [Docker Workflow](../../development/beginner/airstack-cli/docker_usage.md) — General Docker operations diff --git a/docs/simulation/ms-airsim/index.md b/docs/simulation/ms-airsim/index.md index ae86e0b6a..5fca23a31 100644 --- a/docs/simulation/ms-airsim/index.md +++ b/docs/simulation/ms-airsim/index.md @@ -23,6 +23,10 @@ Microsoft AirSim (legacy) provides an alternative simulation backend for AirStac - Archived project (no new features, but stable) - UE 4.27 only (older engine) +## Project status + +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). + ## Quick Start ### 1. Scene (auto-fetched on first launch) @@ -56,18 +60,9 @@ 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 -airstack images build --profile ms-airsim -``` - -The container runs `1 + 2*NUM_ROBOTS` tmux windows: -- **Window 0**: AirSim binary (Unreal Engine rendering) -- **Windows 1..N**: one PX4 SITL instance per robot -- **Windows N+1..2N**: one ROS 2 bridge node per robot (depth + stereo RGB + camera_info) +To build or pull the images first, see [Docker reference → Image Management](docker.md#image-management). -To attach to the tmux session: +To attach to the container's tmux session (window layout and startup sequence are detailed in the [Docker reference](docker.md#accessing-the-container)): ```bash airstack connect ms-airsim @@ -130,9 +125,10 @@ The default configuration is a forward-facing **stereo pair** (left + right) plu | FOV | 90° | `AIRSIM_CAM_FOV` | | Baseline (2 × Y offset) | 0.12 m | `AIRSIM_CAM_Y` | | Forward (X) offset | 0.4 m | `AIRSIM_CAM_X` | +| Vertical (Z) offset | 0 m | `AIRSIM_CAM_Z` | | Pitch | 0° | `AIRSIM_CAM_PITCH` | -Cameras are defined per vehicle in the generated `settings.json` under `Vehicles.robot_.Cameras`. +Cameras are defined per vehicle in the generated `settings.json` under `Vehicles.robot_.Cameras`. Override the `.env` variables and restart the container to regenerate `settings.json` (see [Docker reference → Settings Generation](docker.md#settings-generation)). ### Bridge node parameters @@ -147,11 +143,7 @@ Declared (with these defaults) in `simulation/ms-airsim/ros_ws/src/ms_airsim_ros ### Environment variables -| Variable | Default | Description | -|----------|---------|-------------| -| `SIM_IP` | `172.31.0.200` | Simulation container IP | -| `MS_AIRSIM_ENV_DIR` | `simulation/ms-airsim/assets/scenes` | Host path to extracted AirSim scenes | -| `MS_AIRSIM_BINARY_PATH` | _(unset → auto-fetch Blocks)_ | Path to binary inside container. If unset, the entrypoint fetches Blocks and points at it. | +Container-level environment variables (`AUTOLAUNCH`, `NUM_ROBOTS`, `SIM_IP`, `MS_AIRSIM_*`) are documented in the [Docker reference → Environment Variables](docker.md#environment-variables). ## Published ROS 2 Topics @@ -164,24 +156,30 @@ Declared (with these defaults) in `simulation/ms-airsim/ros_ws/src/ms_airsim_ros | `/{robot_name}/sensors/front_stereo/depth` | `sensor_msgs/Image` | Depth image (32FC1, meters) | | `/clock` | `rosgraph_msgs/Clock` | Simulation clock from AirSim | -### Project status - -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 **Bridge can't connect to Microsoft AirSim (legacy):** -- Ensure the AirSim binary is running and `settings.json` is loaded -- Check that `ms_airsim_ip` parameter matches where AirSim is running +- Ensure the AirSim binary is running (`airsim` tmux window) and `settings.json` is loaded +- The entrypoint retries until the AirSim API is ready; check for connection errors in the container logs +- Check that the bridge node's `ms_airsim_ip` parameter matches where AirSim is running (default: `127.0.0.1` — same container) **No depth images:** - 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 +- Echo the topic: `ros2 topic echo /robot_1/sensors/front_stereo/depth --once` **MAVROS won't connect:** - Verify `SIM_IP=172.31.0.200` is set in `.env` (default) -- Ensure PX4 SITL has started (check AirSim console for MAVLink messages) -- Check port configuration: offboard=24540+i, onboard=24580+i +- Ensure PX4 SITL has started (look for `[mavlink]` output in the `robot__px4` tmux window) +- Check port configuration: offboard `14540 + ROS_DOMAIN_ID` (see Data flow above); AirSim's own control channel uses `24540+i`/`24580+i` + +For container-level issues (UE4 binary won't launch, GPU/Vulkan access, PX4 lockstep connection, DDS topic visibility across containers), see the [Docker reference → Troubleshooting](docker.md#troubleshooting). + +## See Also + +- [Docker Configuration](docker.md) — container reference: services, env vars, networking, tmux layout, startup sequence +- [Simulation Scenes](../scenes.md) — scene catalog and fetch helper +- [Simulation Overview](../index.md) — choosing between simulators diff --git a/docs/simulation/scenes.md b/docs/simulation/scenes.md index 805d700a0..4cca0f56b 100644 --- a/docs/simulation/scenes.md +++ b/docs/simulation/scenes.md @@ -165,7 +165,7 @@ proceeds and the container auto-fetches it inside the `airsim` tmux window. You can also pre-fetch manually: ```bash -./simulation/ms-airsim/assets/scenes/fetch_scene.sh forest +./simulation/ms-airsim/assets/scenes/fetch_scene.sh blocks ``` An explicit `MS_AIRSIM_BINARY_PATH` (pointing at any extracted UE4 binary) diff --git a/docs/tutorials/airstack_on_osmo.md b/docs/tutorials/airstack_on_osmo.md index 865e9f17a..14daeb880 100644 --- a/docs/tutorials/airstack_on_osmo.md +++ b/docs/tutorials/airstack_on_osmo.md @@ -1,12 +1,14 @@ # AirStack on OSMO — Recommended Remote Development Workflow -This is AirStack's recommended day-to-day development path. +This is AirStack's recommended **remote** development path — for Mac and +Windows users, machines without a local NVIDIA GPU, or anyone who wants to +develop against the lab's shared GPU pool. 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 Foxglove dashboard back to your browser. -Why this is the recommended path: +Why this is the recommended remote path: - **Pooled GPUs.** A lab's GPUs are shared on-demand across the whole team instead of pinned one-per-desktop. Onboarding doesn't require buying @@ -23,15 +25,15 @@ Why this is the recommended path: - **Hardware bigger than your laptop.** The pod has more CPU/RAM/GPU than most dev laptops, even if you have a GPU laptop. -> **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. +> **Have a Linux+GPU desktop?** Local development is the golden path — +> it's faster for tight inner loops. See +> [Getting Started](../getting_started/index.md). OSMO is the recommended +> alternative when local isn't an option. ## Who is this for? -Anyone developing AirStack — Mac, Windows, or Linux, with or without a -local GPU. +Anyone developing AirStack remotely — Mac, Windows, or Linux, especially +without a local GPU. You're comfortable using `git` from a terminal, you have an SSH key (`~/.ssh/id_ed25519` or similar), and you have either VS Code or Cursor diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md deleted file mode 100644 index 59d84a48a..000000000 --- a/docs/tutorials/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Tutorials - -Step-by-step guides for common AirStack workflows. If you are new, start with **Getting Started**. - -| Tutorial | Description | -|---|---| -| [Getting Started](../getting_started/index.md) | Install AirStack, pull Docker images, launch a simulated robot, and fly it for the first time. | -| [AirStack on OSMO (Mac/Windows OK)](airstack_on_osmo.md) | Develop on AirStack from a Mac, Windows, or no-GPU Linux laptop using NVIDIA OSMO + VS Code/Cursor Remote-SSH. No local Docker or local `airstack install`; use a local repo clone for the `airstack osmo:*` wrappers and workflow YAML. | -| [Multi-Robot Simulation](../robot/docker/robot_identity.md) | Spin up multiple simulated robots in Isaac Sim and verify independent ROS 2 namespaces. | -| [Autonomy Modes](../robot/autonomy_modes.md) | Understand the `full_default`, `lite_default`, and `lite_offload_global` stack topologies and the commands to run each. | -| [Deploying to Hardware](../real_world/deploying_to_hardware.md) | Flash a Jetson or VOXL device, configure the robot hostname, and run the autonomy stack on a real drone. | diff --git a/git-hooks/README.md b/git-hooks/README.md index 84e69617e..2a453f5d2 100644 --- a/git-hooks/README.md +++ b/git-hooks/README.md @@ -1,33 +1,23 @@ -# Git Hooks +# Git Hooks (deprecated) -This directory contains git hooks used in the AirStack repository. +!!! warning "These hooks are obsolete — do not install them." -## Available Hooks +The docker-versioning pre-commit hook in this directory wrote the current git +commit hash into the `.env` `VERSION` variable. That scheme has been replaced: +`VERSION` must now be **valid semver, strictly greater than the base branch**, +enforced by the `check-version-increment.yml` CI gate on every pull request. +A hook-written commit hash fails that gate. -### Docker Versioning Hook +The supported flow is: -The `update-docker-image-tag.pre-commit` hook automatically updates the `VERSION` in the `.env` file with the current git commit hash whenever Docker-related files (Dockerfile or docker-compose.yaml) are modified. It also adds a comment above the variable indicating that the value is auto-generated from the git commit hash. +1. Bump `VERSION` in `.env` to the next semver value. +2. Record the change in the versioned Release Notes + (`docs/release_notes/index.md`). -This ensures that Docker images are always tagged with the exact commit they were built from, eliminating version conflicts between parallel branches. +See the `bump-version-and-release` skill (`.agents/skills/bump-version-and-release`) +for the full workflow. -### Installation - -To install the hooks: - -1. Copy the hook to your local .git/hooks directory: - ```bash - cp git-hooks/docker-versioning/update-docker-image-tag.pre-commit .git/hooks/pre-commit - ``` - -2. Make sure the hook file is executable: - ```bash - chmod +x .git/hooks/pre-commit - ``` - -## How the Docker Versioning Hook Works - -1. When you commit changes, the hook checks if any Dockerfile or docker-compose.yaml files are being committed -2. If Docker-related files are detected, it updates the VERSION in the .env file with the current git commit hash and adds a comment above the variable -3. The modified .env file is automatically added to the commit - -This approach eliminates version conflicts between parallel branches by ensuring Docker images are tagged with the exact commit they were built from. \ No newline at end of file +> Note: `airstack config git-hooks` (and `airstack config all`) still installs +> the old hook from this directory; until that CLI path is removed, avoid +> running it. The hook script is kept only so existing installs can be +> identified and removed (`rm .git/hooks/pre-commit`). diff --git a/git-hooks/docker-versioning/README.md b/git-hooks/docker-versioning/README.md index 49000933d..6bd9e58b2 100644 --- a/git-hooks/docker-versioning/README.md +++ b/git-hooks/docker-versioning/README.md @@ -1,35 +1,9 @@ -# Docker Versioning Git Hook +# Docker Versioning Hook (deprecated) -This directory contains a git hook that automatically updates the Docker image tag with the current git commit hash. +Do not install this hook. It writes the git commit hash into the `.env` +`VERSION` variable, which conflicts with the current versioning scheme: +`VERSION` must be valid semver and strictly greater than the base branch, +enforced by the `check-version-increment.yml` CI gate. -## Hook: update-docker-image-tag.pre-commit - -This pre-commit hook automatically updates the `VERSION` in the `.env` file with the current git commit hash whenever Docker-related files (Dockerfile or docker-compose.yaml) are modified. - -### Features - -- Automatically updates `VERSION` with the git commit hash -- Adds a comment above the variable indicating it's auto-generated -- Only triggers when Docker-related files are modified -- Automatically stages the modified .env file for commit - -### Installation - -To install the hook: - -1. Copy the hook to your local .git/hooks directory: - ```bash - cp update-docker-image-tag.pre-commit ../../.git/hooks/pre-commit - ``` - -2. Make sure the hook file is executable: - ```bash - chmod +x ../../.git/hooks/pre-commit - ``` - -### Benefits - -- Eliminates version conflicts between parallel branches -- Ensures Docker images are tagged with the exact commit they were built from -- Simplifies tracking which version of the code is running in Docker containers -- Provides a consistent and automated versioning system for Docker images \ No newline at end of file +See [`git-hooks/README.md`](../README.md) for the supported release flow and +how to remove an existing install of this hook. diff --git a/mkdocs.yml b/mkdocs.yml index fa136549e..fe771b31f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,153 +68,160 @@ markdown_extensions: permalink: true nav: - Home: docs/index.md - - Getting Started: - - docs/getting_started/index.md - - Modular AirStack Walkthrough: docs/getting_started/modular_airstack.md - - docs/tutorials/airstack_on_osmo.md - - docs/getting_started/tutorials_reference.md - - Development: - - docs/development/index.md - - Beginner Tutorials: - - Key Concepts: - - Overview: docs/development/beginner/key_concepts.md - - CLI Introduction: docs/development/beginner/airstack-cli/index.md - - Docker Workflow: docs/development/beginner/airstack-cli/docker_usage.md - - docs/development/beginner/development_environment.md - - docs/development/beginner/vscode/vscode_debug.md - - docs/development/beginner/fork_your_own_project.md - - Intermediate Tutorials: - - Testing: - - Overview: docs/development/intermediate/testing/index.md - - Unit Testing: docs/development/intermediate/testing/unit_testing.md - - System Tests: tests/README.md - - End-to-End Testing: docs/development/intermediate/testing/end_to_end_testing.md - - CI/CD Pipeline: docs/development/intermediate/testing/ci_cd.md - - CI/CD Orchestrator: tests/ci-cd-orchestrator.md - - Module CI: docs/development/module_ci.md - - Frame Conventions: docs/development/intermediate/frame_conventions.md - - Docker Build Profiles: docs/development/intermediate/docker-build-profiles.md - - Contributing: - - docs/development/intermediate/contributing.md - - docs/development/intermediate/documentation.md - - Feature Notebook: docs/development/intermediate/feature_notebook.md - - Advanced Tutorials: - - AI Agent Guide: docs/development/advanced/ai_agent_guide.md - - AirStack Modules: docs/development/modules.md - - AirStack Stacks: docs/development/stacks.md - - AirStack Fleets: docs/development/fleets.md - - AirStack CLI Tool: - - Extending: docs/development/advanced/airstack-cli/extending.md - - Architecture: docs/development/advanced/airstack-cli/architecture.md - - Simulation: - - docs/simulation/index.md - - docs/simulation/scenes.md - - Isaac Sim: - - docs/simulation/isaac_sim/index.md - - Docker: docs/simulation/isaac_sim/docker.md - - docs/simulation/isaac_sim/pegasus_scene_setup.md + - Beginner Tutorials: + - Get AirStack Flying: docs/getting_started/index.md + - Fly a Mission from the GCS: docs/getting_started/fly_a_mission.md + - Change a Parameter: docs/getting_started/change_a_parameter.md + - Modular AirStack Walkthrough: docs/getting_started/modular_airstack.md + - Write Your First Module: docs/getting_started/first_module.md + - Your First Fleet: docs/getting_started/first_fleet.md + - Build and Fly Your Own Scene: docs/getting_started/build_your_own_scene.md + - Deploy to Hardware: docs/real_world/deploying_to_hardware.md + - What Next: docs/getting_started/tutorials_reference.md + - Concepts: + - Key Concepts: docs/development/beginner/key_concepts.md + - System Architecture: docs/robot/autonomy/system_architecture.md + - Modular AirStack: + - Modules: docs/development/modules.md + - Stacks: docs/development/stacks.md + - Fleets: docs/development/fleets.md + - Robot Identity: docs/robot/docker/robot_identity.md + - Simulation Platforms: + - Overview: docs/simulation/index.md + - Isaac Sim: docs/simulation/isaac_sim/index.md + - Pegasus Extension: docs/simulation/isaac_sim/pegasus_scene_setup.md + - Microsoft AirSim (legacy): docs/simulation/ms-airsim/index.md + - Simple Sim: docs/simulation/simple_sim/index.md + - Ground Control Station: docs/gcs/index.md + - CI/CD Architecture: docs/development/intermediate/testing/ci_cd.md + - CLI Architecture: docs/development/advanced/airstack-cli/architecture.md + - Frame Conventions: docs/development/intermediate/frame_conventions.md + - How-to Guides: + - Overview: docs/development/index.md + - Development Environment: + - Setup: docs/development/beginner/development_environment.md + - VSCode & Debugging: docs/development/beginner/vscode/vscode_debug.md + - Fork Your Own Project: docs/development/beginner/fork_your_own_project.md + - Remote Development on OSMO: docs/tutorials/airstack_on_osmo.md + - Docker & Builds: + - Docker Workflow: docs/development/beginner/airstack-cli/docker_usage.md + - Build Profiles: docs/development/intermediate/docker-build-profiles.md + - Simulation: + - Scenes: docs/simulation/scenes.md + - Isaac Sim Container Workflows: docs/simulation/isaac_sim/container_workflows.md - Spawning Drones: docs/simulation/isaac_sim/spawning_drones.md - Overhead Camera: docs/simulation/isaac_sim/overhead_camera.md - - docs/simulation/isaac_sim/ascent_sitl_extension.md - - docs/simulation/isaac_sim/export_stages_from_unreal.md - - Microsoft AirSim (legacy): - - docs/simulation/ms-airsim/index.md - - Docker: docs/simulation/ms-airsim/docker.md - - Simple Sim: - - docs/simulation/simple_sim/index.md - - Docker: docs/simulation/simple_sim/docker.md - - Robot: - - docs/robot/index.md + - Gimbal: docs/robot/autonomy/sensors/gimbal.md + - Export Stages from Unreal: docs/simulation/isaac_sim/export_stages_from_unreal.md + - Autonomy: + - Add a State Estimator: docs/robot/autonomy/perception/adding_a_state_estimator.md + - Add a World Model and Planner: docs/robot/autonomy/adding_a_world_model_and_planner.md + - Add a Controller: docs/robot/autonomy/adding_a_controller.md + - Create a Coordination Algorithm: docs/robot/autonomy/coordination/creating_coordination_algorithms.md + - Coordination Payloads: docs/robot/autonomy/coordination/payloads.md + - Integration Checklist: docs/robot/autonomy/integration_checklist.md + - GCS Operation: + - Waypoints & Geofences: docs/gcs/waypoints_and_geofences.md + - Foxglove Visualization: docs/gcs/foxglove.md + - Extending the Visualizer: docs/gcs/extending_foxglove.md + - Operating the GCS: docs/gcs/usage/user_interface.md + - Robot & Field: + - Overview: docs/real_world/index.md + - Install on Hardware: docs/real_world/installation/index.md + - Add a Vehicle or Platform: docs/development/adding_a_vehicle.md + - HITL Testing: docs/real_world/HITL/index.md + - Data Offloading: docs/real_world/data_offloading/index.md + - Data Offloading (DIY patterns): docs/robot/logging/data_offloading.md + - Logging: docs/robot/logging/index.md + - ROS Bags: docs/robot/logging/rosbags.md + - Modules & Stacks: + - Create a Custom Stack: docs/development/creating_a_stack.md + - Module CI: docs/development/module_ci.md + - Testing: + - Overview: docs/development/intermediate/testing/index.md + - Unit Testing: docs/development/intermediate/testing/unit_testing.md + - End-to-End Testing: docs/development/intermediate/testing/end_to_end_testing.md + - Using CI: docs/development/intermediate/testing/using_ci.md + - Contributing: + - Contributing Guide: docs/development/intermediate/contributing.md + - Documentation Guide: docs/development/intermediate/documentation.md + - Feature Notebook: docs/development/intermediate/feature_notebook.md + - Working with Coding Agents: docs/development/working_with_coding_agents.md + - Extending the CLI: docs/development/advanced/airstack-cli/extending.md + - Reference: + - CLI: docs/development/beginner/airstack-cli/index.md - Configuration: - - docs/robot/configuration/index.md - - Docker: - - docs/robot/docker/index.md - - Robot Identity: docs/robot/docker/robot_identity.md - - Autonomy Modules: - - Overview: docs/robot/autonomy/index.md - - System Architecture: docs/robot/autonomy/system_architecture.md - - Tasks and Task Executors: docs/robot/autonomy/tasks.md - - Interface: - - docs/robot/autonomy/interface/index.md - - Sensors: - - docs/robot/autonomy/sensors/index.md - - Gimbal: docs/robot/autonomy/sensors/gimbal.md - - Perception: - - docs/robot/autonomy/perception/index.md - - OptiTrack (asm_optitrack module): docs/robot/optitrack.md - - Local: - - docs/robot/autonomy/local/index.md - - World Model: - - docs/robot/autonomy/local/world_model/index.md - - DROAN Obstacle Avoidance: - - Disparity Expansion: robot/ros_ws/src/local/world_models/disparity_expansion/README.md - - Disparity Graph: robot/ros_ws/src/local/world_models/disparity_graph/README.md - - Disparity Graph Cost Map: robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md - - Planning: - - docs/robot/autonomy/local/planning/index.md - - Trajectory Library: robot/ros_ws/src/local/planners/trajectory_library/README.md - - Takeoff Landing Planner: - - Overview: robot/ros_ws/src/local/planners/takeoff_landing_planner/README.md - - Testing: robot/ros_ws/src/local/planners/takeoff_landing_planner/test/README.md - - DROAN Local Planner: robot/ros_ws/src/local/planners/droan_local_planner/README.md - - DROAN GL: robot/ros_ws/src/local/planners/droan_gl/README.md - - Controls: - - docs/robot/autonomy/local/controls/index.md - - Trajectory Controller: robot/ros_ws/src/local/controls/trajectory_controller/README.md - - Global: - - docs/robot/autonomy/global/index.md - - World Model: - - docs/robot/autonomy/global/world_model/index.md - - VDB Mapping: robot/ros_ws/src/global/world_models/vdb_mapping_ros2/README.md - - Planning: - - docs/robot/autonomy/global/planning/index.md - - Random Walk: robot/ros_ws/src/global/planners/random_walk/README.md - - Exploration: robot/ros_ws/src/global/planners/exploration/README.md - - Behavior: - - docs/robot/autonomy/behavior/index.md - - Integration Guide: docs/robot/autonomy/integration_checklist.md + - Robot Configuration: docs/robot/configuration/index.md + - Environment Variables: docs/robot/configuration/environment_variables.md + - Vehicle Config Schema: config/vehicles/README.md + - Local Calibration: config/local/README.md + - Static Transforms: docs/robot/static_transforms/index.md + - Interfaces: - Interface Conventions Spec: docs/robot/autonomy/interface_conventions.md - - Coordination: - - Overview: docs/robot/autonomy/coordination/index.md - - Payloads & Foxglove: docs/robot/autonomy/coordination/payloads.md - - Autonomy Modes: docs/robot/autonomy_modes.md - - DDS Router: docs/robot/autonomy/dds_router.md - - Static Transforms: - - docs/robot/static_transforms/index.md - - Logging: - - docs/robot/logging/index.md - - ROS Bags: docs/robot/logging/rosbags.md - - Data Offloading: docs/robot/logging/data_offloading.md - - Bag Recorder: common/ros_packages/logging/bag_recorder_pid/README.md - - Modules: - - Catalog: docs/modules/index.md - - dfm2_disturbances: docs/modules/dfm2_disturbances.md - - macvo: docs/modules/macvo.md - - optitrack: docs/modules/optitrack.md + - Tasks and Task Executors: docs/robot/autonomy/tasks.md + - DDS Router: docs/robot/autonomy/dds_router.md + - airstack_msgs: common/ros_packages/msgs/airstack_msgs/README.md + - Containers: + - Robot: docs/robot/docker/index.md + - Isaac Sim: docs/simulation/isaac_sim/docker.md + - Microsoft AirSim (legacy): docs/simulation/ms-airsim/docker.md + - Simple Sim: docs/simulation/simple_sim/docker.md + - GCS: docs/gcs/docker/index.md + - Deployment Topologies: docs/robot/autonomy_modes.md + - Platform Matrix: docs/real_world/supported_platforms.md - Reference Stacks: - full_default: stacks/full_default/README.md - full_droan_cpu: stacks/full_droan_cpu/README.md - full_macvo: stacks/full_macvo/README.md - lite_default: stacks/lite_default/README.md - lite_offload_global: stacks/lite_offload_global/README.md - - Ground Control Station: - - docs/gcs/index.md - - Docker: - - docs/gcs/docker/index.md - - Usage: - - User Interface: docs/gcs/usage/user_interface.md - - Foxglove: - - Visualization: docs/gcs/foxglove.md - - Adding Waypoints & Geofences: docs/gcs/waypoints_and_geofences.md - - Real World: - - docs/real_world/index.md - - Deploying to Hardware: docs/real_world/deploying_to_hardware.md - - Installation on Hardware: - - docs/real_world/installation/index.md - - HITL Testing: - - docs/real_world/HITL/index.md - - Data Offloading: - - docs/real_world/data_offloading/index.md + - Modules Catalog: + - Catalog: docs/modules/index.md + - module.yaml Schema: common/module_schema/README.md + - dfm2_disturbances: docs/modules/dfm2_disturbances.md + - macvo: docs/modules/macvo.md + - optitrack: docs/modules/optitrack.md + - Autonomy Packages: + - Robot Overview: docs/robot/index.md + - Autonomy Overview: docs/robot/autonomy/index.md + - Interface: docs/robot/autonomy/interface/index.md + - Sensors: + - Overview: docs/robot/autonomy/sensors/index.md + - LiDAR Point Cloud Filter: robot/ros_ws/src/sensors/lidar_point_cloud_filter/README.md + - Perception: + - Overview: docs/robot/autonomy/perception/index.md + - OptiTrack (asm_optitrack module): docs/robot/optitrack.md + - Local: + - Overview: docs/robot/autonomy/local/index.md + - World Model: docs/robot/autonomy/local/world_model/index.md + - Disparity Expansion: robot/ros_ws/src/local/world_models/disparity_expansion/README.md + - Disparity Graph: robot/ros_ws/src/local/world_models/disparity_graph/README.md + - Disparity Graph Cost Map: robot/ros_ws/src/local/world_models/disparity_graph_cost_map/README.md + - Planning: docs/robot/autonomy/local/planning/index.md + - Trajectory Library: robot/ros_ws/src/local/planners/trajectory_library/README.md + - Takeoff Landing Planner: robot/ros_ws/src/local/planners/takeoff_landing_planner/README.md + - Takeoff Landing Testing: robot/ros_ws/src/local/planners/takeoff_landing_planner/test/README.md + - DROAN Local Planner: robot/ros_ws/src/local/planners/droan_local_planner/README.md + - DROAN GL: robot/ros_ws/src/local/planners/droan_gl/README.md + - Controls: docs/robot/autonomy/local/controls/index.md + - Trajectory Controller: robot/ros_ws/src/local/controls/trajectory_controller/README.md + - Global: + - Overview: docs/robot/autonomy/global/index.md + - World Model: docs/robot/autonomy/global/world_model/index.md + - VDB Mapping: robot/ros_ws/src/global/world_models/vdb_mapping_ros2/README.md + - Planning: docs/robot/autonomy/global/planning/index.md + - Random Walk: robot/ros_ws/src/global/planners/random_walk/README.md + - Exploration: robot/ros_ws/src/global/planners/exploration/README.md + - Behavior: docs/robot/autonomy/behavior/index.md + - Coordination: docs/robot/autonomy/coordination/index.md + - RViz Tasks Panel: common/ros_packages/gui/rviz/rviz_tasks_panel/README.md + - RViz 3D Waypoint Plugin: common/ros_packages/gui/rviz/3d_waypoint_rviz2_plugin/README.md + - Bag Recorder: common/ros_packages/logging/bag_recorder_pid/README.md + - CI & Testing: + - System Test Suite: tests/README.md + - CI/CD Orchestrator Runbook: tests/ci-cd-orchestrator.md + - OSMO Lab Admin Guide: osmo/README.md + - AI Agent Guide: docs/development/advanced/ai_agent_guide.md - Release Notes: docs/release_notes/index.md - About: docs/about.md plugins: @@ -225,6 +232,13 @@ plugins: - redirects: redirect_maps: 'index.md': 'docs/index.md' + # Fossil pages removed in the Diátaxis docs overhaul + 'docs/simulation/isaac_sim/scene_setup.md': 'docs/simulation/isaac_sim/pegasus_scene_setup.md' + 'docs/simulation/isaac_sim/ascent_sitl_extension.md': 'docs/simulation/isaac_sim/pegasus_scene_setup.md' + 'docs/development/intermediate/testing/testing_frameworks.md': 'docs/development/intermediate/testing/index.md' + 'docs/development/development_environment.md': 'docs/development/beginner/development_environment.md' + 'docs/development/airstack-cli/index.md': 'docs/development/beginner/airstack-cli/index.md' + 'docs/tutorials/index.md': 'docs/getting_started/tutorials_reference.md' repo_name: castacks/AirStack repo_url: https://github.com/castacks/AirStack theme: diff --git a/robot/ros_ws/src/global/planners/exploration/README.md b/robot/ros_ws/src/global/planners/exploration/README.md index f510e4cef..0e609baa8 100644 --- a/robot/ros_ws/src/global/planners/exploration/README.md +++ b/robot/ros_ws/src/global/planners/exploration/README.md @@ -11,7 +11,7 @@ include) with: ``: -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: +Then when running the robot stack, after taking off, toggle the global plan from the rviz Tasks Panel or the GCS (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. @@ -38,7 +38,7 @@ The planner is derived from the random walk planner and shares several of its pa ## Services |
Parameter
| Type | Description |----------------------------|----------------------------------------|-----------------------| -| `~/global_plan_toggle` | std_srvs/Trigger | A toggle switch to turn on and off the random walk planner.| +| `~/global_plan_toggle` | std_srvs/Trigger | A toggle switch to turn on and off the exploration planner.| ## Subscriptions |
Parameter
| Type | Description @@ -49,7 +49,7 @@ The planner is derived from the random walk planner and shares several of its pa ## Publications |
Parameter
| Type | Description |----------------------------|----------------------------------------|-----------------------| -| `~/pub_global_plan_topic` | nav_msgs/Path | Outputs the global plan that is generated from the random walk planner.| +| `~/pub_global_plan_topic` | nav_msgs/Path | Outputs the global plan generated by the exploration planner.| diff --git a/robot/ros_ws/src/local/planners/trajectory_library/README.md b/robot/ros_ws/src/local/planners/trajectory_library/README.md index dad899195..db8c65c90 100644 --- a/robot/ros_ws/src/local/planners/trajectory_library/README.md +++ b/robot/ros_ws/src/local/planners/trajectory_library/README.md @@ -2,6 +2,125 @@ Contact: John Keller -Defines some basic trajectory classes and functions for generating and manipulating trajectories. +`trajectory_library` is a C++ library (no node of its own) for generating and manipulating candidate trajectories for obstacle-avoidance planners. It provides the `Trajectory`/`Waypoint` classes used throughout the local layer to interpolate, transform, trim, merge, and visualize waypoint paths, plus a `TrajectoryLibrary` class that loads a *library* of candidate trajectory generators from a YAML config file — the file the DROAN local planner points its `trajectory_library_config` parameter at. Every trajectory converts to/from [`airstack_msgs/msg/TrajectoryXYZVYaw`](../../../../../../common/ros_packages/msgs/airstack_msgs/README.md), the trajectory-controller command type (see the [Interface Conventions Specification §5](../../../../../../docs/robot/autonomy/interface_conventions.md)). -Docs TODO. Help appreciated. \ No newline at end of file +## Core classes + +Defined in [`include/trajectory_library/trajectory_library.hpp`](include/trajectory_library/trajectory_library.hpp), implemented in [`src/trajectory_library.cpp`](src/trajectory_library.cpp): + +| Class | What it is | Key operations | +|---|---|---| +| `Waypoint` | One sample: position, yaw, velocity, acceleration, jerk, time | `interpolate()`, `as_odometry_msg()` (→ `airstack_msgs/Odometry`) | +| `Trajectory` | A timed waypoint sequence in a TF frame; constructible from `TrajectoryXYZVYaw` or `nav_msgs/Path` (positions only, velocity 0) | closest-point queries, `get_waypoint(time)`, `get_odom(time)`, `to_frame()`, `merge()`, `trim()`, `get_trimmed_trajectory_between_distances()`, `get_reversed_trajectory()`, `get_markers()` (RViz), `get_TrajectoryXYZVYaw_msg()` | +| `TrajectoryLibrary` | Loads a set of candidate-trajectory generators from a YAML file | `get_static_trajectories()`, `get_dynamic_trajectories(odom)`, `get_markers()` | + +Waypoint times are generated lazily from positions and speeds (`generate_waypoint_times()`): the time to each waypoint is segment distance divided by the average of the two endpoint speeds (floored at 0.01 m/s). When a `TrajectoryXYZVYaw` is ingested, each waypoint's scalar `velocity` is turned into a velocity *vector* along the local segment direction. + +## Trajectory generator classes + +The library distinguishes **static** generators (fixed shape, computed once, `get_trajectory()`) from **dynamic** generators (recomputed from the robot's current odometry, `get_trajectory(odom)`): + +| Class | Kind | What it generates | Parameters (constructor) | +|---|---|---|---| +| `CurveTrajectory` | static | A constant-speed arc in the x-y plane of `frame`, integrating heading at a fixed turn rate for `time` seconds in `dt` steps; yaw either follows the heading or is fixed | `linear_velocity` (m/s), `angular_velocity` (rad/s), `frame`, `time` (s), `dt` (s), `use_heading`, `yaw` (rad) | +| `AccelerationTrajectory` | dynamic | Forward-integrates the robot's current position/velocity (transformed into `frame`) under a constant acceleration for horizon `ht` in `dt` steps; per-waypoint speed capped at `max_velocity` | `frame`, `ax, ay, az` (m/s²), `dt` (s), `ht` (s), `max_velocity` (m/s) | +| `TakeoffTrajectory` | dynamic | A 3-waypoint vertical (optionally tilted) climb of `height` meters from the current pose, ending with a near-zero-velocity waypoint | `height` (m), `velocity` (m/s), `path_roll`, `path_pitch` (rad), `relative_to_orientation` | + +Only `curve` and `acceleration` can be created from the YAML config; `TakeoffTrajectory` is constructed programmatically (the `takeoff_landing_planner` builds its takeoff/landing trajectories with it). Entries with any other `type` are silently skipped by the parser (`src/trajectory_library.cpp:1284-1344`). + +## Config file format (`trajectory_library_config`) + +`TrajectoryLibrary(config_filename, node_ptr)` loads a YAML file with a single top-level key, `trajectories:`, a list of generator definitions. Each entry's keys depend on its `type`: + +**`type: curve`** → one static `CurveTrajectory`: + +| Key | Unit | Meaning | +|---|---|---| +| `linear_velocity` | m/s | Constant speed along the arc | +| `angular_velocity` | **deg/s** | Turn rate (converted to rad/s at load) | +| `frame` | TF frame | Frame the arc starts at the origin of (e.g. `tracking_point_stabilized`) | +| `time` | s | Duration of the arc | +| `dt` | s | Waypoint spacing in time | +| `yaw` | `heading` or **deg** | The literal string `heading` makes yaw follow the direction of travel; a number fixes yaw to that value | + +**`type: acceleration`** → one dynamic `AccelerationTrajectory`. The acceleration vector is given either componentwise (`x`/`y`/`z`) **or** polar (`magnitude`/`magnitude_yaw`/`magnitude_pitch`); if neither complete set is present the entry is rejected with a console message: + +| Key | Unit | Meaning | +|---|---|---| +| `frame` | TF frame | Frame the integration happens in (e.g. `look_ahead_point_stabilized`) | +| `x`, `y`, `z` | m/s² | Acceleration vector components | +| `magnitude` | m/s² | Alternative: acceleration magnitude… | +| `magnitude_yaw`, `magnitude_pitch` | **deg** | …rotated by this yaw/pitch from the +x axis | +| `dt` | s | Integration/waypoint time step | +| `ht` | s | Horizon time (how long to integrate) | +| `max_velocity` | m/s | Speed cap applied to each generated waypoint | + +**`$(param )` substitution:** any scalar value may be the string `$(param name)`, which is replaced at load time with the value of the ROS parameter `name` on the node that constructed the `TrajectoryLibrary` (parser: `include/trajectory_library/trajectory_library.hpp`, `parse()`). This is how one config file serves different speed profiles: DROAN's `droan.yaml` sets `dt`, `ht`, `ht_long`, `max_velocity`, and `magnitude` as node parameters and the trajectory YAML references them. `TrajectoryLibrary`'s constructor declares these five parameter names (with placeholder defaults) so the substitution always resolves. + +Real excerpt from [`config/long.yaml`](config/long.yaml) — the default library for `droan_local_planner` (an `acceleration` fan: one entry per `magnitude_yaw` heading, 22.5° apart, plus climbing/descending variants): + +```yaml +--- +trajectories: + - dt: $(param dt) + frame: look_ahead_point_stabilized + ht: $(param ht_long) + magnitude: $(param magnitude) + magnitude_pitch: 0 + magnitude_yaw: 0 + max_velocity: $(param max_velocity) + type: acceleration + - dt: $(param dt) + frame: look_ahead_point_stabilized + ht: $(param ht_long) + magnitude: $(param magnitude) + magnitude_pitch: 0 + magnitude_yaw: 22.5 + max_velocity: $(param max_velocity) + type: acceleration + # ... more headings ... +``` + +And a `curve` example from [`config/backup.yaml`](config/backup.yaml): + +```yaml +trajectories: + - type: curve + linear_velocity: 1 + angular_velocity: -45 + frame: tracking_point_stabilized + dt: 0.2 + time: 3 + yaw: heading +``` + +### Shipped config files + +Installed to `share/trajectory_library/config/`; reference them with `$(find-pkg-share trajectory_library)/config/.yaml`: + +| File | Contents | +|---|---| +| `long.yaml` | Acceleration fan over `ht_long` horizon with level/climb/descend pitches — **DROAN's default** | +| `flat.yaml` | Acceleration fan, level flight only (`magnitude_pitch: 0`) | +| `acceleration_magnitudes.yaml` | Large acceleration fan parameterized by `$(param magnitude)` | +| `acceleration_trajectories.yaml`, `acceleration_trajectories_fast.yaml` | Fixed-value (no `$(param)`) acceleration sets at low/high accelerations | +| `demo_trajectory_definitions.yaml` | Slow `curve` set (0.2 m/s) for demos | +| `backup.yaml` | `curve` set in `tracking_point_stabilized` plus a straight acceleration entry | +| `fixed_trajectories.yaml` | **Different schema** — a catalog listing which `attributes` each `airstack_msgs/FixedTrajectory` type (Figure8, Racetrack, Circle, Line, Point) takes; not loadable by `TrajectoryLibrary`, and no trunk code reads it | + +[`src/trajectory_library_generator.py`](src/trajectory_library_generator.py) is a standalone developer script (not installed) that plots `curve`/`arc` config entries with matplotlib for eyeballing a library before flying it. + +## Consumers + +| Package | How it uses this library | +|---|---| +| [`droan_local_planner`](../droan_local_planner/README.md) | Constructs `TrajectoryLibrary` from its `trajectory_library_config` parameter (default `$(find-pkg-share trajectory_library)/config/long.yaml`, set in `config/droan.yaml`); each planning cycle calls `get_dynamic_trajectories(look_ahead_odom)` to get the candidate set it collision-checks and scores | +| [`droan_gl`](../droan_gl/README.md) | Links the library for the `Trajectory`/`Waypoint` utility classes (e.g. wrapping the incoming `nav_msgs/Path` global plan); it does **not** load a YAML library — its candidates come from its own graph expansion | +| [`takeoff_landing_planner`](../takeoff_landing_planner/README.md) | Constructs `TakeoffTrajectory` generators programmatically for takeoff and landing | + +## See also + +- [DROAN Local Planner README](../droan_local_planner/README.md) — the primary consumer of the YAML library +- [DROAN GL README](../droan_gl/README.md) — GPU DROAN variant +- [Trajectory Controller README](../../controls/trajectory_controller/README.md) — where the generated `TrajectoryXYZVYaw` trajectories are sent +- [Interface Conventions Specification](../../../../../../docs/robot/autonomy/interface_conventions.md) — canonical topics/types for the trajectory group