From 4b44c3de91204d382fd1127c086235e35b476f31 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 1 Jan 2026 01:08:30 +0000 Subject: [PATCH] Update to Bridges --- README.md | 101 ++++++++++++++++++--------- build_exe.py | 109 ++++++++++++++++++++--------- docs/architecture.md | 158 +++++++++++++++++++++--------------------- docs/gazebo.md | 159 +++++++++++++------------------------------ docs/pybullet.md | 107 +++++++++++++---------------- ros_bridge.py | 8 ++- run_bridge.py | 136 ++++++++++++++++++++++++++++++++++++ run_gazebo.py | 122 +++++++++++++++++++++++++++++++++ simulation_host.py | 2 +- 9 files changed, 578 insertions(+), 324 deletions(-) create mode 100644 run_bridge.py create mode 100644 run_gazebo.py diff --git a/README.md b/README.md index 0758ca6..ec3ceb9 100644 --- a/README.md +++ b/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 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` \ No newline at end of file +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 +``` \ No newline at end of file diff --git a/build_exe.py b/build_exe.py index 1dddc27..b22b254 100644 --- a/build_exe.py +++ b/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() diff --git a/docs/architecture.md b/docs/architecture.md index d1ad935..b855749 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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 | ✅ | ⚠️ | ❌ | ❌ | ✅ | diff --git a/docs/gazebo.md b/docs/gazebo.md index 7692194..d4b7dee 100644 --- a/docs/gazebo.md +++ b/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 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 ``` diff --git a/docs/pybullet.md b/docs/pybullet.md index 428c674..ab685d8 100644 --- a/docs/pybullet.md +++ b/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 -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 ``` diff --git a/ros_bridge.py b/ros_bridge.py index 3bdaa22..d9f6ffc 100644 --- a/ros_bridge.py +++ b/ros_bridge.py @@ -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, diff --git a/run_bridge.py b/run_bridge.py new file mode 100644 index 0000000..8a423e0 --- /dev/null +++ b/run_bridge.py @@ -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() diff --git a/run_gazebo.py b/run_gazebo.py new file mode 100644 index 0000000..876e814 --- /dev/null +++ b/run_gazebo.py @@ -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() diff --git a/simulation_host.py b/simulation_host.py index 2e5fe4b..bc6d0c7 100644 --- a/simulation_host.py +++ b/simulation_host.py @@ -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