Update to Bridges

This commit is contained in:
2026-01-01 01:08:30 +00:00
parent 2b01f636fe
commit 4b44c3de91
9 changed files with 578 additions and 324 deletions

View File

@@ -4,51 +4,83 @@ A GPS-denied drone landing simulation using relative sensors (IMU, altimeter, ca
## Quick Start ## Quick Start
```bash ### Standalone Mode (Any Platform)
# 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)
# Activate and run ```bash
source activate.sh # Linux/macOS source activate.sh # Linux/macOS
. .\activate.ps1 # Windows . .\activate.ps1 # Windows
python standalone_simulation.py --pattern circular --speed 0.3 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 ## Platform Compatibility
| Feature | Ubuntu | Arch | macOS | Windows | | Feature | Ubuntu | Arch | macOS | Windows | WSL2 |
|---------|--------|------|-------|---------| |---------|--------|------|-------|---------|------|
| Standalone | ✅ | ✅ | ✅ | ✅ | | Standalone | ✅ | ✅ | ✅ | ✅ | ✅ |
| ROS 2 | ✅ | ⚠️ | ❌ | ❌ | | ROS 2 Mode | ✅ | ⚠️ | ❌ | ❌ | ✅ |
| Gazebo | ✅ | ⚠️ | ❌ | ❌ | | Gazebo | ✅ | ⚠️ | ❌ | ❌ | ✅ |
**All platforms support standalone mode** - no ROS 2 required!
## Files ## Files
| File | Description | | File | Description |
|------|-------------| |------|-------------|
| `standalone_simulation.py` | **All-in-one simulation (no ROS 2)** | | `standalone_simulation.py` | All-in-one (no ROS 2 required) |
| `simulation_host.py` | PyBullet simulator (ROS 2 mode) | | `simulation_host.py` | PyBullet simulator server |
| `ros_bridge.py` | UDP ↔ ROS 2 bridge | | `run_bridge.py` | **PyBullet bridge + Controllers** |
| `gazebo_bridge.py` | Gazebo ↔ ROS 2 bridge | | `run_gazebo.py` | **Gazebo bridge + Controllers** |
| `controllers.py` | Runs drone + rover controllers |
| `drone_controller.py` | Drone landing logic (edit this) | | `drone_controller.py` | Drone landing logic (edit this) |
| `rover_controller.py` | Moving landing pad | | `rover_controller.py` | Moving landing pad |
## Controller Options ## Options
```bash ```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: Options:
--pattern, -p stationary, linear, circular, square --pattern, -p stationary, linear, circular, square, random
--speed, -s Speed in m/s (default: 0.5) --speed, -s Speed in m/s (default: 0.5)
--amplitude, -a Amplitude in meters (default: 2.0) --amplitude, -a Amplitude in meters (default: 2.0)
--host, -H Simulator IP (default: 0.0.0.0)
``` ```
## GPS-Denied Sensors ## GPS-Denied Sensors
@@ -65,16 +97,21 @@ Options:
| Document | Description | | Document | Description |
|----------|-------------| |----------|-------------|
| [Installation](docs/installation.md) | All platform setup guides | | [Installation](docs/installation.md) | Platform setup guides + WSL2 |
| [Architecture](docs/architecture.md) | System components | | [Architecture](docs/architecture.md) | System components |
| [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 |
| [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` Run simulator on one machine, controllers on another:
2. Watch the drone land automatically
3. Edit `drone_controller.py` to implement your own algorithm **Machine 1 (with display):**
4. Test: `python standalone_simulation.py --pattern circular` ```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
```

View File

@@ -1,9 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
PyInstaller build script for simulation_host.py. PyInstaller build script for drone simulation executables.
Creates a standalone executable that includes PyBullet and pybullet_data. 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 os
import platform import platform
import sys import sys
@@ -22,23 +29,20 @@ def get_pybullet_data_path() -> str:
return pybullet_data.getDataPath() return pybullet_data.getDataPath()
def build_executable(): def build_executable(source_name: str, output_name: str, console: bool = True):
print("=" * 60) """Build a single executable."""
print(" DRONE SIMULATION - BUILD EXECUTABLE")
print("=" * 60)
script_dir = Path(__file__).parent script_dir = Path(__file__).parent
source_file = script_dir / "simulation_host.py" source_file = script_dir / source_name
if not source_file.exists(): if not source_file.exists():
print(f"Error: {source_file} not found!") 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() system = platform.system().lower()
print(f"Platform: {system}")
pybullet_path = get_pybullet_data_path() pybullet_path = get_pybullet_data_path()
print(f"PyBullet data path: {pybullet_path}")
if system == 'windows': if system == 'windows':
separator = ';' separator = ';'
@@ -51,43 +55,80 @@ def build_executable():
str(source_file), str(source_file),
'--onefile', '--onefile',
'--clean', '--clean',
'--name=simulation_host', f'--name={output_name}',
f'--add-data={data_spec}', f'--add-data={data_spec}',
] ]
if system == 'windows': if console:
build_args.append('--windowed') build_args.append('--console')
elif system == 'darwin': else:
if system in ['windows', 'darwin']:
build_args.append('--windowed') build_args.append('--windowed')
else: else:
build_args.append('--console') build_args.append('--console')
print("\nBuild configuration:")
for arg in build_args:
print(f" {arg}")
print("\nStarting PyInstaller build...")
print("-" * 60)
try: try:
PyInstaller.__main__.run(build_args) PyInstaller.__main__.run(build_args)
print("-" * 60)
print("\nBuild completed successfully!")
dist_dir = script_dir / "dist" dist_dir = script_dir / "dist"
if system == 'windows': if system == 'windows':
exe_path = dist_dir / "simulation_host.exe" exe_path = dist_dir / f"{output_name}.exe"
elif system == 'darwin': elif system == 'darwin' and not console:
exe_path = dist_dir / "simulation_host.app" exe_path = dist_dir / f"{output_name}.app"
else: 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: 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) sys.exit(1)
print("=" * 60)
if __name__ == '__main__': if __name__ == '__main__':
build_executable() main()

View File

@@ -4,113 +4,107 @@ GPS-denied drone landing simulation with multiple operation modes.
## 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 │ │ standalone_simulation.py │
│ ┌──────────────────────────────────┐ │ │ ┌──────────────────────────────────┐ │
│ │ PyBullet Physics Engine │ │ │ │ PyBullet Physics + Camera │ │
│ │ ┌────────┐ ┌────────────────┐ │ │ │ │ Built-in Landing Controller │ │
│ │ │ Drone │ │ Landing Pad │ │ │ │ Rover Movement Patterns │ │
│ │ └────────┘ └────────────────┘ │ │
│ ├──────────────────────────────────┤ │
│ │ Built-in Controller │ │
│ │ • Landing algorithm │ │
│ │ • Rover movement patterns │ │
│ └──────────────────────────────────┘ │ │ └──────────────────────────────────┘ │
└────────────────────────────────────────┘ └────────────────────────────────────────┘
``` ```
### Full Mode (Linux + ROS 2) ### 2. PyBullet + ROS 2 Mode (2 Terminals)
Modular architecture for development and testing:
``` ```
┌─────────────────────────────────────────────────────────────────────────┐ Terminal 1 Terminal 2
│ Simulation System │ ┌──────────────────┐ ┌──────────────────────────┐
├─────────────────────────────────────────────────────────────────────────┤ │ simulation_host │◄─UDP───►│ run_bridge.py │
(PyBullet) ┌────────────────────┐
┌──────────────────┐ ┌──────────────────────────┐ Port 5555 │ ROS2SimulatorBridge│
│ simulation_host │◄── UDP:5555 ──────►│ ros_bridge.py │ │ │ │ DroneController │
(PyBullet) (UDP ↔ ROS Bridge) │ RoverController │ │
└──────────────────┘ └────────────┬─────────────┘ └──────────────────┘ └────────────────────
│ OR │ │ └──────────────────────────┘
│ ┌──────────────────┐ ┌────────────┴─────────────┐ │ ```
│ │ Gazebo │◄── ROS Topics ────►│ gazebo_bridge.py │ │
│ │ (Linux only) │ │ (Gazebo ROS Bridge) │ │ ### 3. Gazebo + ROS 2 Mode (2 Terminals, Linux Only)
│ └──────────────────┘ └────────────┬─────────────┘ │
│ │ │ ```
┌────────────▼─────────────┐ │ Terminal 1 Terminal 2
│ │ controllers.py │ │ ┌──────────────────┐ ┌──────────────────────────┐
│ ┌─────────────────────┐ │ Gazebo │◄─ROS───►│ run_gazebo.py
DroneController │ │ (gz sim ...) │ ┌────────────────────┐
│ │ RoverController │ │ │ GazeboBridge │ │
│ └─────────────────────┘ │ DroneController │ │
└──────────────────────────┘ │ RoverController │
└─────────────────────────────────────────────────────────────────────────┘ └──────────────────┘ │ └────────────────────┘
└──────────────────────────┘
``` ```
## Components ## 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 | ## ROS Topics
|-----------|-------------|
| **standalone_simulation.py** | Complete simulation with built-in controller |
### 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 | ## Network Configuration
|-----------|-------------|
| **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 |
## ROS Topics (Full Mode) All components default to `0.0.0.0` for network accessibility.
| Topic | Type | Publisher | Subscriber | ### Remote Setup
|-------|------|-----------|------------|
| `/cmd_vel` | `Twist` | DroneController | Bridge |
| `/drone/telemetry` | `String` | Bridge | DroneController |
| `/rover/telemetry` | `String` | RoverController | DroneController |
## GPS-Denied Sensor Flow **Machine 1 (with display):**
```bash
``` python simulation_host.py # Listens on 0.0.0.0:5555
Simulator Bridge DroneController
│ │ │
│ Render Camera │ │
│ Compute Physics │ │
│──────────────────────►│ │
│ │ GPS-Denied Sensors: │
│ │ - IMU │
│ │ - Altimeter │
│ │ - Velocity │
│ │ - Camera Image │
│ │ - Landing Pad Detection │
│ │─────────────────────────►│
│ │ /cmd_vel │
│◄──────────────────────│◄─────────────────────────│
``` ```
## Platform Support **Machine 2 (headless controller):**
```bash
python run_bridge.py --host 192.168.1.100
```
| Mode | Windows | Linux | macOS | ### UDP Ports
|------|---------|-------|-------|
| Standalone | ✅ | ✅ | ✅ |
| Full (ROS 2) | ⚠️ | ✅ | ⚠️ |
| Gazebo | ❌ | ✅ | ❌ |
## UDP Protocol (Full Mode)
| Port | Direction | Content | | Port | Direction | Content |
|------|-----------|---------| |------|-----------|---------|
| 5555 | Bridge → Simulator | Command JSON | | 5555 | Bridge → Simulator | Commands |
| 5556 | Simulator → Bridge | Telemetry JSON | | 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 | ✅ | ⚠️ | ❌ | ❌ | ✅ |

View File

@@ -1,17 +1,8 @@
# Gazebo Simulation # Gazebo Simulation
Running the GPS-denied drone simulation with Gazebo. Running the GPS-denied drone simulation with Gazebo (Linux only).
## Prerequisites ## Quick Start (2 Terminals)
Install Gazebo and ROS-Gazebo bridge:
```bash
./setup/install_ubuntu.sh
source activate.sh
```
## Quick Start
**Terminal 1 - Start Gazebo:** **Terminal 1 - Start Gazebo:**
```bash ```bash
@@ -19,86 +10,54 @@ source activate.sh
gz sim gazebo/worlds/drone_landing.sdf gz sim gazebo/worlds/drone_landing.sdf
``` ```
**Terminal 2 - Spawn drone and start bridge:** **Terminal 2 - Run Controllers:**
```bash ```bash
source activate.sh 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 \ gz service -s /world/drone_landing_world/create \
--reqtype gz.msgs.EntityFactory \ --reqtype gz.msgs.EntityFactory \
--reptype gz.msgs.Boolean \ --reptype gz.msgs.Boolean \
--req 'sdf_filename: "gazebo/models/drone/model.sdf", name: "drone"' --req 'sdf_filename: "gazebo/models/drone/model.sdf", name: "drone"'
# Start bridge
python gazebo_bridge.py
``` ```
**Terminal 3 - Run controllers:** ## GPS-Denied Sensors
```bash
source activate.sh
python controllers.py --pattern circular --speed 0.3
```
## World Description The `run_gazebo.py` script provides the same sensor interface as PyBullet:
The `drone_landing.sdf` world contains: | Sensor | Source |
|--------|--------|
| Object | Description | | IMU | Gazebo odometry |
|--------|-------------| | Altimeter | Gazebo Z position |
| Ground Plane | Infinite flat surface | | Velocity | Gazebo twist |
| Sun | Directional light with shadows | | Camera | Gazebo camera sensor |
| Landing Pad | Green box with "H" marker at origin | | Landing Pad | Computed from relative position |
## 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` |
## Gazebo Topics ## Gazebo Topics
| Topic | Type | Description | | Topic | Type | Description |
|-------|------|-------------| |-------|------|-------------|
| `/drone/cmd_vel` | `gz.msgs.Twist` | Velocity commands | | `/drone/cmd_vel` | `Twist` | Velocity commands |
| `/model/drone/odometry` | `gz.msgs.Odometry` | Drone state | | `/model/drone/odometry` | `Odometry` | Drone state |
| `/drone/camera` | `gz.msgs.Image` | Camera images | | `/drone/camera` | `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 |
## Headless Mode ## Headless Mode
@@ -108,51 +67,29 @@ Run without GUI:
gz sim -s gazebo/worlds/drone_landing.sdf 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 ## 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 ### Model not found
Set the model path: Set the model path:
```bash ```bash
export GZ_SIM_RESOURCE_PATH=$PWD/gazebo/models:$GZ_SIM_RESOURCE_PATH 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 ```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
``` ```

View File

@@ -2,25 +2,19 @@
Running the GPS-denied drone simulation with PyBullet. 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 ```bash
. .\activate.ps1 source activate.sh
python standalone_simulation.py python standalone_simulation.py --pattern circular --speed 0.3
``` ```
### Options ### Options
```powershell ```bash
# Stationary landing pad python standalone_simulation.py --help
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
Options: Options:
--pattern, -p stationary, linear, circular, square --pattern, -p stationary, linear, circular, square
@@ -28,11 +22,11 @@ Options:
--amplitude, -a Movement amplitude in meters (default: 2.0) --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:** **Terminal 1 - Simulator:**
```bash ```bash
@@ -40,36 +34,49 @@ source activate.sh
python simulation_host.py python simulation_host.py
``` ```
**Terminal 2 - ROS Bridge:** **Terminal 2 - Controllers:**
```bash ```bash
source activate.sh source activate.sh
python ros_bridge.py python run_bridge.py --pattern circular --speed 0.3
```
**Terminal 3 - Controllers:**
```bash
source activate.sh
python controllers.py --pattern circular --speed 0.3
``` ```
### Remote Setup ### Remote Setup
Run simulator on one machine, controllers on another. Run simulator on one machine, controllers on another:
**Machine 1 (with display):** **Machine 1 (with display):**
```bash ```bash
python simulation_host.py python simulation_host.py # Listens on 0.0.0.0:5555
``` ```
**Machine 2 (headless):** **Machine 2 (headless):**
```bash ```bash
source activate.sh python run_bridge.py --host 192.168.1.100 --pattern circular
python ros_bridge.py --host <MACHINE_1_IP>
python controllers.py
``` ```
--- ---
## 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 ## Simulation Parameters
| Parameter | Value | | Parameter | Value |
@@ -77,7 +84,7 @@ python controllers.py
| Physics Rate | 240 Hz | | Physics Rate | 240 Hz |
| Telemetry Rate | 24 Hz | | Telemetry Rate | 24 Hz |
| Drone Mass | 1.0 kg | | Drone Mass | 1.0 kg |
| Gravity | -9.81 m/s² | | UDP Port | 5555 (commands), 5556 (telemetry) |
## GPS-Denied Sensors ## GPS-Denied Sensors
@@ -89,40 +96,18 @@ python controllers.py
| Camera | 320x240 downward JPEG image | | Camera | 320x240 downward JPEG image |
| Landing Pad | Vision-based relative position | | 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 ## Troubleshooting
### "Cannot connect to X server" ### "Cannot connect to X server"
PyBullet requires a display: PyBullet GUI requires a display:
- Run on machine with monitor ```bash
- Use X11 forwarding: `ssh -X user@host` # Use virtual display
- Virtual display: `xvfb-run python simulation_host.py` xvfb-run python standalone_simulation.py
# Or use X11 forwarding
ssh -X user@host
```
### Drone flies erratically ### Drone flies erratically
@@ -134,7 +119,7 @@ Kd = 0.2
### Camera image not appearing ### Camera image not appearing
Ensure PIL/Pillow is installed: Install Pillow:
```bash ```bash
pip install pillow pip install pillow
``` ```

View File

@@ -20,7 +20,7 @@ from std_msgs.msg import String
class ROS2SimulatorBridge(Node): class ROS2SimulatorBridge(Node):
"""Bridges ROS 2 topics to UDP-based simulator.""" """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 DEFAULT_SIMULATOR_PORT = 5555
SOCKET_TIMEOUT = 0.1 SOCKET_TIMEOUT = 0.1
RECEIVE_BUFFER_SIZE = 4096 RECEIVE_BUFFER_SIZE = 4096
@@ -171,8 +171,10 @@ Examples:
""" """
) )
parser.add_argument( parser.add_argument(
'--host', '-H', type=str, default='127.0.0.1', '--host', '-H',
help='IP address of the simulator (default: 127.0.0.1)' type=str,
default='0.0.0.0',
help='Simulator host IP address (default: 0.0.0.0)'
) )
parser.add_argument( parser.add_argument(
'--port', '-p', type=int, default=5555, '--port', '-p', type=int, default=5555,

136
run_bridge.py Normal file
View 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
View 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()

View File

@@ -20,7 +20,7 @@ import pybullet_data
class DroneSimulator: class DroneSimulator:
"""PyBullet-based drone simulation with camera.""" """PyBullet-based drone simulation with camera."""
UDP_HOST = '127.0.0.1' UDP_HOST = '0.0.0.0'
UDP_PORT = 5555 UDP_PORT = 5555
UDP_BUFFER_SIZE = 65535 UDP_BUFFER_SIZE = 65535
SOCKET_TIMEOUT = 0.001 SOCKET_TIMEOUT = 0.001