Update to Bridges
This commit is contained in:
101
README.md
101
README.md
@@ -4,51 +4,83 @@ A GPS-denied drone landing simulation using relative sensors (IMU, altimeter, ca
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install (choose your platform)
|
||||
./setup/install_ubuntu.sh # Ubuntu/Debian
|
||||
./setup/install_arch.sh # Arch Linux
|
||||
./setup/install_macos.sh # macOS
|
||||
.\setup\install_windows.ps1 # Windows (PowerShell)
|
||||
### Standalone Mode (Any Platform)
|
||||
|
||||
# Activate and run
|
||||
source activate.sh # Linux/macOS
|
||||
. .\activate.ps1 # Windows
|
||||
```bash
|
||||
source activate.sh # Linux/macOS
|
||||
. .\activate.ps1 # Windows
|
||||
|
||||
python standalone_simulation.py --pattern circular --speed 0.3
|
||||
```
|
||||
|
||||
### Two-Terminal Mode - PyBullet (with ROS 2)
|
||||
|
||||
**Terminal 1 - Simulator:**
|
||||
```bash
|
||||
python simulation_host.py
|
||||
```
|
||||
|
||||
**Terminal 2 - Bridge + Controllers:**
|
||||
```bash
|
||||
python run_bridge.py --pattern circular --speed 0.3
|
||||
```
|
||||
|
||||
### Two-Terminal Mode - Gazebo (Linux only)
|
||||
|
||||
**Terminal 1 - Gazebo:**
|
||||
```bash
|
||||
gz sim gazebo/worlds/drone_landing.sdf
|
||||
```
|
||||
|
||||
**Terminal 2 - Bridge + Controllers:**
|
||||
```bash
|
||||
python run_gazebo.py --pattern circular --speed 0.3
|
||||
```
|
||||
|
||||
That's it! Only 2 terminals needed.
|
||||
|
||||
## Installation
|
||||
|
||||
| Platform | Command |
|
||||
|----------|---------|
|
||||
| Ubuntu/Debian | `./setup/install_ubuntu.sh` |
|
||||
| Arch Linux | `./setup/install_arch.sh` |
|
||||
| macOS | `./setup/install_macos.sh` |
|
||||
| Windows | `.\setup\install_windows.ps1` |
|
||||
|
||||
## Platform Compatibility
|
||||
|
||||
| Feature | Ubuntu | Arch | macOS | Windows |
|
||||
|---------|--------|------|-------|---------|
|
||||
| Standalone | ✅ | ✅ | ✅ | ✅ |
|
||||
| ROS 2 | ✅ | ⚠️ | ❌ | ❌ |
|
||||
| Gazebo | ✅ | ⚠️ | ❌ | ❌ |
|
||||
|
||||
**All platforms support standalone mode** - no ROS 2 required!
|
||||
| Feature | Ubuntu | Arch | macOS | Windows | WSL2 |
|
||||
|---------|--------|------|-------|---------|------|
|
||||
| Standalone | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| ROS 2 Mode | ✅ | ⚠️ | ❌ | ❌ | ✅ |
|
||||
| Gazebo | ✅ | ⚠️ | ❌ | ❌ | ✅ |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `standalone_simulation.py` | **All-in-one simulation (no ROS 2)** |
|
||||
| `simulation_host.py` | PyBullet simulator (ROS 2 mode) |
|
||||
| `ros_bridge.py` | UDP ↔ ROS 2 bridge |
|
||||
| `gazebo_bridge.py` | Gazebo ↔ ROS 2 bridge |
|
||||
| `controllers.py` | Runs drone + rover controllers |
|
||||
| `standalone_simulation.py` | All-in-one (no ROS 2 required) |
|
||||
| `simulation_host.py` | PyBullet simulator server |
|
||||
| `run_bridge.py` | **PyBullet bridge + Controllers** |
|
||||
| `run_gazebo.py` | **Gazebo bridge + Controllers** |
|
||||
| `drone_controller.py` | Drone landing logic (edit this) |
|
||||
| `rover_controller.py` | Moving landing pad |
|
||||
|
||||
## Controller Options
|
||||
## Options
|
||||
|
||||
```bash
|
||||
python standalone_simulation.py --help
|
||||
# Standalone
|
||||
python standalone_simulation.py --pattern circular --speed 0.3
|
||||
|
||||
# ROS 2 mode
|
||||
python run_bridge.py --pattern circular --speed 0.3 --host <SIMULATOR_IP>
|
||||
|
||||
Options:
|
||||
--pattern, -p stationary, linear, circular, square
|
||||
--pattern, -p stationary, linear, circular, square, random
|
||||
--speed, -s Speed in m/s (default: 0.5)
|
||||
--amplitude, -a Amplitude in meters (default: 2.0)
|
||||
--host, -H Simulator IP (default: 0.0.0.0)
|
||||
```
|
||||
|
||||
## GPS-Denied Sensors
|
||||
@@ -65,16 +97,21 @@ Options:
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [Installation](docs/installation.md) | All platform setup guides |
|
||||
| [Installation](docs/installation.md) | Platform setup guides + WSL2 |
|
||||
| [Architecture](docs/architecture.md) | System components |
|
||||
| [Protocol](docs/protocol.md) | Sensor data formats |
|
||||
| [Drone Guide](docs/drone_guide.md) | Landing algorithm guide |
|
||||
| [PyBullet](docs/pybullet.md) | PyBullet setup |
|
||||
| [Gazebo](docs/gazebo.md) | Gazebo setup (Linux) |
|
||||
|
||||
## Getting Started
|
||||
## Network Setup (Remote Simulator)
|
||||
|
||||
1. Run `python standalone_simulation.py`
|
||||
2. Watch the drone land automatically
|
||||
3. Edit `drone_controller.py` to implement your own algorithm
|
||||
4. Test: `python standalone_simulation.py --pattern circular`
|
||||
Run simulator on one machine, controllers on another:
|
||||
|
||||
**Machine 1 (with display):**
|
||||
```bash
|
||||
python simulation_host.py # Listens on 0.0.0.0:5555
|
||||
```
|
||||
|
||||
**Machine 2 (headless):**
|
||||
```bash
|
||||
python run_bridge.py --host 192.168.1.100 # Connect to Machine 1
|
||||
```
|
||||
109
build_exe.py
109
build_exe.py
@@ -1,9 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PyInstaller build script for simulation_host.py.
|
||||
Creates a standalone executable that includes PyBullet and pybullet_data.
|
||||
PyInstaller build script for drone simulation executables.
|
||||
Creates standalone executables that include PyBullet and dependencies.
|
||||
|
||||
Usage:
|
||||
python build_exe.py # Build standalone_simulation
|
||||
python build_exe.py simulation_host # Build simulation_host
|
||||
python build_exe.py standalone # Build standalone_simulation
|
||||
python build_exe.py all # Build all
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
@@ -22,23 +29,20 @@ def get_pybullet_data_path() -> str:
|
||||
return pybullet_data.getDataPath()
|
||||
|
||||
|
||||
def build_executable():
|
||||
print("=" * 60)
|
||||
print(" DRONE SIMULATION - BUILD EXECUTABLE")
|
||||
print("=" * 60)
|
||||
|
||||
def build_executable(source_name: str, output_name: str, console: bool = True):
|
||||
"""Build a single executable."""
|
||||
script_dir = Path(__file__).parent
|
||||
source_file = script_dir / "simulation_host.py"
|
||||
source_file = script_dir / source_name
|
||||
|
||||
if not source_file.exists():
|
||||
print(f"Error: {source_file} not found!")
|
||||
sys.exit(1)
|
||||
return False
|
||||
|
||||
print(f"\nBuilding: {source_name} -> {output_name}")
|
||||
print("-" * 40)
|
||||
|
||||
system = platform.system().lower()
|
||||
print(f"Platform: {system}")
|
||||
|
||||
pybullet_path = get_pybullet_data_path()
|
||||
print(f"PyBullet data path: {pybullet_path}")
|
||||
|
||||
if system == 'windows':
|
||||
separator = ';'
|
||||
@@ -51,43 +55,80 @@ def build_executable():
|
||||
str(source_file),
|
||||
'--onefile',
|
||||
'--clean',
|
||||
'--name=simulation_host',
|
||||
f'--name={output_name}',
|
||||
f'--add-data={data_spec}',
|
||||
]
|
||||
|
||||
if system == 'windows':
|
||||
build_args.append('--windowed')
|
||||
elif system == 'darwin':
|
||||
build_args.append('--windowed')
|
||||
else:
|
||||
if console:
|
||||
build_args.append('--console')
|
||||
|
||||
print("\nBuild configuration:")
|
||||
for arg in build_args:
|
||||
print(f" {arg}")
|
||||
|
||||
print("\nStarting PyInstaller build...")
|
||||
print("-" * 60)
|
||||
else:
|
||||
if system in ['windows', 'darwin']:
|
||||
build_args.append('--windowed')
|
||||
else:
|
||||
build_args.append('--console')
|
||||
|
||||
try:
|
||||
PyInstaller.__main__.run(build_args)
|
||||
print("-" * 60)
|
||||
print("\nBuild completed successfully!")
|
||||
|
||||
dist_dir = script_dir / "dist"
|
||||
if system == 'windows':
|
||||
exe_path = dist_dir / "simulation_host.exe"
|
||||
elif system == 'darwin':
|
||||
exe_path = dist_dir / "simulation_host.app"
|
||||
exe_path = dist_dir / f"{output_name}.exe"
|
||||
elif system == 'darwin' and not console:
|
||||
exe_path = dist_dir / f"{output_name}.app"
|
||||
else:
|
||||
exe_path = dist_dir / "simulation_host"
|
||||
exe_path = dist_dir / output_name
|
||||
|
||||
print(f"\nExecutable location: {exe_path}")
|
||||
print(f" Created: {exe_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nBuild failed: {e}")
|
||||
print(f" Build failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Build simulation executables')
|
||||
parser.add_argument(
|
||||
'target',
|
||||
nargs='?',
|
||||
default='standalone',
|
||||
choices=['standalone', 'simulation_host', 'all'],
|
||||
help='What to build (default: standalone)'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print(" DRONE SIMULATION - BUILD EXECUTABLES")
|
||||
print("=" * 60)
|
||||
print(f"Platform: {platform.system()}")
|
||||
print(f"PyBullet data: {get_pybullet_data_path()}")
|
||||
|
||||
success = True
|
||||
|
||||
if args.target in ['standalone', 'all']:
|
||||
success &= build_executable(
|
||||
'standalone_simulation.py',
|
||||
'drone_simulation',
|
||||
console=False
|
||||
)
|
||||
|
||||
if args.target in ['simulation_host', 'all']:
|
||||
success &= build_executable(
|
||||
'simulation_host.py',
|
||||
'simulation_host',
|
||||
console=True
|
||||
)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
if success:
|
||||
print(" BUILD COMPLETE!")
|
||||
print(" Executables in: dist/")
|
||||
else:
|
||||
print(" BUILD FAILED!")
|
||||
sys.exit(1)
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
build_executable()
|
||||
main()
|
||||
|
||||
@@ -4,113 +4,107 @@ GPS-denied drone landing simulation with multiple operation modes.
|
||||
|
||||
## Operation Modes
|
||||
|
||||
### Standalone Mode (Windows/Simple)
|
||||
### 1. Standalone Mode (Any Platform)
|
||||
|
||||
All-in-one simulation - no ROS 2 or external dependencies:
|
||||
Single-process simulation - no ROS 2 or networking required:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ standalone_simulation.py │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ PyBullet Physics Engine │ │
|
||||
│ │ ┌────────┐ ┌────────────────┐ │ │
|
||||
│ │ │ Drone │ │ Landing Pad │ │ │
|
||||
│ │ └────────┘ └────────────────┘ │ │
|
||||
│ ├──────────────────────────────────┤ │
|
||||
│ │ Built-in Controller │ │
|
||||
│ │ • Landing algorithm │ │
|
||||
│ │ • Rover movement patterns │ │
|
||||
│ │ PyBullet Physics + Camera │ │
|
||||
│ │ Built-in Landing Controller │ │
|
||||
│ │ Rover Movement Patterns │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Full Mode (Linux + ROS 2)
|
||||
|
||||
Modular architecture for development and testing:
|
||||
### 2. PyBullet + ROS 2 Mode (2 Terminals)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Simulation System │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ simulation_host │◄── UDP:5555 ──────►│ ros_bridge.py │ │
|
||||
│ │ (PyBullet) │ │ (UDP ↔ ROS Bridge) │ │
|
||||
│ └──────────────────┘ └────────────┬─────────────┘ │
|
||||
│ OR │ │
|
||||
│ ┌──────────────────┐ ┌────────────┴─────────────┐ │
|
||||
│ │ Gazebo │◄── ROS Topics ────►│ gazebo_bridge.py │ │
|
||||
│ │ (Linux only) │ │ (Gazebo ↔ ROS Bridge) │ │
|
||||
│ └──────────────────┘ └────────────┬─────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────▼─────────────┐ │
|
||||
│ │ controllers.py │ │
|
||||
│ │ ┌─────────────────────┐ │ │
|
||||
│ │ │ DroneController │ │ │
|
||||
│ │ │ RoverController │ │ │
|
||||
│ │ └─────────────────────┘ │ │
|
||||
│ └──────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
Terminal 1 Terminal 2
|
||||
┌──────────────────┐ ┌──────────────────────────┐
|
||||
│ simulation_host │◄─UDP───►│ run_bridge.py │
|
||||
│ (PyBullet) │ │ ┌────────────────────┐ │
|
||||
│ Port 5555 │ │ │ ROS2SimulatorBridge│ │
|
||||
│ │ │ │ DroneController │ │
|
||||
│ │ │ │ RoverController │ │
|
||||
└──────────────────┘ │ └────────────────────┘ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
### 3. Gazebo + ROS 2 Mode (2 Terminals, Linux Only)
|
||||
|
||||
```
|
||||
Terminal 1 Terminal 2
|
||||
┌──────────────────┐ ┌──────────────────────────┐
|
||||
│ Gazebo │◄─ROS───►│ run_gazebo.py │
|
||||
│ (gz sim ...) │ │ ┌────────────────────┐ │
|
||||
│ │ │ │ GazeboBridge │ │
|
||||
│ │ │ │ DroneController │ │
|
||||
│ │ │ │ RoverController │ │
|
||||
└──────────────────┘ │ └────────────────────┘ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Standalone
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `standalone_simulation.py` | All-in-one simulation |
|
||||
| `simulation_host.py` | PyBullet physics server (UDP) |
|
||||
| `run_bridge.py` | PyBullet bridge + controllers |
|
||||
| `run_gazebo.py` | Gazebo bridge + controllers |
|
||||
| `drone_controller.py` | Landing algorithm |
|
||||
| `rover_controller.py` | Moving landing pad |
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| **standalone_simulation.py** | Complete simulation with built-in controller |
|
||||
## ROS Topics
|
||||
|
||||
### Full Mode
|
||||
| Topic | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `/cmd_vel` | `Twist` | Drone velocity commands |
|
||||
| `/drone/telemetry` | `String` | GPS-denied sensor data |
|
||||
| `/rover/telemetry` | `String` | Rover position (internal) |
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| **PyBullet** (`simulation_host.py`) | Physics engine, UDP networking, camera |
|
||||
| **Gazebo** | Full robotics simulator (Linux only) |
|
||||
| **ros_bridge.py** | Connects PyBullet ↔ ROS 2 via UDP |
|
||||
| **gazebo_bridge.py** | Connects Gazebo ↔ ROS 2 |
|
||||
| **controllers.py** | Runs drone + rover controllers |
|
||||
| **drone_controller.py** | GPS-denied landing logic |
|
||||
| **rover_controller.py** | Moving landing pad patterns |
|
||||
## Network Configuration
|
||||
|
||||
## ROS Topics (Full Mode)
|
||||
All components default to `0.0.0.0` for network accessibility.
|
||||
|
||||
| Topic | Type | Publisher | Subscriber |
|
||||
|-------|------|-----------|------------|
|
||||
| `/cmd_vel` | `Twist` | DroneController | Bridge |
|
||||
| `/drone/telemetry` | `String` | Bridge | DroneController |
|
||||
| `/rover/telemetry` | `String` | RoverController | DroneController |
|
||||
### Remote Setup
|
||||
|
||||
## GPS-Denied Sensor Flow
|
||||
|
||||
```
|
||||
Simulator Bridge DroneController
|
||||
│ │ │
|
||||
│ Render Camera │ │
|
||||
│ Compute Physics │ │
|
||||
│──────────────────────►│ │
|
||||
│ │ GPS-Denied Sensors: │
|
||||
│ │ - IMU │
|
||||
│ │ - Altimeter │
|
||||
│ │ - Velocity │
|
||||
│ │ - Camera Image │
|
||||
│ │ - Landing Pad Detection │
|
||||
│ │─────────────────────────►│
|
||||
│ │ /cmd_vel │
|
||||
│◄──────────────────────│◄─────────────────────────│
|
||||
**Machine 1 (with display):**
|
||||
```bash
|
||||
python simulation_host.py # Listens on 0.0.0.0:5555
|
||||
```
|
||||
|
||||
## Platform Support
|
||||
**Machine 2 (headless controller):**
|
||||
```bash
|
||||
python run_bridge.py --host 192.168.1.100
|
||||
```
|
||||
|
||||
| Mode | Windows | Linux | macOS |
|
||||
|------|---------|-------|-------|
|
||||
| Standalone | ✅ | ✅ | ✅ |
|
||||
| Full (ROS 2) | ⚠️ | ✅ | ⚠️ |
|
||||
| Gazebo | ❌ | ✅ | ❌ |
|
||||
|
||||
## UDP Protocol (Full Mode)
|
||||
### UDP Ports
|
||||
|
||||
| Port | Direction | Content |
|
||||
|------|-----------|---------|
|
||||
| 5555 | Bridge → Simulator | Command JSON |
|
||||
| 5556 | Simulator → Bridge | Telemetry JSON |
|
||||
| 5555 | Bridge → Simulator | Commands |
|
||||
| 5556 | Simulator → Bridge | Telemetry |
|
||||
|
||||
## GPS-Denied Sensors
|
||||
|
||||
All modes provide the same sensor data:
|
||||
|
||||
| Sensor | Data |
|
||||
|--------|------|
|
||||
| IMU | Orientation, angular velocity |
|
||||
| Altimeter | Altitude, vertical velocity |
|
||||
| Velocity | Estimated from optical flow |
|
||||
| Camera | 320x240 downward JPEG |
|
||||
| Landing Pad | Relative position (when visible) |
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Mode | Ubuntu | Arch | macOS | Windows | WSL2 |
|
||||
|------|--------|------|-------|---------|------|
|
||||
| Standalone | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| PyBullet+ROS | ✅ | ⚠️ | ❌ | ❌ | ✅ |
|
||||
| Gazebo+ROS | ✅ | ⚠️ | ❌ | ❌ | ✅ |
|
||||
|
||||
159
docs/gazebo.md
159
docs/gazebo.md
@@ -1,17 +1,8 @@
|
||||
# Gazebo Simulation
|
||||
|
||||
Running the GPS-denied drone simulation with Gazebo.
|
||||
Running the GPS-denied drone simulation with Gazebo (Linux only).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install Gazebo and ROS-Gazebo bridge:
|
||||
|
||||
```bash
|
||||
./setup/install_ubuntu.sh
|
||||
source activate.sh
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
## Quick Start (2 Terminals)
|
||||
|
||||
**Terminal 1 - Start Gazebo:**
|
||||
```bash
|
||||
@@ -19,86 +10,54 @@ source activate.sh
|
||||
gz sim gazebo/worlds/drone_landing.sdf
|
||||
```
|
||||
|
||||
**Terminal 2 - Spawn drone and start bridge:**
|
||||
**Terminal 2 - Run Controllers:**
|
||||
```bash
|
||||
source activate.sh
|
||||
python run_gazebo.py --pattern circular --speed 0.3
|
||||
```
|
||||
|
||||
# Spawn drone
|
||||
## Options
|
||||
|
||||
```bash
|
||||
python run_gazebo.py --help
|
||||
|
||||
Options:
|
||||
--pattern stationary, linear, circular, square, random
|
||||
--speed, -s Rover speed in m/s (default: 0.5)
|
||||
--amplitude, -a Movement amplitude (default: 2.0)
|
||||
--no-rover Disable rover controller
|
||||
```
|
||||
|
||||
## Spawning the Drone
|
||||
|
||||
If the drone isn't in the world, spawn it:
|
||||
|
||||
```bash
|
||||
gz service -s /world/drone_landing_world/create \
|
||||
--reqtype gz.msgs.EntityFactory \
|
||||
--reptype gz.msgs.Boolean \
|
||||
--req 'sdf_filename: "gazebo/models/drone/model.sdf", name: "drone"'
|
||||
|
||||
# Start bridge
|
||||
python gazebo_bridge.py
|
||||
```
|
||||
|
||||
**Terminal 3 - Run controllers:**
|
||||
```bash
|
||||
source activate.sh
|
||||
python controllers.py --pattern circular --speed 0.3
|
||||
```
|
||||
## GPS-Denied Sensors
|
||||
|
||||
## World Description
|
||||
The `run_gazebo.py` script provides the same sensor interface as PyBullet:
|
||||
|
||||
The `drone_landing.sdf` world contains:
|
||||
|
||||
| Object | Description |
|
||||
|--------|-------------|
|
||||
| Ground Plane | Infinite flat surface |
|
||||
| Sun | Directional light with shadows |
|
||||
| Landing Pad | Green box with "H" marker at origin |
|
||||
|
||||
## Drone Model
|
||||
|
||||
Quadrotor drone with:
|
||||
|
||||
- **Body**: 0.3m × 0.3m × 0.1m, 1.0 kg
|
||||
- **Rotors**: 4 spinning rotors
|
||||
- **IMU**: Orientation and angular velocity
|
||||
- **Camera**: 320x240 downward-facing sensor
|
||||
- **Odometry**: Position and velocity
|
||||
|
||||
### Gazebo Plugins
|
||||
|
||||
| Plugin | Function |
|
||||
|--------|----------|
|
||||
| MulticopterMotorModel | Motor dynamics |
|
||||
| MulticopterVelocityControl | Velocity commands |
|
||||
| OdometryPublisher | Pose and twist |
|
||||
|
||||
## Camera System
|
||||
|
||||
The drone has a downward-facing camera:
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Resolution | 320 x 240 |
|
||||
| FOV | 60 degrees |
|
||||
| Format | Base64 encoded JPEG |
|
||||
| Update Rate | 30 Hz (Gazebo) / ~5 Hz (in telemetry) |
|
||||
| Topic | `/drone/camera` |
|
||||
| Sensor | Source |
|
||||
|--------|--------|
|
||||
| IMU | Gazebo odometry |
|
||||
| Altimeter | Gazebo Z position |
|
||||
| Velocity | Gazebo twist |
|
||||
| Camera | Gazebo camera sensor |
|
||||
| Landing Pad | Computed from relative position |
|
||||
|
||||
## Gazebo Topics
|
||||
|
||||
| Topic | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `/drone/cmd_vel` | `gz.msgs.Twist` | Velocity commands |
|
||||
| `/model/drone/odometry` | `gz.msgs.Odometry` | Drone state |
|
||||
| `/drone/camera` | `gz.msgs.Image` | Camera images |
|
||||
| `/drone/imu` | `gz.msgs.IMU` | IMU data |
|
||||
|
||||
## GPS-Denied Sensors
|
||||
|
||||
The `gazebo_bridge.py` converts Gazebo data to GPS-denied sensor format:
|
||||
|
||||
| Sensor | Source |
|
||||
|--------|--------|
|
||||
| IMU | Odometry orientation + angular velocity |
|
||||
| Altimeter | Odometry Z position |
|
||||
| Velocity | Odometry twist |
|
||||
| Camera | Camera sensor (base64 JPEG) |
|
||||
| Landing Pad | Computed from relative position |
|
||||
| `/drone/cmd_vel` | `Twist` | Velocity commands |
|
||||
| `/model/drone/odometry` | `Odometry` | Drone state |
|
||||
| `/drone/camera` | `Image` | Camera images |
|
||||
|
||||
## Headless Mode
|
||||
|
||||
@@ -108,51 +67,29 @@ Run without GUI:
|
||||
gz sim -s gazebo/worlds/drone_landing.sdf
|
||||
```
|
||||
|
||||
## Using the Launch File
|
||||
|
||||
For ROS 2 packages:
|
||||
|
||||
```bash
|
||||
ros2 launch <package_name> drone_landing.launch.py
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Cannot connect to display"
|
||||
|
||||
```bash
|
||||
export DISPLAY=:0
|
||||
# or use headless mode
|
||||
gz sim -s gazebo/worlds/drone_landing.sdf
|
||||
```
|
||||
|
||||
### Drone falls immediately
|
||||
|
||||
The velocity controller may need to be enabled:
|
||||
|
||||
```bash
|
||||
gz topic -t /drone/enable -m gz.msgs.Boolean -p 'data: true'
|
||||
```
|
||||
|
||||
### Topics not visible in ROS
|
||||
|
||||
Ensure the bridge is running:
|
||||
|
||||
```bash
|
||||
python gazebo_bridge.py
|
||||
```
|
||||
|
||||
### Model not found
|
||||
|
||||
Set the model path:
|
||||
|
||||
```bash
|
||||
export GZ_SIM_RESOURCE_PATH=$PWD/gazebo/models:$GZ_SIM_RESOURCE_PATH
|
||||
```
|
||||
|
||||
### Camera image not in telemetry
|
||||
### Drone falls immediately
|
||||
|
||||
Ensure PIL/Pillow is installed:
|
||||
Enable the velocity controller:
|
||||
```bash
|
||||
pip install pillow
|
||||
gz topic -t /drone/enable -m gz.msgs.Boolean -p 'data: true'
|
||||
```
|
||||
|
||||
### "Cannot connect to display"
|
||||
|
||||
Use headless mode or WSLg:
|
||||
```bash
|
||||
# Headless
|
||||
gz sim -s gazebo/worlds/drone_landing.sdf
|
||||
|
||||
# Or ensure DISPLAY is set
|
||||
export DISPLAY=:0
|
||||
```
|
||||
|
||||
107
docs/pybullet.md
107
docs/pybullet.md
@@ -2,25 +2,19 @@
|
||||
|
||||
Running the GPS-denied drone simulation with PyBullet.
|
||||
|
||||
## Windows (Standalone Mode)
|
||||
## Standalone Mode (Recommended)
|
||||
|
||||
No ROS 2 required! Run the all-in-one simulation:
|
||||
No ROS 2 required! Single terminal:
|
||||
|
||||
```powershell
|
||||
. .\activate.ps1
|
||||
python standalone_simulation.py
|
||||
```bash
|
||||
source activate.sh
|
||||
python standalone_simulation.py --pattern circular --speed 0.3
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```powershell
|
||||
# Stationary landing pad
|
||||
python standalone_simulation.py
|
||||
|
||||
# Moving rover patterns
|
||||
python standalone_simulation.py --pattern circular --speed 0.3
|
||||
python standalone_simulation.py --pattern linear --speed 0.5
|
||||
python standalone_simulation.py --pattern square --speed 0.4
|
||||
```bash
|
||||
python standalone_simulation.py --help
|
||||
|
||||
Options:
|
||||
--pattern, -p stationary, linear, circular, square
|
||||
@@ -28,11 +22,11 @@ Options:
|
||||
--amplitude, -a Movement amplitude in meters (default: 2.0)
|
||||
```
|
||||
|
||||
The simulation includes a built-in landing controller. Watch the drone automatically land on the rover!
|
||||
|
||||
---
|
||||
|
||||
## Linux (Full ROS 2 Mode)
|
||||
## ROS 2 Mode (2 Terminals)
|
||||
|
||||
For distributed or remote simulation:
|
||||
|
||||
**Terminal 1 - Simulator:**
|
||||
```bash
|
||||
@@ -40,36 +34,49 @@ source activate.sh
|
||||
python simulation_host.py
|
||||
```
|
||||
|
||||
**Terminal 2 - ROS Bridge:**
|
||||
**Terminal 2 - Controllers:**
|
||||
```bash
|
||||
source activate.sh
|
||||
python ros_bridge.py
|
||||
```
|
||||
|
||||
**Terminal 3 - Controllers:**
|
||||
```bash
|
||||
source activate.sh
|
||||
python controllers.py --pattern circular --speed 0.3
|
||||
python run_bridge.py --pattern circular --speed 0.3
|
||||
```
|
||||
|
||||
### Remote Setup
|
||||
|
||||
Run simulator on one machine, controllers on another.
|
||||
Run simulator on one machine, controllers on another:
|
||||
|
||||
**Machine 1 (with display):**
|
||||
```bash
|
||||
python simulation_host.py
|
||||
python simulation_host.py # Listens on 0.0.0.0:5555
|
||||
```
|
||||
|
||||
**Machine 2 (headless):**
|
||||
```bash
|
||||
source activate.sh
|
||||
python ros_bridge.py --host <MACHINE_1_IP>
|
||||
python controllers.py
|
||||
python run_bridge.py --host 192.168.1.100 --pattern circular
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building Standalone Executable
|
||||
|
||||
Create a distributable executable:
|
||||
|
||||
```bash
|
||||
source activate.sh
|
||||
|
||||
# Build standalone simulation (recommended)
|
||||
python build_exe.py
|
||||
|
||||
# Build simulation_host
|
||||
python build_exe.py simulation_host
|
||||
|
||||
# Build all
|
||||
python build_exe.py all
|
||||
```
|
||||
|
||||
Executables are created in `dist/`.
|
||||
|
||||
---
|
||||
|
||||
## Simulation Parameters
|
||||
|
||||
| Parameter | Value |
|
||||
@@ -77,7 +84,7 @@ python controllers.py
|
||||
| Physics Rate | 240 Hz |
|
||||
| Telemetry Rate | 24 Hz |
|
||||
| Drone Mass | 1.0 kg |
|
||||
| Gravity | -9.81 m/s² |
|
||||
| UDP Port | 5555 (commands), 5556 (telemetry) |
|
||||
|
||||
## GPS-Denied Sensors
|
||||
|
||||
@@ -89,40 +96,18 @@ python controllers.py
|
||||
| Camera | 320x240 downward JPEG image |
|
||||
| Landing Pad | Vision-based relative position |
|
||||
|
||||
## Camera System
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Resolution | 320 x 240 |
|
||||
| FOV | 60 degrees |
|
||||
| Format | Base64 encoded JPEG |
|
||||
| Direction | Downward-facing |
|
||||
|
||||
## World Setup
|
||||
|
||||
| Object | Position | Description |
|
||||
|--------|----------|-------------|
|
||||
| Ground | z = 0 | Infinite plane |
|
||||
| Rover | (0, 0, 0.15) | 1m × 1m landing pad |
|
||||
| Drone | (0, 0, 5) | Starting position |
|
||||
|
||||
## Building Executable
|
||||
|
||||
Create standalone executable:
|
||||
|
||||
```bash
|
||||
source activate.sh
|
||||
python build_exe.py
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Cannot connect to X server"
|
||||
|
||||
PyBullet requires a display:
|
||||
- Run on machine with monitor
|
||||
- Use X11 forwarding: `ssh -X user@host`
|
||||
- Virtual display: `xvfb-run python simulation_host.py`
|
||||
PyBullet GUI requires a display:
|
||||
```bash
|
||||
# Use virtual display
|
||||
xvfb-run python standalone_simulation.py
|
||||
|
||||
# Or use X11 forwarding
|
||||
ssh -X user@host
|
||||
```
|
||||
|
||||
### Drone flies erratically
|
||||
|
||||
@@ -134,7 +119,7 @@ Kd = 0.2
|
||||
|
||||
### Camera image not appearing
|
||||
|
||||
Ensure PIL/Pillow is installed:
|
||||
Install Pillow:
|
||||
```bash
|
||||
pip install pillow
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ from std_msgs.msg import String
|
||||
class ROS2SimulatorBridge(Node):
|
||||
"""Bridges ROS 2 topics to UDP-based simulator."""
|
||||
|
||||
DEFAULT_SIMULATOR_HOST = '127.0.0.1'
|
||||
DEFAULT_SIMULATOR_HOST = '0.0.0.0'
|
||||
DEFAULT_SIMULATOR_PORT = 5555
|
||||
SOCKET_TIMEOUT = 0.1
|
||||
RECEIVE_BUFFER_SIZE = 4096
|
||||
@@ -171,8 +171,10 @@ Examples:
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
'--host', '-H', type=str, default='127.0.0.1',
|
||||
help='IP address of the simulator (default: 127.0.0.1)'
|
||||
'--host', '-H',
|
||||
type=str,
|
||||
default='0.0.0.0',
|
||||
help='Simulator host IP address (default: 0.0.0.0)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--port', '-p', type=int, default=5555,
|
||||
|
||||
136
run_bridge.py
Normal file
136
run_bridge.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run Bridge - Runs ROS bridge and controllers together.
|
||||
Connects to PyBullet simulator running on another machine or terminal.
|
||||
|
||||
Usage: python run_bridge.py [--host HOST] [--pattern PATTERN] [--speed SPEED]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import rclpy
|
||||
from rclpy.executors import MultiThreadedExecutor
|
||||
|
||||
from ros_bridge import ROS2SimulatorBridge
|
||||
from drone_controller import DroneController
|
||||
from rover_controller import RoverController, MovementPattern
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Run ROS bridge and controllers together',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python run_bridge.py # Connect to local simulator
|
||||
python run_bridge.py --host 192.168.1.100 # Connect to remote simulator
|
||||
python run_bridge.py --pattern circular # With moving rover
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
'--host', '-H', type=str, default='0.0.0.0',
|
||||
help='Simulator host IP (default: 0.0.0.0)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--port', '-p', type=int, default=5555,
|
||||
help='Simulator port (default: 5555)'
|
||||
)
|
||||
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'
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print(" ROS Bridge + Controllers")
|
||||
print("=" * 60)
|
||||
print(f" Simulator: {args.host}:{args.port}")
|
||||
print(f" Rover Pattern: {args.pattern}")
|
||||
print(f" Rover Speed: {args.speed} m/s")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
rclpy.init()
|
||||
|
||||
nodes = []
|
||||
executor = MultiThreadedExecutor(num_threads=4)
|
||||
|
||||
try:
|
||||
# Create bridge
|
||||
bridge = ROS2SimulatorBridge(
|
||||
simulator_host=args.host,
|
||||
simulator_port=args.port
|
||||
)
|
||||
nodes.append(bridge)
|
||||
executor.add_node(bridge)
|
||||
print("[OK] ROS Bridge started")
|
||||
|
||||
# Create drone controller
|
||||
drone = DroneController()
|
||||
nodes.append(drone)
|
||||
executor.add_node(drone)
|
||||
print("[OK] Drone Controller started")
|
||||
|
||||
# Create rover controller (optional)
|
||||
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("All systems running. Press Ctrl+C to stop.")
|
||||
print()
|
||||
|
||||
# Handle Ctrl+C gracefully
|
||||
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...")
|
||||
for node in nodes:
|
||||
if hasattr(node, 'shutdown'):
|
||||
node.shutdown()
|
||||
node.destroy_node()
|
||||
|
||||
if rclpy.ok():
|
||||
rclpy.shutdown()
|
||||
|
||||
print("Shutdown complete.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
122
run_gazebo.py
Normal file
122
run_gazebo.py
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run Gazebo Bridge - Runs Gazebo bridge and controllers together.
|
||||
Connects to Gazebo simulation running in another terminal.
|
||||
|
||||
Usage: python run_gazebo.py [--pattern PATTERN] [--speed SPEED]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import rclpy
|
||||
from rclpy.executors import MultiThreadedExecutor
|
||||
|
||||
from gazebo_bridge import GazeboBridge
|
||||
from drone_controller import DroneController
|
||||
from rover_controller import RoverController, MovementPattern
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Run Gazebo bridge and controllers together',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python run_gazebo.py # Stationary rover
|
||||
python run_gazebo.py --pattern circular # Circular movement
|
||||
python run_gazebo.py --pattern square --speed 0.3
|
||||
"""
|
||||
)
|
||||
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'
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print(" Gazebo Bridge + Controllers")
|
||||
print("=" * 60)
|
||||
print(f" Rover Pattern: {args.pattern}")
|
||||
print(f" Rover Speed: {args.speed} m/s")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
rclpy.init()
|
||||
|
||||
nodes = []
|
||||
executor = MultiThreadedExecutor(num_threads=4)
|
||||
|
||||
try:
|
||||
# Create Gazebo bridge
|
||||
bridge = GazeboBridge()
|
||||
nodes.append(bridge)
|
||||
executor.add_node(bridge)
|
||||
print("[OK] Gazebo Bridge started")
|
||||
|
||||
# Create drone controller
|
||||
drone = DroneController()
|
||||
nodes.append(drone)
|
||||
executor.add_node(drone)
|
||||
print("[OK] Drone Controller started")
|
||||
|
||||
# Create rover controller (optional)
|
||||
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("All systems running. Press Ctrl+C to stop.")
|
||||
print()
|
||||
|
||||
# Handle Ctrl+C gracefully
|
||||
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...")
|
||||
for node in nodes:
|
||||
node.destroy_node()
|
||||
|
||||
if rclpy.ok():
|
||||
rclpy.shutdown()
|
||||
|
||||
print("Shutdown complete.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -20,7 +20,7 @@ import pybullet_data
|
||||
class DroneSimulator:
|
||||
"""PyBullet-based drone simulation with camera."""
|
||||
|
||||
UDP_HOST = '127.0.0.1'
|
||||
UDP_HOST = '0.0.0.0'
|
||||
UDP_PORT = 5555
|
||||
UDP_BUFFER_SIZE = 65535
|
||||
SOCKET_TIMEOUT = 0.001
|
||||
|
||||
Reference in New Issue
Block a user