Ardupilot Update

This commit is contained in:
2026-01-02 21:45:16 +00:00
parent 61c47f82fc
commit 6c72bbf24c
13 changed files with 2189 additions and 8 deletions

View File

@@ -1,6 +1,9 @@
# Drone Landing Simulation (GPS-Denied) # Drone Landing Simulation (GPS-Denied)
A GPS-denied drone landing simulation using relative sensors (IMU, altimeter, camera, landing pad detection) with **PyBullet** and **Gazebo** simulators. A GPS-denied drone landing simulation using relative sensors (IMU, altimeter, camera, landing pad detection) with multiple backends:
- **PyBullet** - Lightweight physics simulation
- **Gazebo** - Full robotics simulator
- **ArduPilot SITL** - Realistic flight controller with MAVProxy
## Quick Start ## Quick Start
@@ -37,6 +40,24 @@ ros2 launch gazebo/launch/drone_landing.launch.py
python run_gazebo.py --pattern circular --speed 0.3 python run_gazebo.py --pattern circular --speed 0.3
``` ```
### ArduPilot SITL + Gazebo (Three Terminals - Realistic Flight Controller)
**Terminal 1 - Launch Gazebo with ArduPilot world:**
```bash
ros2 launch gazebo/launch/ardupilot_drone.launch.py
```
**Terminal 2 - Start ArduPilot SITL:**
```bash
cd ~/ardupilot
sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON --console
```
**Terminal 3 - Run MAVLink Bridge + Controllers:**
```bash
python run_ardupilot.py --no-sitl --pattern circular
```
## Installation ## Installation
| Platform | Command | | Platform | Command |
@@ -53,6 +74,7 @@ python run_gazebo.py --pattern circular --speed 0.3
| Standalone | ✅ | ✅ | ✅ | ✅ | ✅ | | Standalone | ✅ | ✅ | ✅ | ✅ | ✅ |
| PyBullet + ROS 2 | ✅ | ⚠️ | ❌ | ❌ | ✅ | | PyBullet + ROS 2 | ✅ | ⚠️ | ❌ | ❌ | ✅ |
| Gazebo + ROS 2 | ✅ | ⚠️ | ❌ | ❌ | ✅ | | Gazebo + ROS 2 | ✅ | ⚠️ | ❌ | ❌ | ✅ |
| ArduPilot + Gazebo | ✅ | ⚠️ | ❌ | ❌ | ✅ |
## Files ## Files
@@ -62,6 +84,8 @@ python run_gazebo.py --pattern circular --speed 0.3
| `simulation_host.py` | PyBullet simulator server | | `simulation_host.py` | PyBullet simulator server |
| `run_bridge.py` | PyBullet bridge + Controllers | | `run_bridge.py` | PyBullet bridge + Controllers |
| `run_gazebo.py` | Gazebo bridge + Controllers | | `run_gazebo.py` | Gazebo bridge + Controllers |
| `run_ardupilot.py` | **ArduPilot SITL** + MAVLink bridge |
| `mavlink_bridge.py` | MAVLink ↔ ROS 2 bridge |
| `config.py` | **Configuration file** (edit to customize) | | `config.py` | **Configuration file** (edit to customize) |
| `drone_controller.py` | **Your landing algorithm** (edit this!) | | `drone_controller.py` | **Your landing algorithm** (edit this!) |
| `rover_controller.py` | Moving landing pad controller | | `rover_controller.py` | Moving landing pad controller |
@@ -111,6 +135,7 @@ Options:
| [Architecture](docs/architecture.md) | System components diagram | | [Architecture](docs/architecture.md) | System components diagram |
| [Gazebo Guide](docs/gazebo.md) | Gazebo-specific instructions | | [Gazebo Guide](docs/gazebo.md) | Gazebo-specific instructions |
| [PyBullet Guide](docs/pybullet.md) | PyBullet-specific instructions | | [PyBullet Guide](docs/pybullet.md) | PyBullet-specific instructions |
| [ArduPilot Guide](docs/ardupilot.md) | **ArduPilot SITL + MAVProxy** |
| [Protocol](docs/protocol.md) | Sensor data formats | | [Protocol](docs/protocol.md) | Sensor data formats |
| [Drone Guide](docs/drone_guide.md) | Landing algorithm guide | | [Drone Guide](docs/drone_guide.md) | Landing algorithm guide |

View File

@@ -101,3 +101,49 @@ CONTROLLER = {
# Control rate # Control rate
"rate": 50, # Control loop frequency (Hz) "rate": 50, # Control loop frequency (Hz)
} }
# =============================================================================
# ARDUPILOT / MAVLINK CONFIGURATION
# =============================================================================
ARDUPILOT = {
# Vehicle type
"vehicle": "ArduCopter", # ArduCopter, ArduPlane, APMrover2
"frame": "gazebo-iris", # Gazebo model frame
# SITL connection
"sitl_host": "127.0.0.1", # SITL host address
"sitl_port": 5760, # SITL TCP port
# MAVProxy output (for MAVLink bridge)
"mavproxy_host": "127.0.0.1",
"mavproxy_port": 14550, # MAVProxy UDP output
# Gazebo plugin connection (JSON interface)
"gazebo_fdm_port_in": 9002, # Port for motor commands
"gazebo_fdm_port_out": 9003, # Port for sensor data
# Arming requirements (for simulation, can be relaxed)
"require_gps": False, # GPS required for arming
"require_ekf": True, # EKF required for arming
}
MAVLINK = {
# MAVLink system IDs
"system_id": 1, # Our system ID
"component_id": 191, # MAV_COMP_ID_MISSIONPLANNER
# Target system (usually the autopilot)
"target_system": 1,
"target_component": 1,
# Connection timeout (seconds)
"heartbeat_timeout": 5.0,
"connection_timeout": 30.0,
# Data stream rates (Hz)
"stream_rate_position": 50,
"stream_rate_attitude": 50,
"stream_rate_raw_sensors": 50,
"stream_rate_extended_status": 5,
}

View File

@@ -62,6 +62,34 @@ Data flow:
- DroneController receives telemetry, publishes to `/cmd_vel` - DroneController receives telemetry, publishes to `/cmd_vel`
- GazeboBridge forwards to `/drone/cmd_vel` → Gazebo moves drone - GazeboBridge forwards to `/drone/cmd_vel` → Gazebo moves drone
### 4. ArduPilot SITL + Gazebo Mode (Three Terminals, Linux/WSL2)
```
Terminal 1 Terminal 2 Terminal 3
┌──────────────┐ ┌─────────────────┐ ┌────────────────────────┐
│ Gazebo + │ │ ArduPilot SITL │ │ run_ardupilot.py │
│ ArduPilot │◄──►│ sim_vehicle.py │ │ ┌──────────────────┐ │
│ Plugin │JSON│ + MAVProxy │◄───►│ │ MAVLinkBridge │ │
│ │ │ │ UDP │ │ DroneController │ │
│ ardupilot_ │ │ Flight Control │ │ │ RoverController │ │
│ drone.sdf │ │ + GCS │ │ └──────────────────┘ │
└──────────────┘ └─────────────────┘ └────────────────────────┘
```
Data flow:
- ArduPilot SITL sends motor commands → Gazebo plugin controls drone
- Gazebo plugin sends sensor data → ArduPilot SITL for state estimation
- MAVProxy outputs telemetry → MAVLinkBridge converts to ROS telemetry
- DroneController receives telemetry, publishes velocity commands
- MAVLinkBridge sends MAVLink commands → ArduPilot SITL executes
Key differences from simple Gazebo mode:
- Full ArduPilot flight controller (EKF, stabilization, failsafes)
- Real MAVLink protocol for commands and telemetry
- Support for all ArduPilot flight modes (GUIDED, LAND, etc.)
- Arming checks and safety features
- Compatible with ground control stations (QGroundControl, Mission Planner)
## Components ## Components
| File | Description | | File | Description |
@@ -71,12 +99,16 @@ Data flow:
| `simulation_host.py` | PyBullet physics server (UDP) | | `simulation_host.py` | PyBullet physics server (UDP) |
| `run_bridge.py` | PyBullet bridge + controllers | | `run_bridge.py` | PyBullet bridge + controllers |
| `run_gazebo.py` | Gazebo bridge + controllers | | `run_gazebo.py` | Gazebo bridge + controllers |
| `run_ardupilot.py` | **ArduPilot SITL** + MAVLink bridge |
| `mavlink_bridge.py` | MAVLink ↔ ROS 2 bridge |
| `drone_controller.py` | **Your landing algorithm** | | `drone_controller.py` | **Your landing algorithm** |
| `rover_controller.py` | Moving landing pad | | `rover_controller.py` | Moving landing pad |
| `ros_bridge.py` | ROS-UDP bridge (used by run_bridge.py) | | `ros_bridge.py` | ROS-UDP bridge (used by run_bridge.py) |
| `gazebo_bridge.py` | Gazebo-ROS bridge (used by run_gazebo.py) | | `gazebo_bridge.py` | Gazebo-ROS bridge (used by run_gazebo.py) |
| `gazebo/launch/drone_landing.launch.py` | ROS 2 launch file for Gazebo | | `gazebo/launch/drone_landing.launch.py` | ROS 2 launch file for Gazebo |
| `gazebo/worlds/drone_landing.sdf` | Gazebo world with drone + rover | | `gazebo/launch/ardupilot_drone.launch.py` | ROS 2 launch file for ArduPilot |
| `gazebo/worlds/drone_landing.sdf` | Gazebo world with simple velocity control |
| `gazebo/worlds/ardupilot_drone.sdf` | Gazebo world with ArduPilot plugin |
## ROS 2 Topics ## ROS 2 Topics

285
docs/ardupilot.md Normal file
View File

@@ -0,0 +1,285 @@
# ArduPilot SITL + Gazebo Integration
This guide explains how to run the drone simulation with ArduPilot Software-In-The-Loop (SITL) and MAVProxy, providing a realistic flight controller stack.
## Overview
The ArduPilot integration replaces the simple velocity control with a full ArduPilot flight stack:
```
┌──────────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ ArduPilot SITL │◄───►│ Gazebo + Plugin │◄───►│ MAVLink Bridge │
│ (Flight Control)│ JSON│ (Physics Sim) │ ROS │ + Controllers │
└──────────────────┘ └─────────────────┘ └──────────────────┘
▲ │
│ UDP │
│ ▼
┌──────────────────┐ ┌──────────────────┐
│ MAVProxy │◄────────────────────────────►│ DroneController │
│ (GCS) │ MAVLink Commands │ (Your Algorithm) │
└──────────────────┘ └──────────────────┘
```
## Components
| Component | Description |
|-----------|-------------|
| **ArduPilot SITL** | Full autopilot firmware running in simulation |
| **ardupilot_gazebo** | Plugin connecting Gazebo physics to ArduPilot |
| **MAVProxy** | Ground Control Station for monitoring/commands |
| **MAVLink Bridge** | ROS 2 node bridging MAVLink ↔ ROS topics |
| **Drone Controller** | Your landing algorithm |
## Prerequisites
### 1. ArduPilot SITL
Install ArduPilot development environment:
```bash
# Ubuntu/Debian
git clone https://github.com/ArduPilot/ardupilot.git ~/ardupilot
cd ~/ardupilot
git submodule update --init --recursive
Tools/environment_install/install-prereqs-ubuntu.sh -y
. ~/.profile
# Set environment
echo 'export PATH=$PATH:$HOME/ardupilot/Tools/autotest' >> ~/.bashrc
echo 'export ARDUPILOT_HOME=$HOME/ardupilot' >> ~/.bashrc
source ~/.bashrc
```
### 2. ArduPilot Gazebo Plugin
Install the ardupilot_gazebo plugin:
```bash
# For Gazebo Garden/Harmonic
git clone https://github.com/ArduPilot/ardupilot_gazebo.git ~/ardupilot_gazebo
cd ~/ardupilot_gazebo
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j4
# Add to Gazebo plugin path
echo 'export GZ_SIM_SYSTEM_PLUGIN_PATH=$HOME/ardupilot_gazebo/build:$GZ_SIM_SYSTEM_PLUGIN_PATH' >> ~/.bashrc
echo 'export GZ_SIM_RESOURCE_PATH=$HOME/ardupilot_gazebo/models:$HOME/ardupilot_gazebo/worlds:$GZ_SIM_RESOURCE_PATH' >> ~/.bashrc
source ~/.bashrc
```
### 3. pymavlink
```bash
pip install pymavlink
```
## Quick Start
### Option 1: Integrated Launch (Recommended)
This starts everything together:
```bash
# Terminal 1: Start Gazebo
ros2 launch gazebo/launch/ardupilot_drone.launch.py
# Terminal 2: Start SITL
cd ~/ardupilot
sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON --console --map
# Terminal 3: Run bridge + controllers
python run_ardupilot.py --no-sitl --pattern circular
```
### Option 2: Manual Setup
```bash
# Terminal 1: Start Gazebo world
gz sim -r gazebo/worlds/ardupilot_drone.sdf
# Terminal 2: Start ArduPilot SITL
cd ~/ardupilot
sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON --console
# Terminal 3: Run MAVLink bridge + controllers
python run_ardupilot.py --no-sitl
```
### Option 3: Full Automatic
```bash
# Starts everything (requires SITL installed)
python run_ardupilot.py --pattern circular --console --map
```
## Flight Operations
### Using MAVProxy Commands
Once connected, use MAVProxy to control the drone:
```bash
# Set GUIDED mode for algorithm control
mode guided
# Arm motors
arm throttle
# Take off to 5 meters
takeoff 5
# Land
mode land
# Disarm
disarm
```
### Using the MAVLink Bridge API
From Python, you can control the drone directly:
```python
from mavlink_bridge import MAVLinkBridge
# Create bridge
bridge = MAVLinkBridge(sitl_port=14550)
# Arm and takeoff
bridge.set_mode('GUIDED')
bridge.arm()
bridge.takeoff(altitude=5.0)
# Land
bridge.land()
```
## Files
| File | Description |
|------|-------------|
| `mavlink_bridge.py` | ROS 2 ↔ MAVLink bridge |
| `run_ardupilot.py` | Integrated launcher |
| `gazebo/worlds/ardupilot_drone.sdf` | Gazebo world with ArduPilot plugin |
| `gazebo/launch/ardupilot_drone.launch.py` | ROS 2 launch file |
## Configuration
Edit `config.py` to adjust ArduPilot settings:
```python
ARDUPILOT = {
"vehicle": "ArduCopter", # ArduCopter, ArduPlane, APMrover2
"frame": "gazebo-iris", # Gazebo frame
"sitl_host": "127.0.0.1",
"sitl_port": 5760,
"mavproxy_port": 14550,
}
MAVLINK = {
"system_id": 1,
"component_id": 191,
"heartbeat_timeout": 5.0,
}
```
## Telemetry Format
The MAVLink bridge publishes telemetry in the same format as other modes:
```json
{
"imu": {
"orientation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0},
"angular_velocity": {"x": 0.0, "y": 0.0, "z": 0.0}
},
"altimeter": {
"altitude": 5.0,
"vertical_velocity": 0.0
},
"velocity": {"x": 0.0, "y": 0.0, "z": 0.0},
"position": {"x": 0.0, "y": 0.0, "z": 5.0},
"landing_pad": {
"relative_x": 0.5,
"relative_y": 0.2,
"distance": 4.8,
"confidence": 0.95
},
"battery": {"voltage": 12.6, "remaining": 100},
"armed": true,
"flight_mode": "GUIDED",
"connected": true
}
```
## Troubleshooting
### SITL Not Starting
```bash
# Check if SITL is installed
which sim_vehicle.py
# Set ArduPilot path
export ARDUPILOT_HOME=~/ardupilot
export PATH=$PATH:$ARDUPILOT_HOME/Tools/autotest
```
### Gazebo Plugin Not Found
```bash
# Check plugin path
echo $GZ_SIM_SYSTEM_PLUGIN_PATH
# Verify plugin exists
ls ~/ardupilot_gazebo/build/libArduPilotPlugin.so
```
### No MAVLink Connection
```bash
# Check if SITL is listening
netstat -tuln | grep 14550
# Test with mavlink console
python -c "from pymavlink import mavutil; c = mavutil.mavlink_connection('udpin:127.0.0.1:14550'); print(c.wait_heartbeat())"
```
### Drone Won't Arm
Common issues:
1. **Pre-arm checks failing** - Check MAVProxy console for errors
2. **GPS required** - In simulation, you may need to wait for GPS lock
3. **EKF not ready** - Wait for EKF to initialize
Disable pre-arm checks for testing (not recommended for real flights):
```
# In MAVProxy
param set ARMING_CHECK 0
```
## Flight Modes
| Mode | Description |
|------|-------------|
| **GUIDED** | Accept velocity/position commands from controller |
| **LOITER** | Hold position (GPS required) |
| **ALT_HOLD** | Maintain altitude, manual horizontal |
| **LAND** | Automatic landing |
| **STABILIZE** | Attitude stabilization only |
For autonomous landing, use **GUIDED** mode.
## Architecture Comparison
| Feature | Simple Gazebo | ArduPilot + Gazebo |
|---------|--------------|-------------------|
| Flight Controller | Velocity control | Full ArduPilot |
| Stabilization | Manual PD | Inbuilt EKF + PID |
| Flight Modes | None | All ArduPilot modes |
| Arming | Not required | Safety checks |
| Failsafes | None | Battery, GPS, etc. |
| MAVLink | No | Full protocol |
| GCS Support | No | QGC, Mission Planner |
| Realism | Low | High |

View File

@@ -7,6 +7,7 @@ Setup instructions for all supported platforms.
| Platform | Command | | Platform | Command |
|----------|---------| |----------|---------|
| Ubuntu/Debian | `./setup/install_ubuntu.sh` | | Ubuntu/Debian | `./setup/install_ubuntu.sh` |
| Ubuntu + ArduPilot | `./setup/install_ubuntu.sh --with-ardupilot` |
| Arch Linux | `./setup/install_arch.sh` | | Arch Linux | `./setup/install_arch.sh` |
| macOS | `./setup/install_macos.sh` | | macOS | `./setup/install_macos.sh` |
| Windows | `.\setup\install_windows.ps1` | | Windows | `.\setup\install_windows.ps1` |
@@ -28,6 +29,7 @@ python standalone_simulation.py
| **Standalone Simulation** | ✅ | ✅ | ✅ | ✅ | ✅ | | **Standalone Simulation** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **ROS 2** | ✅ | ⚠️ AUR | ❌ | ❌ | ✅ | | **ROS 2** | ✅ | ⚠️ AUR | ❌ | ❌ | ✅ |
| **Gazebo** | ✅ | ⚠️ AUR | ❌ | ❌ | ✅ | | **Gazebo** | ✅ | ⚠️ AUR | ❌ | ❌ | ✅ |
| **ArduPilot SITL** | ✅ | ⚠️ Manual | ❌ | ❌ | ✅ |
| **Full Mode** | ✅ | ⚠️ | ❌ | ❌ | ✅ | | **Full Mode** | ✅ | ⚠️ | ❌ | ❌ | ✅ |
| **GUI Support** | ✅ | ✅ | ✅ | ✅ | ✅ WSLg | | **GUI Support** | ✅ | ✅ | ✅ | ✅ | ✅ WSLg |
@@ -58,7 +60,18 @@ python standalone_simulation.py
**Installs:** **Installs:**
- ROS 2 (Humble or Jazzy based on Ubuntu version) - ROS 2 (Humble or Jazzy based on Ubuntu version)
- Gazebo (ros-gz) - Gazebo (ros-gz)
- Python packages: pybullet, numpy, pillow, pyinstaller - Python packages: pybullet, numpy, pillow, pyinstaller, pymavlink
**With ArduPilot SITL (full flight controller):**
```bash
# Run installer with ArduPilot
./setup/install_ubuntu.sh --with-ardupilot
# This will also install:
# - ArduPilot SITL (~15-20 min build)
# - ArduPilot Gazebo plugin
# - MAVProxy
```
--- ---
@@ -407,6 +420,84 @@ After installation, verify packages:
python -c "import pybullet; print('PyBullet OK')" python -c "import pybullet; print('PyBullet OK')"
python -c "import numpy; print('NumPy OK')" python -c "import numpy; print('NumPy OK')"
python -c "from PIL import Image; print('Pillow OK')" python -c "from PIL import Image; print('Pillow OK')"
python -c "from pymavlink import mavutil; print('pymavlink OK')"
``` ```
All should print "OK". All should print "OK".
---
## ArduPilot SITL Manual Setup
If you want to install ArduPilot SITL manually (without the install script):
### 1. Install ArduPilot
```bash
# Clone ArduPilot
git clone --recurse-submodules https://github.com/ArduPilot/ardupilot.git ~/ardupilot
cd ~/ardupilot
# Install prerequisites (Ubuntu)
Tools/environment_install/install-prereqs-ubuntu.sh -y
# Reload profile
. ~/.profile
# Build ArduCopter SITL
./waf configure --board sitl
./waf copter
```
### 2. Install ArduPilot Gazebo Plugin
```bash
# Clone plugin
git clone https://github.com/ArduPilot/ardupilot_gazebo.git ~/ardupilot_gazebo
cd ~/ardupilot_gazebo
# Build
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
```
### 3. Set Environment Variables
Add to `~/.bashrc`:
```bash
# ArduPilot
export ARDUPILOT_HOME=$HOME/ardupilot
export PATH=$PATH:$ARDUPILOT_HOME/Tools/autotest
# ArduPilot Gazebo Plugin
export GZ_SIM_SYSTEM_PLUGIN_PATH=$HOME/ardupilot_gazebo/build:$GZ_SIM_SYSTEM_PLUGIN_PATH
export GZ_SIM_RESOURCE_PATH=$HOME/ardupilot_gazebo/models:$HOME/ardupilot_gazebo/worlds:$GZ_SIM_RESOURCE_PATH
```
### 4. Test SITL
```bash
# Test ArduCopter SITL
cd ~/ardupilot
sim_vehicle.py -v ArduCopter --console --map
```
### 5. Run with Gazebo
```bash
# Terminal 1: Launch Gazebo
ros2 launch gazebo/launch/ardupilot_drone.launch.py
# Terminal 2: Start SITL
cd ~/ardupilot
sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON --console
# Terminal 3: Run controllers
cd ~/simulation
source activate.sh
python run_ardupilot.py --no-sitl --pattern circular
```
For more details, see [ArduPilot Guide](ardupilot.md).

View File

@@ -174,6 +174,9 @@ class DroneController(Node):
landing_pad = telemetry.get('landing_pad', None) landing_pad = telemetry.get('landing_pad', None)
# camera = telemetry.get('camera', None)
# Descent control # Descent control
if altitude > 1.0: if altitude > 1.0:
target_descent_rate = -0.5 target_descent_rate = -0.5

View File

@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""
ArduPilot SITL + Gazebo Launch File
Works with Gazebo Harmonic/Garden and ArduPilot SITL.
This launch file:
1. Starts Gazebo with ArduPilot-compatible drone model
2. Sets up ROS-Gazebo bridge for telemetry
3. Optionally starts ArduPilot SITL
NOTE: ArduPilot SITL integration requires the ardupilot_gazebo plugin.
Install from: https://github.com/ArduPilot/ardupilot_gazebo
"""
import os
import shutil
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
"""Generate the launch description."""
# Get paths
script_dir = os.path.dirname(os.path.abspath(__file__))
gazebo_dir = os.path.dirname(script_dir)
world_file = os.path.join(gazebo_dir, 'worlds', 'ardupilot_drone.sdf')
# Check for ArduPilot Gazebo plugin
plugin_paths = [
os.path.expanduser("~/ardupilot_gazebo/build"),
"/opt/ardupilot_gazebo/lib",
os.environ.get("GZ_SIM_SYSTEM_PLUGIN_PATH", ""),
]
ardupilot_plugin_found = any(os.path.exists(p) for p in plugin_paths if p)
# Determine Gazebo command
if shutil.which('gz'):
gz_cmd = ['gz', 'sim', '-r', world_file]
elif shutil.which('ign'):
gz_cmd = ['ign', 'gazebo', '-r', world_file]
else:
gz_cmd = ['gz', 'sim', '-r', world_file]
use_sim_time = LaunchConfiguration('use_sim_time', default='true')
start_sitl = LaunchConfiguration('start_sitl', default='false')
# Set plugin path for ArduPilot
env = os.environ.copy()
plugin_search_paths = ':'.join([p for p in plugin_paths if p and os.path.exists(p)])
if plugin_search_paths:
env['GZ_SIM_SYSTEM_PLUGIN_PATH'] = plugin_search_paths + ':' + env.get('GZ_SIM_SYSTEM_PLUGIN_PATH', '')
actions = [
DeclareLaunchArgument(
'use_sim_time',
default_value='true',
description='Use simulation clock'
),
DeclareLaunchArgument(
'start_sitl',
default_value='false',
description='Start ArduPilot SITL automatically'
),
# Start Gazebo with ArduPilot world
ExecuteProcess(
cmd=gz_cmd,
output='screen',
additional_env=env
),
# ROS-Gazebo Bridge for telemetry and commands
# Delayed start to wait for Gazebo
TimerAction(
period=2.0,
actions=[
Node(
package='ros_gz_bridge',
executable='parameter_bridge',
name='gz_bridge',
arguments=[
# Rover velocity commands (ROS to Gazebo)
'/rover/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist',
# Odometry (from Gazebo to ROS) - fallback if MAVLink not used
'/model/drone/odometry@nav_msgs/msg/Odometry[gz.msgs.Odometry',
# Camera (from Gazebo to ROS)
'/drone/camera@sensor_msgs/msg/Image[gz.msgs.Image',
# IMU (from Gazebo to ROS)
'/imu@sensor_msgs/msg/Imu[gz.msgs.IMU',
# Clock
'/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock',
],
parameters=[{'use_sim_time': use_sim_time}],
output='screen'
),
]
),
]
return LaunchDescription(actions)
if __name__ == '__main__':
print("=" * 60)
print(" ArduPilot SITL + Gazebo Launch File")
print("=" * 60)
print()
print("This is a ROS 2 launch file for ArduPilot integration.")
print()
print("Prerequisites:")
print(" 1. Gazebo (gz sim or ign gazebo)")
print(" 2. ros_gz_bridge package")
print(" 3. ArduPilot Gazebo plugin (optional, for SITL)")
print()
print("Usage:")
print(" ros2 launch gazebo/launch/ardupilot_drone.launch.py")
print()
print("Then in another terminal:")
print(" # Start SITL")
print(" sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON")
print()
print(" # Or use the integrated runner")
print(" python run_ardupilot.py --no-sitl")
print()
print("Manual Gazebo Start:")
if shutil.which('gz'):
print(" gz sim -r gazebo/worlds/ardupilot_drone.sdf")
else:
print(" ign gazebo gazebo/worlds/ardupilot_drone.sdf")

View File

@@ -0,0 +1,504 @@
<?xml version="1.0" ?>
<!--
ArduPilot SITL + Gazebo Drone Landing World
This world integrates with ArduPilot SITL via the ardupilot_gazebo plugin.
The drone receives motor commands from ArduPilot and sends sensor data back.
Prerequisites:
1. ArduPilot Gazebo Plugin: https://github.com/ArduPilot/ardupilot_gazebo
2. ArduPilot SITL with JSON backend
Usage:
1. Start Gazebo: gz sim -r gazebo/worlds/ardupilot_drone.sdf
2. Start SITL: sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON
3. Run bridge: python run_ardupilot.py --no-sitl
-->
<sdf version="1.9">
<world name="ardupilot_landing_world">
<!-- Physics - match ArduPilot defaults -->
<physics name="1ms" type="ode">
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>
<!-- Gazebo Plugins -->
<plugin filename="gz-sim-physics-system"
name="gz::sim::systems::Physics"/>
<plugin filename="gz-sim-user-commands-system"
name="gz::sim::systems::UserCommands"/>
<plugin filename="gz-sim-scene-broadcaster-system"
name="gz::sim::systems::SceneBroadcaster"/>
<plugin filename="gz-sim-sensors-system"
name="gz::sim::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
<plugin filename="gz-sim-imu-system"
name="gz::sim::systems::Imu"/>
<plugin filename="gz-sim-air-pressure-system"
name="gz::sim::systems::AirPressure"/>
<plugin filename="gz-sim-altimeter-system"
name="gz::sim::systems::Altimeter"/>
<plugin filename="gz-sim-navsat-system"
name="gz::sim::systems::NavSat"/>
<!-- Lighting -->
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<direction>-0.5 0.1 -0.9</direction>
</light>
<light type="directional" name="fill_light">
<cast_shadows>false</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.3 0.3 0.3 1</diffuse>
<specular>0.0 0.0 0.0 1</specular>
<direction>0.5 0.3 -0.5</direction>
</light>
<!-- Ground Plane -->
<model name="ground_plane">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<surface>
<friction>
<ode>
<mu>1</mu>
<mu2>1</mu2>
</ode>
</friction>
</surface>
</collision>
<visual name="visual">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<material>
<ambient>0.5 0.5 0.5 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
</material>
</visual>
</link>
</model>
<!-- Landing Pad (Rover) - Velocity controlled for moving target -->
<model name="landing_pad">
<pose>0 0 0.15 0 0 0</pose>
<link name="base_link">
<inertial>
<mass>10.0</mass>
<inertia>
<ixx>1.0</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>1.0</iyy><iyz>0</iyz>
<izz>1.0</izz>
</inertia>
</inertial>
<visual name="visual">
<geometry>
<box><size>1.0 1.0 0.3</size></box>
</geometry>
<material>
<ambient>0.1 0.5 0.1 1</ambient>
<diffuse>0.2 0.7 0.2 1</diffuse>
</material>
</visual>
<collision name="collision">
<geometry>
<box><size>1.0 1.0 0.3</size></box>
</geometry>
</collision>
<!-- H marker -->
<visual name="h_center">
<pose>0 0 0.151 0 0 0</pose>
<geometry><box><size>0.6 0.1 0.001</size></box></geometry>
<material>
<ambient>1 1 1 1</ambient>
<diffuse>1 1 1 1</diffuse>
</material>
</visual>
<visual name="h_left">
<pose>-0.25 0 0.151 0 0 0</pose>
<geometry><box><size>0.1 0.5 0.001</size></box></geometry>
<material>
<ambient>1 1 1 1</ambient>
<diffuse>1 1 1 1</diffuse>
</material>
</visual>
<visual name="h_right">
<pose>0.25 0 0.151 0 0 0</pose>
<geometry><box><size>0.1 0.5 0.001</size></box></geometry>
<material>
<ambient>1 1 1 1</ambient>
<diffuse>1 1 1 1</diffuse>
</material>
</visual>
</link>
<!-- Velocity control for rover -->
<plugin filename="gz-sim-velocity-control-system"
name="gz::sim::systems::VelocityControl">
<topic>/rover/cmd_vel</topic>
<initial_linear>0 0 0</initial_linear>
</plugin>
</model>
<!--
ArduPilot Iris Quadcopter
This model uses the ArduPilot Gazebo plugin for flight control.
Motor commands come from ArduPilot SITL, and sensor data is sent back.
-->
<model name="iris_with_ardupilot">
<pose>0 0 0.194923 0 0 0</pose>
<link name="base_link">
<inertial>
<pose>0 0 0 0 0 0</pose>
<mass>1.5</mass>
<inertia>
<ixx>0.029125</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.029125</iyy>
<iyz>0</iyz>
<izz>0.055225</izz>
</inertia>
</inertial>
<collision name="base_link_collision">
<geometry>
<box><size>0.47 0.47 0.11</size></box>
</geometry>
</collision>
<!-- Main body -->
<visual name="base_link_visual">
<geometry>
<box><size>0.3 0.3 0.1</size></box>
</geometry>
<material>
<ambient>0.8 0.1 0.1 1</ambient>
<diffuse>0.9 0.2 0.2 1</diffuse>
</material>
</visual>
<!-- Arms -->
<visual name="arm_fl">
<pose>0.12 0.12 0 0 0 0.785</pose>
<geometry><box><size>0.2 0.02 0.02</size></box></geometry>
<material><diffuse>0.3 0.3 0.3 1</diffuse></material>
</visual>
<visual name="arm_fr">
<pose>0.12 -0.12 0 0 0 -0.785</pose>
<geometry><box><size>0.2 0.02 0.02</size></box></geometry>
<material><diffuse>0.3 0.3 0.3 1</diffuse></material>
</visual>
<visual name="arm_bl">
<pose>-0.12 0.12 0 0 0 -0.785</pose>
<geometry><box><size>0.2 0.02 0.02</size></box></geometry>
<material><diffuse>0.3 0.3 0.3 1</diffuse></material>
</visual>
<visual name="arm_br">
<pose>-0.12 -0.12 0 0 0 0.785</pose>
<geometry><box><size>0.2 0.02 0.02</size></box></geometry>
<material><diffuse>0.3 0.3 0.3 1</diffuse></material>
</visual>
<!-- GPS antenna indicator -->
<visual name="gps_antenna">
<pose>0 0 0.08 0 0 0</pose>
<geometry><cylinder><radius>0.02</radius><length>0.04</length></cylinder></geometry>
<material><diffuse>0.2 0.2 0.8 1</diffuse></material>
</visual>
<!-- IMU Sensor -->
<sensor name="imu_sensor" type="imu">
<always_on>true</always_on>
<update_rate>400</update_rate>
<topic>imu</topic>
<imu>
<angular_velocity>
<x><noise type="gaussian"><mean>0</mean><stddev>0.0002</stddev></noise></x>
<y><noise type="gaussian"><mean>0</mean><stddev>0.0002</stddev></noise></y>
<z><noise type="gaussian"><mean>0</mean><stddev>0.0002</stddev></noise></z>
</angular_velocity>
<linear_acceleration>
<x><noise type="gaussian"><mean>0</mean><stddev>0.017</stddev></noise></x>
<y><noise type="gaussian"><mean>0</mean><stddev>0.017</stddev></noise></y>
<z><noise type="gaussian"><mean>0</mean><stddev>0.017</stddev></noise></z>
</linear_acceleration>
</imu>
</sensor>
<!-- Air Pressure Sensor (Barometer) -->
<sensor name="air_pressure" type="air_pressure">
<always_on>true</always_on>
<update_rate>50</update_rate>
<topic>air_pressure</topic>
<air_pressure>
<reference_altitude>0</reference_altitude>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.01</stddev>
</noise>
</air_pressure>
</sensor>
<!-- Downward Camera for Landing Pad Detection -->
<sensor name="camera" type="camera">
<pose>0 0 -0.05 0 1.5708 0</pose>
<always_on>true</always_on>
<update_rate>30</update_rate>
<topic>/drone/camera</topic>
<camera>
<horizontal_fov>1.047</horizontal_fov>
<image>
<width>320</width>
<height>240</height>
<format>R8G8B8</format>
</image>
<clip>
<near>0.1</near>
<far>100</far>
</clip>
</camera>
</sensor>
</link>
<!-- Rotor 0: Front Right (CCW) -->
<link name="rotor_0">
<pose>0.13 -0.22 0.023 0 0 0</pose>
<inertial>
<mass>0.025</mass>
<inertia>
<ixx>9.75e-06</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>1.66704e-04</iyy><iyz>0</iyz>
<izz>1.66704e-04</izz>
</inertia>
</inertial>
<visual name="visual">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
<material><diffuse>0.1 0.1 0.1 1</diffuse></material>
</visual>
<collision name="collision">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
</collision>
</link>
<joint name="rotor_0_joint" type="revolute">
<parent>base_link</parent>
<child>rotor_0</child>
<axis>
<xyz>0 0 1</xyz>
<limit><lower>-1e16</lower><upper>1e16</upper></limit>
</axis>
</joint>
<!-- Rotor 1: Back Left (CCW) -->
<link name="rotor_1">
<pose>-0.13 0.2 0.023 0 0 0</pose>
<inertial>
<mass>0.025</mass>
<inertia>
<ixx>9.75e-06</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>1.66704e-04</iyy><iyz>0</iyz>
<izz>1.66704e-04</izz>
</inertia>
</inertial>
<visual name="visual">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
<material><diffuse>0.1 0.1 0.1 1</diffuse></material>
</visual>
<collision name="collision">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
</collision>
</link>
<joint name="rotor_1_joint" type="revolute">
<parent>base_link</parent>
<child>rotor_1</child>
<axis>
<xyz>0 0 1</xyz>
<limit><lower>-1e16</lower><upper>1e16</upper></limit>
</axis>
</joint>
<!-- Rotor 2: Front Left (CW) -->
<link name="rotor_2">
<pose>0.13 0.22 0.023 0 0 0</pose>
<inertial>
<mass>0.025</mass>
<inertia>
<ixx>9.75e-06</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>1.66704e-04</iyy><iyz>0</iyz>
<izz>1.66704e-04</izz>
</inertia>
</inertial>
<visual name="visual">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
<material><diffuse>0.1 0.1 0.1 1</diffuse></material>
</visual>
<collision name="collision">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
</collision>
</link>
<joint name="rotor_2_joint" type="revolute">
<parent>base_link</parent>
<child>rotor_2</child>
<axis>
<xyz>0 0 1</xyz>
<limit><lower>-1e16</lower><upper>1e16</upper></limit>
</axis>
</joint>
<!-- Rotor 3: Back Right (CW) -->
<link name="rotor_3">
<pose>-0.13 -0.2 0.023 0 0 0</pose>
<inertial>
<mass>0.025</mass>
<inertia>
<ixx>9.75e-06</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>1.66704e-04</iyy><iyz>0</iyz>
<izz>1.66704e-04</izz>
</inertia>
</inertial>
<visual name="visual">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
<material><diffuse>0.1 0.1 0.1 1</diffuse></material>
</visual>
<collision name="collision">
<geometry><cylinder><radius>0.1</radius><length>0.005</length></cylinder></geometry>
</collision>
</link>
<joint name="rotor_3_joint" type="revolute">
<parent>base_link</parent>
<child>rotor_3</child>
<axis>
<xyz>0 0 1</xyz>
<limit><lower>-1e16</lower><upper>1e16</upper></limit>
</axis>
</joint>
<!--
ArduPilot Plugin Configuration
This plugin connects to ArduPilot SITL via the JSON interface.
It receives motor commands and sends back sensor data.
fdm_addr: Address of ArduPilot SITL (default 127.0.0.1)
fdm_port_in: Port to receive motor commands (default 9002)
listen_addr: Our address for sending sensor data (127.0.0.1)
listen_port_out: SITL port for receiving sensor data (9003)
If ardupilot_gazebo plugin is not available, this will be ignored.
-->
<plugin filename="ArduPilotPlugin"
name="gz::sim::systems::ArduPilotPlugin">
<!-- Connection settings -->
<fdm_addr>127.0.0.1</fdm_addr>
<fdm_port_in>9002</fdm_port_in>
<listen_addr>127.0.0.1</listen_addr>
<listen_port_out>9003</listen_port_out>
<!-- Model reference link -->
<modelXYZToAirplaneXForwardZDown>0 0 0 3.141593 0 0</modelXYZToAirplaneXForwardZDown>
<gazeboXYZToNED>0 0 0 3.141593 0 0</gazeboXYZToNED>
<!-- IMU configuration -->
<imuName>imu_sensor</imuName>
<!-- Motor configuration (CCW positive, CW negative) -->
<!-- Rotor 0: FR CCW -->
<control channel="0">
<jointName>rotor_0_joint</jointName>
<type>VELOCITY</type>
<offset>0</offset>
<multiplier>838</multiplier>
<p_gain>0.2</p_gain>
<i_gain>0</i_gain>
<d_gain>0</d_gain>
<i_max>0</i_max>
<i_min>0</i_min>
<cmd_max>2.5</cmd_max>
<cmd_min>-2.5</cmd_min>
</control>
<!-- Rotor 1: BL CCW -->
<control channel="1">
<jointName>rotor_1_joint</jointName>
<type>VELOCITY</type>
<offset>0</offset>
<multiplier>838</multiplier>
<p_gain>0.2</p_gain>
<i_gain>0</i_gain>
<d_gain>0</d_gain>
<i_max>0</i_max>
<i_min>0</i_min>
<cmd_max>2.5</cmd_max>
<cmd_min>-2.5</cmd_min>
</control>
<!-- Rotor 2: FL CW -->
<control channel="2">
<jointName>rotor_2_joint</jointName>
<type>VELOCITY</type>
<offset>0</offset>
<multiplier>-838</multiplier>
<p_gain>0.2</p_gain>
<i_gain>0</i_gain>
<d_gain>0</d_gain>
<i_max>0</i_max>
<i_min>0</i_min>
<cmd_max>2.5</cmd_max>
<cmd_min>-2.5</cmd_min>
</control>
<!-- Rotor 3: BR CW -->
<control channel="3">
<jointName>rotor_3_joint</jointName>
<type>VELOCITY</type>
<offset>0</offset>
<multiplier>-838</multiplier>
<p_gain>0.2</p_gain>
<i_gain>0</i_gain>
<d_gain>0</d_gain>
<i_max>0</i_max>
<i_min>0</i_min>
<cmd_max>2.5</cmd_max>
<cmd_min>-2.5</cmd_min>
</control>
</plugin>
<!-- Odometry publisher for compatibility -->
<plugin filename="gz-sim-odometry-publisher-system"
name="gz::sim::systems::OdometryPublisher">
<odom_frame>odom</odom_frame>
<robot_base_frame>base_link</robot_base_frame>
<odom_topic>/model/drone/odometry</odom_topic>
<odom_publish_frequency>50</odom_publish_frequency>
</plugin>
</model>
</world>
</sdf>

580
mavlink_bridge.py Normal file
View File

@@ -0,0 +1,580 @@
#!/usr/bin/env python3
"""
MAVLink Bridge for ArduPilot SITL Integration.
Connects Gazebo simulation to ArduPilot SITL via MAVProxy.
This bridge:
- Connects to ArduPilot SITL via MAVLink (UDP)
- Receives telemetry from ArduPilot (position, attitude, battery, etc.)
- Sends commands to ArduPilot (velocity, position, etc.)
- Publishes ROS 2 telemetry for the drone controller
"""
import json
import math
import time
import threading
from typing import Optional, Dict, Any, Tuple
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from std_msgs.msg import String
try:
from pymavlink import mavutil
from pymavlink.dialects.v20 import ardupilotmega as mavlink
MAVLINK_AVAILABLE = True
except ImportError:
MAVLINK_AVAILABLE = False
print("WARNING: pymavlink not installed. Run: pip install pymavlink")
class MAVLinkBridge(Node):
"""
ROS 2 Node that bridges between ROS topics and ArduPilot SITL via MAVLink.
"""
# MAVLink connection defaults
DEFAULT_SITL_HOST = "127.0.0.1"
DEFAULT_SITL_PORT = 14550 # MAVProxy output port
DEFAULT_SYSTEM_ID = 1
DEFAULT_COMPONENT_ID = 191 # MAV_COMP_ID_MISSIONPLANNER
# Telemetry rate (Hz)
TELEMETRY_RATE = 50.0
def __init__(
self,
sitl_host: str = None,
sitl_port: int = None,
system_id: int = None,
component_id: int = None
):
super().__init__('mavlink_bridge')
# Connection parameters (with parameter overrides)
self.declare_parameter('sitl_host', sitl_host or self.DEFAULT_SITL_HOST)
self.declare_parameter('sitl_port', sitl_port or self.DEFAULT_SITL_PORT)
self.declare_parameter('system_id', system_id or self.DEFAULT_SYSTEM_ID)
self.declare_parameter('component_id', component_id or self.DEFAULT_COMPONENT_ID)
self._sitl_host = self.get_parameter('sitl_host').value
self._sitl_port = self.get_parameter('sitl_port').value
self._system_id = self.get_parameter('system_id').value
self._component_id = self.get_parameter('component_id').value
self.get_logger().info('=' * 60)
self.get_logger().info('MAVLink Bridge Starting...')
self.get_logger().info(f' Connecting to SITL at {self._sitl_host}:{self._sitl_port}')
self.get_logger().info('=' * 60)
# MAVLink connection
self._mav_conn: Optional[mavutil.mavlink_connection] = None
self._connected = False
self._armed = False
self._flight_mode = "UNKNOWN"
# Telemetry state
self._position = [0.0, 0.0, 0.0] # x, y, z (NED frame, converted to ENU)
self._velocity = [0.0, 0.0, 0.0] # vx, vy, vz
self._attitude = [0.0, 0.0, 0.0] # roll, pitch, yaw (radians)
self._angular_velocity = [0.0, 0.0, 0.0] # wx, wy, wz
self._battery_voltage = 0.0
self._battery_remaining = 100
self._gps_fix = 0
self._landed = False
self._home_position = None
self._last_heartbeat = 0.0
# Rover position (for landing pad detection)
self._rover_position = [0.0, 0.0, 0.15]
# Thread safety
self._lock = threading.Lock()
# ROS 2 Subscriptions
self._cmd_vel_sub = self.create_subscription(
Twist, '/cmd_vel', self._cmd_vel_callback, 10
)
self.get_logger().info(' Subscribed to: /cmd_vel')
self._rover_sub = self.create_subscription(
String, '/rover/telemetry', self._rover_callback, 10
)
self.get_logger().info(' Subscribed to: /rover/telemetry')
# ROS 2 Publishers
self._telemetry_pub = self.create_publisher(
String, '/drone/telemetry', 10
)
self.get_logger().info(' Publishing to: /drone/telemetry')
self._status_pub = self.create_publisher(
String, '/mavlink/status', 10
)
self.get_logger().info(' Publishing to: /mavlink/status')
# Initialize MAVLink connection
self._connect_mavlink()
# Start telemetry thread
self._running = True
self._mavlink_thread = threading.Thread(target=self._mavlink_loop, daemon=True)
self._mavlink_thread.start()
# Telemetry timer
telemetry_period = 1.0 / self.TELEMETRY_RATE
self._telemetry_timer = self.create_timer(telemetry_period, self._publish_telemetry)
self.get_logger().info('MAVLink Bridge Ready!')
def _connect_mavlink(self) -> bool:
"""Establish MAVLink connection to ArduPilot SITL."""
if not MAVLINK_AVAILABLE:
self.get_logger().error('pymavlink not available!')
return False
try:
connection_string = f'udpin:{self._sitl_host}:{self._sitl_port}'
self.get_logger().info(f'Connecting to: {connection_string}')
self._mav_conn = mavutil.mavlink_connection(
connection_string,
source_system=self._system_id,
source_component=self._component_id
)
# Wait for heartbeat
self.get_logger().info('Waiting for heartbeat...')
self._mav_conn.wait_heartbeat(timeout=30)
self._connected = True
self._last_heartbeat = time.time()
self.get_logger().info(
f'Connected! System: {self._mav_conn.target_system}, '
f'Component: {self._mav_conn.target_component}'
)
# Request data streams
self._request_data_streams()
return True
except Exception as e:
self.get_logger().error(f'MAVLink connection failed: {e}')
self._connected = False
return False
def _request_data_streams(self):
"""Request required data streams from ArduPilot."""
if not self._mav_conn:
return
# Request all data streams at high rate
streams = [
(mavutil.mavlink.MAV_DATA_STREAM_ALL, 10),
(mavutil.mavlink.MAV_DATA_STREAM_RAW_SENSORS, 50),
(mavutil.mavlink.MAV_DATA_STREAM_EXTENDED_STATUS, 5),
(mavutil.mavlink.MAV_DATA_STREAM_POSITION, 50),
(mavutil.mavlink.MAV_DATA_STREAM_EXTRA1, 50), # Attitude
(mavutil.mavlink.MAV_DATA_STREAM_EXTRA2, 10), # VFR_HUD
]
for stream_id, rate in streams:
self._mav_conn.mav.request_data_stream_send(
self._mav_conn.target_system,
self._mav_conn.target_component,
stream_id,
rate,
1 # Start
)
def _mavlink_loop(self):
"""Background thread for receiving MAVLink messages."""
reconnect_delay = 2.0
while self._running:
if not self._connected:
time.sleep(reconnect_delay)
self._connect_mavlink()
continue
try:
# Non-blocking message receive
msg = self._mav_conn.recv_match(blocking=True, timeout=0.1)
if msg is None:
# Check for connection timeout
if time.time() - self._last_heartbeat > 5.0:
self.get_logger().warning('Heartbeat timeout, reconnecting...')
self._connected = False
continue
self._process_mavlink_message(msg)
except Exception as e:
self.get_logger().error(f'MAVLink receive error: {e}')
self._connected = False
def _process_mavlink_message(self, msg):
"""Process incoming MAVLink messages."""
msg_type = msg.get_type()
with self._lock:
if msg_type == 'HEARTBEAT':
self._last_heartbeat = time.time()
self._armed = msg.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED
self._flight_mode = mavutil.mode_string_v10(msg)
elif msg_type == 'LOCAL_POSITION_NED':
# Convert NED to ENU (ROS convention)
self._position = [msg.y, msg.x, -msg.z] # NED to ENU
self._velocity = [msg.vy, msg.vx, -msg.vz]
elif msg_type == 'GLOBAL_POSITION_INT':
# Backup position from GPS
if self._home_position is None:
self._home_position = (msg.lat, msg.lon, msg.alt)
elif msg_type == 'ATTITUDE':
self._attitude = [msg.roll, msg.pitch, msg.yaw]
self._angular_velocity = [msg.rollspeed, msg.pitchspeed, msg.yawspeed]
elif msg_type == 'SYS_STATUS':
self._battery_voltage = msg.voltage_battery / 1000.0 # mV to V
self._battery_remaining = msg.battery_remaining
elif msg_type == 'GPS_RAW_INT':
self._gps_fix = msg.fix_type
elif msg_type == 'EXTENDED_SYS_STATE':
# Check landed state
self._landed = (msg.landed_state == mavutil.mavlink.MAV_LANDED_STATE_ON_GROUND)
def _cmd_vel_callback(self, msg: Twist):
"""Handle velocity commands from drone controller."""
if not self._connected or not self._mav_conn:
return
try:
# Convert ROS Twist to MAVLink velocity command
# linear.x = forward (pitch), linear.y = left (roll), linear.z = up (thrust)
# angular.z = yaw rate
# Use SET_POSITION_TARGET_LOCAL_NED for velocity control
# Type mask: velocity only (ignore position and acceleration)
type_mask = (
mavutil.mavlink.POSITION_TARGET_TYPEMASK_X_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_Y_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_Z_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AX_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AY_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AZ_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_YAW_IGNORE
)
# Convert velocities (ENU to NED)
vx = msg.linear.x # Forward (body frame pitch command)
vy = msg.linear.y # Left (body frame roll command)
vz = -msg.linear.z # Down (negative because NED)
yaw_rate = msg.angular.z
self._mav_conn.mav.set_position_target_local_ned_send(
0, # time_boot_ms (not used)
self._mav_conn.target_system,
self._mav_conn.target_component,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
type_mask,
0, 0, 0, # position (ignored)
vx, vy, vz, # velocity
0, 0, 0, # acceleration (ignored)
0, yaw_rate # yaw, yaw_rate
)
except Exception as e:
self.get_logger().warning(f'Failed to send velocity command: {e}')
def _rover_callback(self, msg: String):
"""Receive rover position for landing pad detection."""
try:
data = json.loads(msg.data)
pos = data.get('position', {})
with self._lock:
self._rover_position = [
pos.get('x', 0.0),
pos.get('y', 0.0),
pos.get('z', 0.15)
]
except json.JSONDecodeError:
pass
def _get_landing_pad_detection(self) -> Optional[Dict[str, float]]:
"""Simulate landing pad detection based on relative position."""
# Camera parameters
CAMERA_FOV = 60.0
CAMERA_RANGE = 10.0
dx = self._rover_position[0] - self._position[0]
dy = self._rover_position[1] - self._position[1]
dz = self._rover_position[2] - self._position[2]
horizontal_dist = math.sqrt(dx**2 + dy**2)
vertical_dist = -dz # Height above rover
if vertical_dist <= 0 or vertical_dist > CAMERA_RANGE:
return None
fov_rad = math.radians(CAMERA_FOV / 2)
max_horizontal = vertical_dist * math.tan(fov_rad)
if horizontal_dist > max_horizontal:
return None
# Transform to body frame using yaw
yaw = self._attitude[2]
cos_yaw = math.cos(-yaw)
sin_yaw = math.sin(-yaw)
relative_x = dx * cos_yaw - dy * sin_yaw
relative_y = dx * sin_yaw + dy * cos_yaw
# Calculate confidence
angle = math.atan2(horizontal_dist, vertical_dist)
confidence = max(0.0, 1.0 - (angle / fov_rad))
confidence *= max(0.0, 1.0 - (vertical_dist / CAMERA_RANGE))
return {
"relative_x": round(relative_x, 4),
"relative_y": round(relative_y, 4),
"distance": round(vertical_dist, 4),
"confidence": round(confidence, 4)
}
def _publish_telemetry(self):
"""Publish drone telemetry to ROS 2 topic."""
with self._lock:
# Landing pad detection
pad_detection = self._get_landing_pad_detection()
telemetry = {
"imu": {
"orientation": {
"roll": round(self._attitude[0], 4),
"pitch": round(self._attitude[1], 4),
"yaw": round(self._attitude[2], 4)
},
"angular_velocity": {
"x": round(self._angular_velocity[0], 4),
"y": round(self._angular_velocity[1], 4),
"z": round(self._angular_velocity[2], 4)
},
"linear_acceleration": {
"x": 0.0,
"y": 0.0,
"z": 9.81
}
},
"altimeter": {
"altitude": round(self._position[2], 4),
"vertical_velocity": round(self._velocity[2], 4)
},
"velocity": {
"x": round(self._velocity[0], 4),
"y": round(self._velocity[1], 4),
"z": round(self._velocity[2], 4)
},
"position": {
"x": round(self._position[0], 4),
"y": round(self._position[1], 4),
"z": round(self._position[2], 4)
},
"landing_pad": pad_detection,
"camera": {
"width": 320,
"height": 240,
"fov": 60.0,
"image": None # Would need camera integration
},
"battery": {
"voltage": round(self._battery_voltage, 2),
"remaining": self._battery_remaining
},
"landed": self._landed,
"armed": self._armed,
"flight_mode": self._flight_mode,
"connected": self._connected,
"timestamp": round(time.time(), 4)
}
telemetry_msg = String()
telemetry_msg.data = json.dumps(telemetry)
self._telemetry_pub.publish(telemetry_msg)
# Publish status
status_msg = String()
status_msg.data = json.dumps({
"connected": self._connected,
"armed": self._armed,
"mode": self._flight_mode,
"gps_fix": self._gps_fix
})
self._status_pub.publish(status_msg)
# =========================================================================
# ArduPilot Control Commands
# =========================================================================
def arm(self, force: bool = False) -> bool:
"""Arm the drone."""
if not self._connected or not self._mav_conn:
return False
self.get_logger().info('Arming drone...')
self._mav_conn.arducopter_arm()
return self._wait_for_arm(True, timeout=5.0)
def disarm(self, force: bool = False) -> bool:
"""Disarm the drone."""
if not self._connected or not self._mav_conn:
return False
self.get_logger().info('Disarming drone...')
self._mav_conn.arducopter_disarm()
return self._wait_for_arm(False, timeout=5.0)
def _wait_for_arm(self, expected: bool, timeout: float = 5.0) -> bool:
"""Wait for arm/disarm to complete."""
start = time.time()
while time.time() - start < timeout:
if self._armed == expected:
return True
time.sleep(0.1)
return False
def set_mode(self, mode: str) -> bool:
"""Set flight mode (e.g., 'GUIDED', 'LAND', 'LOITER')."""
if not self._connected or not self._mav_conn:
return False
self.get_logger().info(f'Setting mode to: {mode}')
mode_mapping = self._mav_conn.mode_mapping()
if mode.upper() not in mode_mapping:
self.get_logger().error(f'Unknown mode: {mode}')
return False
mode_id = mode_mapping[mode.upper()]
self._mav_conn.set_mode(mode_id)
# Wait for mode change
start = time.time()
while time.time() - start < 5.0:
if self._flight_mode.upper() == mode.upper():
return True
time.sleep(0.1)
return False
def takeoff(self, altitude: float = 5.0) -> bool:
"""Take off to specified altitude."""
if not self._connected or not self._mav_conn:
return False
self.get_logger().info(f'Taking off to {altitude}m...')
# Set GUIDED mode
if not self.set_mode('GUIDED'):
self.get_logger().error('Failed to set GUIDED mode')
return False
# Arm if not armed
if not self._armed:
if not self.arm():
self.get_logger().error('Failed to arm')
return False
# Command takeoff
self._mav_conn.mav.command_long_send(
self._mav_conn.target_system,
self._mav_conn.target_component,
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
0, # confirmation
0, 0, 0, 0, # params 1-4 (unused)
0, 0, # lat, lon (use current)
altitude # altitude
)
return True
def land(self) -> bool:
"""Command the drone to land."""
if not self._connected or not self._mav_conn:
return False
self.get_logger().info('Landing...')
return self.set_mode('LAND')
def goto_position(self, x: float, y: float, z: float, yaw: float = 0.0):
"""Go to a local position (ENU frame)."""
if not self._connected or not self._mav_conn:
return
# Convert ENU to NED
type_mask = (
mavutil.mavlink.POSITION_TARGET_TYPEMASK_VX_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_VY_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_VZ_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AX_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AY_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AZ_IGNORE |
mavutil.mavlink.POSITION_TARGET_TYPEMASK_YAW_RATE_IGNORE
)
self._mav_conn.mav.set_position_target_local_ned_send(
0, # time_boot_ms
self._mav_conn.target_system,
self._mav_conn.target_component,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
type_mask,
y, x, -z, # ENU to NED position
0, 0, 0, # velocity (ignored)
0, 0, 0, # acceleration (ignored)
yaw, 0 # yaw, yaw_rate
)
def destroy_node(self):
"""Clean shutdown."""
self._running = False
if self._mavlink_thread.is_alive():
self._mavlink_thread.join(timeout=2.0)
if self._mav_conn:
self._mav_conn.close()
super().destroy_node()
def main(args=None):
if not MAVLINK_AVAILABLE:
print("ERROR: pymavlink is required. Install with: pip install pymavlink")
return
print("\n" + "=" * 60)
print(" MAVLink Bridge for ArduPilot SITL")
print("=" * 60 + "\n")
rclpy.init(args=args)
bridge = None
try:
bridge = MAVLinkBridge()
rclpy.spin(bridge)
except KeyboardInterrupt:
print('\nShutting down...')
finally:
if bridge:
bridge.destroy_node()
if rclpy.ok():
rclpy.shutdown()
if __name__ == '__main__':
main()

View File

@@ -2,3 +2,4 @@ pybullet>=3.2.5
numpy>=1.20.0 numpy>=1.20.0
pillow>=9.0.0 pillow>=9.0.0
pyinstaller>=6.0.0 pyinstaller>=6.0.0
pymavlink>=2.4.0

359
run_ardupilot.py Normal file
View File

@@ -0,0 +1,359 @@
#!/usr/bin/env python3
"""
Run ArduPilot SITL + Gazebo Simulation.
This script orchestrates the full ArduPilot SITL + Gazebo simulation stack:
1. Launches ArduPilot SITL (sim_vehicle.py with Gazebo model)
2. Starts MAVProxy for GCS connectivity
3. Runs MAVLink bridge + controllers
Usage:
python run_ardupilot.py # Start everything
python run_ardupilot.py --pattern circular # With rover movement
python run_ardupilot.py --no-sitl # Just controllers (SITL already running)
"""
import argparse
import os
import signal
import subprocess
import sys
import time
from pathlib import Path
import rclpy
from rclpy.executors import MultiThreadedExecutor
# Check for pymavlink
try:
from pymavlink import mavutil
MAVLINK_AVAILABLE = True
except ImportError:
MAVLINK_AVAILABLE = False
from mavlink_bridge import MAVLinkBridge
from drone_controller import DroneController
from rover_controller import RoverController, MovementPattern
def find_sim_vehicle() -> str:
"""Find sim_vehicle.py in common ArduPilot locations."""
locations = [
os.path.expanduser("~/ardupilot/Tools/autotest/sim_vehicle.py"),
"/opt/ardupilot/Tools/autotest/sim_vehicle.py",
os.environ.get("ARDUPILOT_HOME", "") + "/Tools/autotest/sim_vehicle.py",
]
for loc in locations:
if os.path.exists(loc):
return loc
# Try finding it in PATH
import shutil
if shutil.which("sim_vehicle.py"):
return "sim_vehicle.py"
return None
def parse_args():
parser = argparse.ArgumentParser(
description='Run ArduPilot SITL + Gazebo + MAVLink Bridge',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python run_ardupilot.py # Full stack with Gazebo
python run_ardupilot.py --pattern circular # Moving rover
python run_ardupilot.py --no-sitl # SITL already running
python run_ardupilot.py --mavproxy-port 14551 # Custom port
"""
)
# SITL options
parser.add_argument(
'--no-sitl', action='store_true',
help='Skip launching SITL (assume already running)'
)
parser.add_argument(
'--sitl-path', type=str,
help='Path to sim_vehicle.py'
)
parser.add_argument(
'--vehicle', type=str, default='ArduCopter',
choices=['ArduCopter', 'ArduPlane', 'APMrover2'],
help='Vehicle type (default: ArduCopter)'
)
parser.add_argument(
'--frame', type=str, default='gazebo-iris',
help='Vehicle frame (default: gazebo-iris)'
)
# MAVProxy settings
parser.add_argument(
'--mavproxy-port', type=int, default=14550,
help='MAVProxy output port (default: 14550)'
)
parser.add_argument(
'--sitl-port', type=int, default=5760,
help='SITL connection port (default: 5760)'
)
# Rover options
parser.add_argument(
'--pattern', type=str, default='stationary',
choices=['stationary', 'linear', 'circular', 'random', 'square'],
help='Rover movement pattern (default: stationary)'
)
parser.add_argument(
'--speed', '-s', type=float, default=0.5,
help='Rover speed in m/s (default: 0.5)'
)
parser.add_argument(
'--amplitude', '-a', type=float, default=2.0,
help='Rover movement amplitude in meters (default: 2.0)'
)
parser.add_argument(
'--no-rover', action='store_true',
help='Disable rover controller'
)
# Other
parser.add_argument(
'--console', action='store_true',
help='Show MAVProxy console'
)
parser.add_argument(
'--map', action='store_true',
help='Show MAVProxy map'
)
args, _ = parser.parse_known_args()
return args
class SITLProcess:
"""Manages the ArduPilot SITL process."""
def __init__(
self,
sim_vehicle_path: str,
vehicle: str = 'ArduCopter',
frame: str = 'gazebo-iris',
sitl_port: int = 5760,
mavproxy_port: int = 14550,
console: bool = False,
show_map: bool = False
):
self.sim_vehicle_path = sim_vehicle_path
self.vehicle = vehicle
self.frame = frame
self.sitl_port = sitl_port
self.mavproxy_port = mavproxy_port
self.console = console
self.show_map = show_map
self.process = None
def start(self) -> bool:
"""Start SITL process."""
if not self.sim_vehicle_path:
print("ERROR: sim_vehicle.py not found!")
print("Please set --sitl-path or ARDUPILOT_HOME environment variable")
return False
cmd = [
sys.executable, self.sim_vehicle_path,
'-v', self.vehicle,
'-f', self.frame,
'--model', 'JSON',
'--sitl-instance-args', f'--sim-address=127.0.0.1',
'--out', f'udp:127.0.0.1:{self.mavproxy_port}',
]
if self.console:
cmd.append('--console')
if self.show_map:
cmd.append('--map')
print(f"Starting SITL: {' '.join(cmd)}")
try:
self.process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
return True
except Exception as e:
print(f"Failed to start SITL: {e}")
return False
def stop(self):
"""Stop SITL process."""
if self.process:
self.process.terminate()
self.process.wait(timeout=5.0)
self.process = None
def wait_for_sitl(host: str = '127.0.0.1', port: int = 14550, timeout: float = 60.0) -> bool:
"""Wait for SITL to be ready."""
print(f"Waiting for SITL on {host}:{port}...")
start = time.time()
while time.time() - start < timeout:
try:
conn = mavutil.mavlink_connection(f'udpin:{host}:{port}', timeout=1)
if conn.wait_heartbeat(timeout=1):
print("SITL is ready!")
conn.close()
return True
except Exception:
pass
time.sleep(1)
print("Timeout waiting for SITL")
return False
def main():
args = parse_args()
if not MAVLINK_AVAILABLE:
print("ERROR: pymavlink is required!")
print("Install with: pip install pymavlink")
return 1
print("=" * 60)
print(" ArduPilot SITL + Gazebo Simulation")
print("=" * 60)
print(f" Vehicle: {args.vehicle}")
print(f" Frame: {args.frame}")
print(f" Rover Pattern: {args.pattern}")
print(f" MAVProxy Port: {args.mavproxy_port}")
print("=" * 60)
print()
sitl = None
# Start SITL if not skipped
if not args.no_sitl:
sim_vehicle = args.sitl_path or find_sim_vehicle()
if not sim_vehicle:
print("=" * 60)
print("WARNING: sim_vehicle.py not found!")
print()
print("Please either:")
print("1. Install ArduPilot: https://ardupilot.org/dev/docs/building-setup-linux-ubuntu-apt.html")
print("2. Set ARDUPILOT_HOME environment variable")
print("3. Use --sitl-path to specify location")
print("4. Use --no-sitl if SITL is already running")
print("=" * 60)
print()
print("Continuing without starting SITL...")
else:
sitl = SITLProcess(
sim_vehicle,
vehicle=args.vehicle,
frame=args.frame,
sitl_port=args.sitl_port,
mavproxy_port=args.mavproxy_port,
console=args.console,
show_map=args.map
)
if not sitl.start():
return 1
print("\nWaiting for SITL to initialize...")
time.sleep(5) # Give SITL time to start
# Wait for SITL connection
if not wait_for_sitl(port=args.mavproxy_port, timeout=30):
print("Could not connect to SITL!")
if sitl:
sitl.stop()
return 1
# Initialize ROS
rclpy.init()
nodes = []
executor = MultiThreadedExecutor(num_threads=4)
try:
# Create MAVLink bridge
bridge = MAVLinkBridge(
sitl_port=args.mavproxy_port
)
nodes.append(bridge)
executor.add_node(bridge)
print("[OK] MAVLink Bridge started")
# Create drone controller
drone = DroneController()
nodes.append(drone)
executor.add_node(drone)
print("[OK] Drone Controller started")
# Create rover controller
if not args.no_rover:
pattern = MovementPattern[args.pattern.upper()]
rover = RoverController(
pattern=pattern,
speed=args.speed,
amplitude=args.amplitude
)
nodes.append(rover)
executor.add_node(rover)
print("[OK] Rover Controller started")
print()
print("=" * 60)
print("System ready!")
print()
print("To arm and takeoff, use MAVProxy commands:")
print(" mode guided")
print(" arm throttle")
print(" takeoff 5")
print()
print("Or use the bridge.arm() and bridge.takeoff() methods")
print("=" * 60)
print()
print("Press Ctrl+C to stop.")
print()
# Handle Ctrl+C
def signal_handler(sig, frame):
print("\nShutting down...")
executor.shutdown()
signal.signal(signal.SIGINT, signal_handler)
executor.spin()
except Exception as e:
print(f"Error: {e}")
raise
finally:
print("Cleaning up...")
# Stop nodes
for node in nodes:
node.destroy_node()
# Stop ROS
if rclpy.ok():
rclpy.shutdown()
# Stop SITL
if sitl:
sitl.stop()
print("Shutdown complete.")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -4,6 +4,7 @@
# ============================================================================= # =============================================================================
# Installs PyBullet and Python dependencies # Installs PyBullet and Python dependencies
# ROS 2 requires AUR (optional - not needed for standalone mode) # ROS 2 requires AUR (optional - not needed for standalone mode)
# ArduPilot SITL can be built from source (optional)
# #
# Usage: ./install_arch.sh # Usage: ./install_arch.sh
# ============================================================================= # =============================================================================
@@ -153,6 +154,7 @@ echo "Checking Python packages:"
python -c "import pybullet; print(' PyBullet: OK')" || echo " PyBullet: FAILED" python -c "import pybullet; print(' PyBullet: OK')" || echo " PyBullet: FAILED"
python -c "import numpy; print(' NumPy: OK')" || echo " NumPy: FAILED" python -c "import numpy; print(' NumPy: OK')" || echo " NumPy: FAILED"
python -c "from PIL import Image; print(' Pillow: OK')" || echo " Pillow: FAILED" python -c "from PIL import Image; print(' Pillow: OK')" || echo " Pillow: FAILED"
python -c "from pymavlink import mavutil; print(' pymavlink: OK')" || echo " pymavlink: FAILED"
echo "" echo ""
echo "==============================================" echo "=============================================="
@@ -185,4 +187,28 @@ echo "After installing, use:"
echo " ign gazebo gazebo/worlds/drone_landing.sdf # Terminal 1" echo " ign gazebo gazebo/worlds/drone_landing.sdf # Terminal 1"
echo " python run_gazebo.py --pattern circular # Terminal 2" echo " python run_gazebo.py --pattern circular # Terminal 2"
echo "" echo ""
echo "=============================================="
echo " Optional: Install ArduPilot SITL"
echo "=============================================="
echo ""
echo "For realistic flight controller simulation:"
echo ""
echo " # Clone and build ArduPilot"
echo " git clone --recurse-submodules https://github.com/ArduPilot/ardupilot.git ~/ardupilot"
echo " cd ~/ardupilot"
echo " pip install -r Tools/environment_install/requirements.txt"
echo ""
echo " # Clone and build ArduPilot Gazebo plugin"
echo " git clone https://github.com/ArduPilot/ardupilot_gazebo.git ~/ardupilot_gazebo"
echo " cd ~/ardupilot_gazebo && mkdir build && cd build"
echo " cmake .. && make -j$(nproc)"
echo ""
echo " # Add to environment"
echo " export ARDUPILOT_HOME=~/ardupilot"
echo " export PATH=\$PATH:\$ARDUPILOT_HOME/Tools/autotest"
echo " export GZ_SIM_SYSTEM_PLUGIN_PATH=~/ardupilot_gazebo/build:\$GZ_SIM_SYSTEM_PLUGIN_PATH"
echo ""
echo "Then run:"
echo " python run_ardupilot.py --pattern circular"
echo ""

View File

@@ -3,8 +3,9 @@
# Drone Simulation - Ubuntu/Debian Installation Script # Drone Simulation - Ubuntu/Debian Installation Script
# ============================================================================= # =============================================================================
# Installs ROS 2, Gazebo, PyBullet, and all required dependencies # Installs ROS 2, Gazebo, PyBullet, and all required dependencies
# Includes optional ArduPilot SITL setup for realistic flight controller
# #
# Usage: ./install_ubuntu.sh # Usage: ./install_ubuntu.sh [--with-ardupilot]
# ============================================================================= # =============================================================================
set -e set -e
@@ -47,10 +48,20 @@ sudo apt-get install -y \
python3 \ python3 \
python3-pip \ python3-pip \
python3-venv \ python3-venv \
git git \
cmake \
build-essential
echo "[INFO] System dependencies installed" echo "[INFO] System dependencies installed"
# Check for ArduPilot option
INSTALL_ARDUPILOT=false
for arg in "$@"; do
if [ "$arg" = "--with-ardupilot" ]; then
INSTALL_ARDUPILOT=true
fi
done
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Step 2: ROS 2 Repository Setup # Step 2: ROS 2 Repository Setup
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -190,10 +201,25 @@ fi
# Set Gazebo model path # Set Gazebo model path
export GZ_SIM_RESOURCE_PATH="$SCRIPT_DIR/gazebo/models:$GZ_SIM_RESOURCE_PATH" export GZ_SIM_RESOURCE_PATH="$SCRIPT_DIR/gazebo/models:$GZ_SIM_RESOURCE_PATH"
# Set ArduPilot paths if installed
if [ -d "$HOME/ardupilot" ]; then
export ARDUPILOT_HOME="$HOME/ardupilot"
export PATH="$PATH:$ARDUPILOT_HOME/Tools/autotest"
echo "[OK] ArduPilot SITL available"
fi
# Set ArduPilot Gazebo plugin path if installed
if [ -d "$HOME/ardupilot_gazebo/build" ]; then
export GZ_SIM_SYSTEM_PLUGIN_PATH="$HOME/ardupilot_gazebo/build:$GZ_SIM_SYSTEM_PLUGIN_PATH"
export GZ_SIM_RESOURCE_PATH="$HOME/ardupilot_gazebo/models:$HOME/ardupilot_gazebo/worlds:$GZ_SIM_RESOURCE_PATH"
echo "[OK] ArduPilot Gazebo plugin available"
fi
echo "" echo ""
echo "Environment ready! Run one of:" echo "Environment ready! Run one of:"
echo " python standalone_simulation.py (No ROS 2 required)" echo " python standalone_simulation.py (No ROS 2 required)"
echo " python simulation_host.py (With ROS 2)" echo " python simulation_host.py (With ROS 2)"
echo " python run_ardupilot.py (With ArduPilot SITL)"
echo "" echo ""
EOF EOF
@@ -213,6 +239,66 @@ echo "Checking Python packages:"
python3 -c "import pybullet; print(' PyBullet: OK')" || echo " PyBullet: FAILED" python3 -c "import pybullet; print(' PyBullet: OK')" || echo " PyBullet: FAILED"
python3 -c "import numpy; print(' NumPy: OK')" || echo " NumPy: FAILED" python3 -c "import numpy; print(' NumPy: OK')" || echo " NumPy: FAILED"
python3 -c "from PIL import Image; print(' Pillow: OK')" || echo " Pillow: FAILED" python3 -c "from PIL import Image; print(' Pillow: OK')" || echo " Pillow: FAILED"
python3 -c "from pymavlink import mavutil; print(' pymavlink: OK')" || echo " pymavlink: FAILED"
# -----------------------------------------------------------------------------
# Step 9: Optional ArduPilot SITL Installation
# -----------------------------------------------------------------------------
if [ "$INSTALL_ARDUPILOT" = true ]; then
echo ""
echo "[STEP 9] Installing ArduPilot SITL..."
# Install ArduPilot dependencies
sudo apt-get install -y \
python3-dev \
python3-opencv \
python3-wxgtk4.0 \
python3-matplotlib \
python3-lxml \
libxml2-dev \
libxslt1-dev || true
# Clone ArduPilot if not exists
if [ ! -d "$HOME/ardupilot" ]; then
echo "[INFO] Cloning ArduPilot..."
git clone --recurse-submodules https://github.com/ArduPilot/ardupilot.git "$HOME/ardupilot"
cd "$HOME/ardupilot"
Tools/environment_install/install-prereqs-ubuntu.sh -y
cd "$PROJECT_ROOT"
else
echo "[INFO] ArduPilot already exists at $HOME/ardupilot"
fi
# Clone ArduPilot Gazebo plugin if not exists
if [ ! -d "$HOME/ardupilot_gazebo" ]; then
echo "[INFO] Cloning ArduPilot Gazebo plugin..."
git clone https://github.com/ArduPilot/ardupilot_gazebo.git "$HOME/ardupilot_gazebo"
cd "$HOME/ardupilot_gazebo"
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
cd "$PROJECT_ROOT"
else
echo "[INFO] ArduPilot Gazebo plugin already exists at $HOME/ardupilot_gazebo"
fi
# Add to bashrc
if ! grep -q "ARDUPILOT_HOME" ~/.bashrc; then
echo '' >> ~/.bashrc
echo '# ArduPilot SITL' >> ~/.bashrc
echo 'export ARDUPILOT_HOME=$HOME/ardupilot' >> ~/.bashrc
echo 'export PATH=$PATH:$ARDUPILOT_HOME/Tools/autotest' >> ~/.bashrc
fi
if ! grep -q "ardupilot_gazebo" ~/.bashrc; then
echo '' >> ~/.bashrc
echo '# ArduPilot Gazebo Plugin' >> ~/.bashrc
echo 'export GZ_SIM_SYSTEM_PLUGIN_PATH=$HOME/ardupilot_gazebo/build:$GZ_SIM_SYSTEM_PLUGIN_PATH' >> ~/.bashrc
echo 'export GZ_SIM_RESOURCE_PATH=$HOME/ardupilot_gazebo/models:$HOME/ardupilot_gazebo/worlds:$GZ_SIM_RESOURCE_PATH' >> ~/.bashrc
fi
echo "[INFO] ArduPilot SITL installed"
fi
echo "" echo ""
echo "==============================================" echo "=============================================="
@@ -223,8 +309,17 @@ echo "Quick start:"
echo " source activate.sh" echo " source activate.sh"
echo " python standalone_simulation.py" echo " python standalone_simulation.py"
echo "" echo ""
echo "Or with ROS 2:" echo "With ROS 2 + Gazebo:"
echo " python simulation_host.py # Terminal 1" echo " python simulation_host.py # Terminal 1"
echo " python ros_bridge.py # Terminal 2" echo " python run_bridge.py # Terminal 2"
echo " python controllers.py # Terminal 3"
echo "" echo ""
echo "With ArduPilot SITL (realistic flight controller):"
echo " ros2 launch gazebo/launch/ardupilot_drone.launch.py # Terminal 1"
echo " sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON # Terminal 2"
echo " python run_ardupilot.py --no-sitl # Terminal 3"
echo ""
if [ "$INSTALL_ARDUPILOT" != true ]; then
echo "To install ArduPilot SITL, run:"
echo " ./setup/install_ubuntu.sh --with-ardupilot"
echo ""
fi