Skip to content
This repository was archived by the owner on Mar 12, 2026. It is now read-only.

UW-CTRL/WISAR-GCS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WISAR-GCS — Multi-Drone Branch

UAV Ground Control Station for Wilderness Search and Rescue Research

This branch contains the multi-drone backend rewrite. It replaces the original single-drone backend with a fully async, dynamically-scaling architecture that manages an arbitrary number of simultaneous PX4 drones over serial and/or UDP connections.


What's Different on This Branch

Single-Drone (main) Multi-Drone (this branch)
Drone identity Implicit drone_id = "drone_{sys_id}" from MAVLink srcSystem
Connections One fixed connection One per physical device; multiple drones per UDP broadcast
State storage Single flat dict GlobalState._drone_data[drone_id] keyed per drone
Command routing Direct CommandHandler looks up drone_registry[drone_id]
Fleet commands N/A START_MISSIONS, STOP_MISSIONS, RTL_ALL
Joystick control Implicit Targeted to selected_drone_id
DroneManager lifecycle Static Spawned on first message from a new sys_id; self-terminates after 1 s of silence
AI chat Single drone Returns per-drone command sequences for the whole fleet
Telemetry broadcast Per-drone Full fleet state in one telemetry push every 200 ms

Architecture

Frontend (React)
      │  WebSocket /ws
      ▼
┌──────────────────────────────────────────────────────────┐
│                    FastAPI Backend                        │
│                                                          │
│  websocket_server.py  ──►  CommandHandler                │
│  (broadcast loop)              │                         │
│                                ▼                         │
│                       drone_registry                     │
│                    { drone_id: DroneManager }            │
│                                ▲                         │
│                                │ spawn on new sys_id     │
│                    ConnectionManager                     │
│                    (device scan every 2 s)               │
└──────────┬───────────────────────────────────────────────┘
           │ MAVLink (serial / UDP)
    ┌──────┴──────────────────────────┐
    │  Serial port(s)   UDP port(s)   │
    │  drone_1          drone_2       │
    │  drone_3          drone_4 ...   │
    └─────────────────────────────────┘

All frontend ↔ backend communication uses a single /ws WebSocket endpoint. Messages are JSON with command (client→server) or messagetype (server→client).


Project Structure

multi-drone backend/
├── main.py                      # Entry point, wires all subsystems
├── requirements.txt
│
├── api/
│   ├── websocket_server.py      # FastAPI app, /ws endpoint, telemetry broadcast loop
│   └── command_handler.py       # Routes WS commands to per-drone DroneManager instances
│
├── core/
│   ├── connection_manager.py    # Device discovery, MAVLink connections, DroneManager spawning
│   ├── mission_planner.py       # Lawnmower/boustrophedon survey pattern generator
│   ├── chat_completion.py       # OpenAI GPT-4o integration for natural-language commands
│   ├── joystick_manager.py      # Pygame gamepad polling → shared_state["joystick_pose"]
│   ├── controller_config.json   # Axis calibration (min/center/max) for physical gamepad
│   └── logging_conf.py          # Rotating file logs (debug.log, info.log, daily rotation)
│
├── drones/
│   ├── drone_manager.py         # Per-drone MAVLink message handler and mission state machine
│   └── drone_commands.py        # MAVLink command functions (arm, takeoff, mode, mission, etc.)
│
└── shared/
    ├── state.py                 # GlobalState singleton — thread-safe per-drone telemetry store
    ├── db.py                    # SQLite persistence (settings, UDP ports)
    ├── constants.py             # Mode mappings, MAV_STATE, MAV_SEVERITY, param types
    └── utils.py                 # haversine_distance for position history throttling

Setup

Prerequisites

  • Python 3.10+
  • One or more PX4 drones or SITL instances (see px4-gazebo-headless for Docker/WSL SITL)

Install

cd "multi-drone backend"

python -m venv venv
.\venv\Scripts\activate

pip install -r requirements.txt

Configure

Create a .env file in multi-drone backend/:

OPENAI_KEY=your_openai_api_key_here

UDP ports are persisted in SQLite (settings table, key udp_ports). The default is [14550]. You can update this at runtime via the UPDATE_SETTINGS WebSocket command.

Run

.\venv\Scripts\activate
python main.py

The server starts on http://0.0.0.0:8000. The WebSocket endpoint is ws://localhost:8000/ws.


Connection Behavior

ConnectionManager scans for devices every 2 seconds:

  1. Serial ports — enumerates ports matching "USB", "Silicon Labs", or "UART" on Windows; /dev/ttyUSB* and /dev/ttyACM* on Linux/macOS
  2. UDP — opens udp:0.0.0.0:<port> for each port in the udp_ports setting (default: 14550)

For each device, it opens a MAVLink connection and waits up to 3 s for a heartbeat. On success, a background reader loop forwards every message to its DroneManager, keyed by the MAVLink srcSystem ID:

sys_id 1  →  drone_registry["drone_1"]  →  DroneManager
sys_id 3  →  drone_registry["drone_3"]  →  DroneManager
...

Multiple drones can share a single UDP broadcast channel. DroneManager instances are created dynamically on the first message from a new sys_id and self-terminate after 1 second of silence.


WebSocket Protocol

Server → Client

messagetype Content
telemetry { "drone_1": {...state}, "drone_3": {...state} } — full fleet state at ~5 Hz
settings { udp_ports, selected_drone_id, joystick_pose, joystick_connected }
waypoints Generated lawnmower waypoints for a polygon
selected_drone Confirmation of active drone selection
chatmessage GPT-4o response with message, confirmation, and per-drone drone_commands
error Error description string

Client → Server

All commands are JSON with a command field and a drone_id field.

Single-drone commands (drone_id: "drone_N"):

command data fields Description
ARM_DISARM Toggle arm/disarm based on current state
TAKEOFF_LAND Toggle takeoff/land based on landed_state
SET_MODE mode Set PX4 flight mode (e.g. "Loiter", "Mission")
RTL Return to launch
CLEAR_MISSION Clear onboard mission
UPLOAD_MISSION waypoints, altitude Upload a list of lat/lon waypoints
SET_PARAM param_id, param_value, param_type Write a MAVLink parameter
CREATE_LAWNMOWER polygon, spacing Generate and return a lawnmower survey pattern
BATCH_COMMANDS commands[] Sequenced commands with state-polling waits between steps

Fleet-wide commands (drone_id: "all"):

command Description
START_MISSIONS Set all drones to Mission mode
STOP_MISSIONS Set all drones to Loiter mode
RTL_ALL RTL every drone

GCS system commands (drone_id: "gcs"):

command data fields Description
UPDATE_SETTINGS udp_ports Persist UDP port list to SQLite
SELECT_DRONE drone_id Set active drone for joystick control
SEND_CHAT message Send natural-language prompt to GPT-4o

Telemetry State Fields

Each drone's state dict in the telemetry broadcast contains:

Field Source message
mode, system_status, autopilot HEARTBEAT
global_lat, global_lon, global_alt GLOBAL_POSITION_INT
global_position_history Appended when drone moves >1 m
local_x, local_y, local_z LOCAL_POSITION_NED
roll, pitch, yaw ATTITUDE
fix_type, satellites_visible, vel GPS_RAW_INT
voltage_battery, current_battery, battery_remaining SYS_STATUS
landed_state EXTENDED_SYS_STATE
home_lat, home_lon, home_alt HOME_POSITION
airspeed, groundspeed, heading, throttle, climb VFR_HUD
parameters PARAM_VALUE (all params, type-converted)
messages STATUSTEXT (appended)
mission_current MISSION_CURRENT
uploaded_mission MISSION_ITEM / MISSION_ITEM_INT
command_ack COMMAND_ACK (appended)

BATCH_COMMANDS

BATCH_COMMANDS allows sequenced multi-step operations with automatic state-polling waits between each step. Each step specifies a command and optionally a wait_for condition:

{
  "command": "BATCH_COMMANDS",
  "drone_id": "drone_1",
  "data": {
    "commands": [
      { "command": "ARM_DISARM" },
      { "wait_for": "ARMED" },
      { "command": "TAKEOFF_LAND" },
      { "wait_for": "FLYING" }
    ]
  }
}

Supported wait_for states (polls every 100 ms, 10 s timeout):

State Condition
ARMED system_status == 4
DISARMED system_status == 3
FLYING landed_state != 1
LANDED landed_state == 1
MODE Current mode matches target
MISSION_CLEARED uploaded_mission == []

LLM / Chat Integration

SEND_CHAT sends the user's message to GPT-4o along with a summary of the current fleet state. The model returns a structured JSON response:

{
  "message": "Taking off drone_1 and drone_3.",
  "confirmation": "Arm both drones then take off to 10 m.",
  "drone_commands": {
    "drone_1": { "commands": [{ "command": "ARM_DISARM" }, { "command": "TAKEOFF_LAND" }] },
    "drone_3": { "commands": [{ "command": "ARM_DISARM" }, { "command": "TAKEOFF_LAND" }] }
  }
}

The frontend presents message/confirmation to the user and, on approval, dispatches each drone_commands entry as a BATCH_COMMANDS message.


Joystick Control

JoystickManager polls a physical gamepad via pygame every 50 ms and writes scaled axis values to shared_state["joystick_pose"]. ConnectionManager then sends MANUAL_CONTROL MAVLink messages to the currently selected drone at 20 Hz.

Axis calibration (min/center/max raw values) is stored in core/controller_config.json and reloaded every 500 ms — changes take effect without a restart.

To select which drone the joystick controls, send:

{ "command": "SELECT_DRONE", "drone_id": "gcs", "data": { "drone_id": "drone_1" } }

Lawnmower Survey Patterns

CREATE_LAWNMOWER generates a boustrophedon (back-and-forth) survey pattern over an arbitrary polygon:

{
  "command": "CREATE_LAWNMOWER",
  "drone_id": "drone_1",
  "data": {
    "polygon": [[lon, lat], [lon, lat], ...],
    "spacing": 20
  }
}

The planner converts the polygon to UTM, finds the minimum bounding rectangle to determine the optimal sweep angle, generates four candidate patterns, selects the one whose first waypoint is nearest to the drone's home position, and returns the result as a waypoints WebSocket message.

About

UAV ground control station for wilderness search and rescue research

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors