Skip to content

Drone Platform-Readiness — Phase 3 Remaining

Goal: Complete Phase 3 (Survey Intelligence) — the final phase of the platform-readiness plan.

Completed Phases: Pre-flight bugs, Phase 0 (Foundation), Phase 1 (Data Quality), Phase 2 (Sensor Config) — see ADR-007.

Design Doc: docs/plans/2026-03-08-drone-platform-readiness-design.md

Tech Stack: Python 3.12, ROS2 Jazzy, PX4 v1.16+, uXRCE-DDS, pytest, jsonschema, PyNaCl, Gazebo Harmonic


Phase 3: Survey Intelligence

~~Task 14: Grid Planner~~ ✓

Implemented in bennu_survey/grid_planner.py. Lawnmower pattern with configurable overlap, WGS84↔UTM projection, point-in-polygon containment. 4 tests passing.


Task 15: Terrain Following

What: adjust_waypoints_for_terrain() takes planned waypoints and a DEM elevation function, adjusts each waypoint's altitude to maintain constant AGL.

Why: Over hilly terrain, flat altitude = variable GSD. Terrain following keeps GSD consistent.

Module: bennu_survey/terrain_follow.py Test: drone/ros2_ws/src/bennu_survey/test/test_terrain_follow.py

What the code does: - adjust_waypoints_for_terrain(waypoints, target_agl, elevation_fn) → adjusted waypoints - Each waypoint's alt = elevation_fn(lat, lon) + target_agl

Tests (2):

def test_flat_terrain_unchanged()              # All elevations 0 → alt = target_agl
def test_hilly_terrain_adjusts()               # Elevation 100m, 150m → alt 180m, 230m (target_agl=80)

Accept: - [ ] Altitude = ground_elevation + target_agl - [ ] Tests pass


Task 16: Coverage Tracker (Image Footprint Based)

What: CoverageTracker tracks which planned grid cells are covered by actual image footprints (not waypoint proximity). Reports coverage percentage and identifies gaps.

Why: Distance-triggered capture can miss triggers or change spacing. Footprint-based coverage tells you what's actually photographed, not just where the drone flew.

Module: bennu_mission/coverage_tracker.py Test: drone/ros2_ws/src/bennu_mission/test/test_coverage_tracker.py

What the code does: - CoverageTracker(grid_cells, cell_size_m) — initializes with planned grid - .record_capture(lat, lon, gsd_cm, image_width_px, image_height_px) — records an image with its footprint - .coverage_pct() → 0.0-1.0 (cells covered / total cells) - .gaps() → list of uncovered grid cells

Tests (4):

def test_coverage_starts_at_zero()           # No captures → 0%
def test_coverage_from_image_footprint()     # Large footprint covers nearby cells
def test_partial_coverage()                  # Distant cells require separate captures
def test_gaps_reported()                     # Uncovered cells listed

Accept: - [ ] Coverage is footprint-based, not waypoint-proximity-based - [ ] Gap detection works - [ ] Tests pass


Task 17: Repeat Mission

What: RepeatMission loads a previous mission's waypoints and replays them. Tracks deviation from the planned path for repeatability metrics.

Why: Change detection requires flying the exact same grid repeatedly. Deviation tracking quantifies how reproducible the flights are.

Module: bennu_mission/repeat_mission.py Test: drone/ros2_ws/src/bennu_mission/test/test_repeat_mission.py

Tests (2):

def test_load_previous_waypoints()           # Loads waypoints from previous mission JSON
def test_deviation_zero_for_exact_match()    # Same positions → deviation < 1m

Accept: - [ ] Previous mission waypoints loaded correctly - [ ] Deviation metric makes physical sense (meters) - [ ] Tests pass


Task 18: Mission Execution Node

What: MissionExecutor manages flight state machine (idle → armed → takeoff → mission → RTL → land) and waypoint progress. Launch files wire everything for survey and mapping profiles.

Why: The planner produces waypoints but nothing executes them. This closes the loop.

Module: bennu_mission/mission_node.py Launch files: bennu_bringup/launch/survey.launch.py, mapping.launch.py Test: drone/ros2_ws/src/bennu_mission/test/test_mission_node.py

What the code does: - MissionExecutor(waypoints) — holds waypoint list - .state → current MissionState enum (IDLE, ARMED, TAKEOFF, MISSION, RTL, LANDED) - .arm(), .takeoff(), .advance_waypoint() — state transitions - .progress_pct() → fraction of waypoints completed - Full ROS2 node behavior (PX4 topic publishing) tested in SITL, not unit tests

Tests (3):

def test_mission_starts_idle()              # Initial state is IDLE
def test_mission_state_transitions()        # arm → ARMED, takeoff → TAKEOFF
def test_waypoint_progress()                # advance_waypoint increments, progress_pct correct

Accept: - [ ] State machine has reasonable transitions - [ ] Launch files wire camera + DDS + executor - [ ] Unit tests pass - [ ] SITL smoke test works (deferred to integration phase)


Phase 3 Exit Gate: - [x] Grid planner generates waypoints from AOI polygon - [ ] Terrain following adjusts altitudes from DEM - [ ] Coverage tracker uses image footprints (not waypoint proximity) - [ ] Mission executor manages flight state machine - [ ] Launch files exist for survey and mapping profiles - [ ] Repeat mission loads previous waypoints with deviation tracking