Spawns 9 quadrotors in a helical ring (3 altitude layers × 3 azimuths, every drone at a distinct azimuth so no rotor sits above another), holds them on station in an ABL wind field, and estimates the inflow direction — plus a per-layer veer profile — from their wind readings.
This matches the "config 1" quick direction pass: a fast, coarse estimate of where the wind is coming from, used to orient the full inflow campaign later.
| File | What it does |
|---|---|
ring_geometry.py |
Single source of truth: ring layout + the ABL wind field. Also a CLI that prints spawn poses. |
worlds/aerosurvey_ring.sdf |
Gazebo Harmonic world (PX4-compatible systems + ground/sun + wind). |
swarm_offboard.py |
ROS 2 node — holds all 9 drones at their ring altitudes via PX4 Offboard. |
wind_estimator.py |
ROS 2 node — software anemometer per drone, circular-mean direction + per-layer veer. |
run_sim.sh |
tmux launcher — agent + gz + 9 PX4 + both nodes. |
You already have Gazebo Harmonic (gz-sim 8). You still need PX4, the DDS agent, ROS 2, and the PX4 ROS 2 messages.
1. PX4-Autopilot (builds the SITL binary + pulls the gz models)
cd ~
git clone https://github.com/PX4/PX4-Autopilot.git --recursive
bash ./PX4-Autopilot/Tools/setup/ubuntu.sh # installs the toolchain
cd PX4-Autopilot
make px4_sitl # first build is slow
# smoke test a single drone, then close it:
make px4_sitl gz_x5002. ROS 2 — Humble on Ubuntu 22.04, or Jazzy on Ubuntu 24.04. Set
ROS_DISTRO in run_sim.sh to match.
3. Micro XRCE-DDS Agent (the PX4↔ROS 2 bridge)
cd ~ && git clone -b v2.4.2 https://github.com/eProsima/Micro-XRCE-DDS-Agent.git
cd Micro-XRCE-DDS-Agent && mkdir build && cd build
cmake .. && make && sudo make install && sudo ldconfig /usr/local/lib/
# test: MicroXRCEAgent udp4 -p 88884. px4_msgs in a ROS 2 workspace
mkdir -p ~/px4_ros2_ws/src && cd ~/px4_ros2_ws/src
git clone https://github.com/PX4/px4_msgs.git
cd ~/px4_ros2_ws && source /opt/ros/$ROS_DISTRO/setup.bash && colcon buildUse the
px4_msgsbranch that matches your PX4 version (themain/releasebranches track recent PX4). A message-definition mismatch shows up as topics that never deliver — rebuildpx4_msgsfrom the matching branch if so.
chmod +x run_sim.sh
PX4_DIR=~/PX4-Autopilot ROS2_WS=~/px4_ros2_ws ROS_DISTRO=humble ./run_sim.shThe launcher copies the world into PX4's gz folder, starts the agent, brings up
the gz server with drone 1, attaches drones 2–9, then starts both ROS 2 nodes.
Ctrl-b n / Ctrl-b p switch tmux windows; the estimator window prints, e.g.:
[9/9 drones] est 209.4deg (true 210.0, err -0.6deg)
layer 20 m -> dir 207.8deg, speed 7.0 m/s (3 drones)
layer 35 m -> dir 210.1deg, speed 8.0 m/s (3 drones)
layer 50 m -> dir 212.3deg, speed 8.7 m/s (3 drones)
The per-layer rows are the veer profile — proof the azimuthal stagger keeps it.
- Ring size / layers / azimuths: top of
ring_geometry.py. - Wind field (speed, shear, direction, veer rate, set
VEER_DEG_PER_M=0to switch veer off): theABL wind fieldblock inring_geometry.py. - Sensor noise:
NOISE_STDinwind_estimator.py. - Headless (no GUI, faster): prefix the first PX4 command with
HEADLESS=1.
- Arming/offboard never engages → the
VehicleCommand.target_systemoff-by-one.swarm_offboard.pyusesinstance + 1; if your PX4 build maps it differently, checkros2 topic echo /px4_1/fmu/out/vehicle_statusand theMAV_SYS_IDparam, then adjustself.target_system. - Topics don't appear →
px4_msgsbuilt from a branch that doesn't match your PX4 version. Rebuild from the matching branch. - Models spawn but won't fly / actuator index errors → the world is
missing a system PX4 expects. Diff
worlds/aerosurvey_ring.sdfagainstPX4-Autopilot/Tools/simulation/gz/worlds/default.sdfand add what's missing. - Wind doesn't push the drones → expected.
WindEffectsonly acts on links with<enable_wind>true</enable_wind>; the stock x500 has none, so the wind is scene context and the measurement is taken in software. To get real station-keeping error, copy the x500 model into a PX4 models folder and add<enable_wind>true</enable_wind>to its base link.
- Swap the analytic
wind_true()for a Mann/Kaimal turbulence field (IEC 61400-1) or a CFD snapshot to stress-test the estimate under real gusts. ros2 bag record -awhile it runs, then plot estimate-vs-time and the error band — that's your validation figure.- For the render: the gz GUI already shows the ring; add a
gz-simcamera or drive a follow-camera path, or feed the rosbag positions to PyVista/Plotly. - Make the wind physical (gotcha 4) and measure how station-keeping drift maps into direction error — that's the realism PX4 buys you over a kinematic sim.