Complete API documentation for the SADE Software Pilot package.
Configuration object passed to pilots at startup.
from software_pilot.config import PilotConfig
config = PilotConfig(
drone_id=0,
mavsdk_port=14550,
mavlink_port=14540,
mqtt_broker_address="localhost:1883",
sade_zone_config_path=Path("/etc/pilot/zones.json"),
custom_settings={"param1": "value1"}
)| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
drone_id |
int |
Yes | — | Unique drone identifier (0-based) |
mavsdk_port |
int |
No | 14550 | gRPC port for MAVSDK autopilot |
mavlink_port |
int |
No | 14540 | UDP port for MAVLink protocol |
mqtt_broker_address |
str |
No | "localhost:1883" | MQTT broker (host:port) |
sade_zone_config_path |
Path | None |
No | None | Path to zone configuration |
custom_settings |
dict[str, Any] |
No | {} | User-defined mission parameters |
Export configuration as dictionary.
config_dict = config.model_dump()Export configuration as JSON string.
json_str = config.model_dump_json()Main interface for controlling drones and monitoring telemetry.
from software_pilot.uav import ResilientDrone
drone = ResilientDrone(
listen_port="14540",
drone_id=0,
mavsdk_port=14550,
max_retries=10,
retry_delay=5,
)listen_port(str): UDP port the drone listens ondrone_id(int): Unique identifier for this dronemavsdk_port(int): gRPC port for local MAVSDK servicemax_retries(int): Maximum reconnection attempts (default: 10)retry_delay(int): Delay between retries in seconds (default: 5)schema(str): Connection schema (default: "udpin://")host(str): Host to connect to (default: "0.0.0.0")
Connect to the drone autopilot. Automatically retries on failure.
try:
await drone.connect()
except ConnectionError:
print("Failed to connect after max retries")Raises:
ConnectionError: If unable to connect aftermax_retriesattempts
Ensure drone is connected, reconnecting if necessary.
await drone.ensure_connected()Get current drone position.
Returns: (latitude, longitude, altitude_msl)
lat, lon, msl_alt = await drone.fetch_drone_position()
print(f"Position: {lat:.6f}, {lon:.6f}, altitude: {msl_alt:.1f}m")Wait until GPS has a valid position estimate.
await drone.wait_for_global_position_estimate()Stream continuous position updates.
Yields: Position objects
async for position in drone.telemetry_position():
print(f"Lat: {position.latitude_deg}, Lon: {position.longitude_deg}")
break # Exit loop after first updateStream continuous health status.
Yields: Health status objects
async for health in drone.telemetry_health():
print(f"GPS OK: {health.is_global_position_ok}")
breakArm the drone motors (prepare for takeoff).
await drone.action_arm()May raise:
- Connection errors if drone is unreachable
Command the drone to take off to default altitude.
await drone.action_takeoff()Command the drone to land at current location.
await drone.action_land()Upload and execute a mission.
mission_steps = [
MissionStep(...),
MissionStep(...),
]
await drone.execute_mission(mission_steps)Parameters:
mission_steps: List ofMissionStepobjects defining the flight plan
Behavior:
- Uploads all waypoints to autopilot
- Arms drone
- Starts mission
- Monitors progress
- Returns when complete
Upload mission without starting execution.
from mavsdk.mission import MissionPlan, MissionItem
items = [MissionItem(...)]
plan = MissionPlan(items)
await drone.mission_upload_mission(plan)Start previously uploaded mission.
await drone.mission_start_mission()Monitor mission execution progress.
Yields: Progress objects with current and total fields
async for progress in drone.mission_mission_progress():
print(f"Waypoint {progress.current}/{progress.total}")Direct access to MAVSDK Mission object for advanced operations.
# Use MAVSDK API directly if needed
await drone.mission.clear_mission()Represents a single waypoint in a flight plan.
from software_pilot.uav import MissionStep
step = MissionStep(
short_name="waypoint_1",
description="Fly north to search area",
ned=NED(north=500, east=0, down=-100),
home_alt=229.0,
speed=15.0,
home=home_lla,
)| Parameter | Type | Required | Description |
|---|---|---|---|
short_name |
str |
Yes | Brief identifier (used in logs) |
description |
str |
Yes | Human-readable description |
ned |
NED |
Yes | Position relative to home |
home_alt |
float |
Yes | Sea-level altitude at home (meters) |
speed |
float |
Yes | Flight speed (m/s) |
home |
Lla |
Yes | Home position (Lla object) |
Convert to MAVSDK MissionItem for upload.
item = step.create_mission_item()North-East-Down coordinate system (relative to home).
from software_pilot.uav import NED
ned = NED(
north=100.0, # meters north
east=50.0, # meters east
down=-75.0, # meters below (negative = up)
)The down value is typically negative to indicate altitude above ground.
Examples:
# 500m north, 0m east, 100m altitude above home
NED(north=500, east=0, down=-100)
# Directly above home at 50m
NED(north=0, east=0, down=-50)
# 200m southeast at 150m altitude
NED(north=0, east=200, down=-150)
# Return to home at same altitude
NED(north=0, east=0, down=0)Request airspace access for the drone to enter a SADE-controlled zone.
from software_pilot.zones import request_sade_zone_entry
lease = request_sade_zone_entry(drone, emulate_wait=False)
if lease:
print(f"Access granted until {lease.expiration_time}")
# Safe to enter zone
else:
print("Access denied, returning home")| Parameter | Type | Default | Description |
|---|---|---|---|
drone |
ResilientDrone |
— | The drone requesting access |
emulate_wait |
bool |
False | If true, sleep to simulate request latency |
SadeZoneLeaseif access grantedNoneif access denied or already occupied
Represents approval to operate in a zone.
from software_pilot.zones import SadeZoneLease
from datetime import datetime, UTC
lease = SadeZoneLease(
drone_id=0,
zone_id="sade-zone-1",
grant_time=datetime.now(UTC),
expiration_time=datetime.now(UTC) + timedelta(minutes=5),
)| Attribute | Type | Description |
|---|---|---|
drone_id |
int |
Drone that holds the lease |
zone_id |
str |
Zone identifier |
grant_time |
datetime |
When lease was granted |
expiration_time |
datetime |
When lease expires |
Check if lease is still valid at given time.
from datetime import datetime, UTC
if lease.is_active(datetime.now(UTC)):
print("Safe to operate")
else:
print("Lease expired, return home")All telemetry methods return async generators. Use async for to consume:
async for update in drone.telemetry_health():
print(update)
# Process one update, then exits (remove break to continue streaming)
breakasync for position in drone.telemetry_position():
print(f"Lat: {position.latitude_deg}")
print(f"Lon: {position.longitude_deg}")
print(f"Alt (ABS): {position.absolute_altitude_m}")
print(f"Alt (REL): {position.relative_altitude_m}")async for health in drone.telemetry_health():
print(f"GPS ready: {health.is_global_position_ok}")
print(f"Compass ready: {health.is_magnetometer_ok}")
print(f"Barometer ready: {health.is_barometer_ok}")async for state in drone.core_connection_state():
print(f"Connected: {state.is_connected}")
print(f"UUID: {state.uuid}")Raised when unable to establish or maintain connection to drone.
try:
await drone.connect()
except ConnectionError as e:
print(f"Connection failed: {e}")Raised when mission upload or execution fails.
from mavsdk.mission import MissionError
try:
await drone.execute_mission(steps)
except MissionError as e:
print(f"Mission error: {e}")Raised when configuration validation fails.
try:
config = PilotConfig(drone_id=-1) # Invalid
except ValueError as e:
print(f"Configuration error: {e}")import asyncio
from pathlib import Path
from droneresponse_mathtools import Lla
from software_pilot.config import PilotConfig
from software_pilot.uav import ResilientDrone, MissionStep, NED
from software_pilot.zones import request_sade_zone_entry
async def main():
# Load configuration
config = PilotConfig(
drone_id=0,
mavsdk_port=14550,
custom_settings={"speed_mps": 15.0}
)
# Create drone interface
drone = ResilientDrone(
listen_port="14540",
drone_id=config.drone_id,
mavsdk_port=config.mavsdk_port,
)
try:
# Connect
await drone.connect()
await drone.wait_for_global_position_estimate()
# Get home position
lat, lon, alt = await drone.fetch_drone_position()
home = Lla(lat=lat, lon=lon, altitude=alt)
# Create mission
speed = config.custom_settings["speed_mps"]
mission = [
MissionStep(
short_name="takeoff",
description="Take off",
ned=NED(north=0, east=0, down=-50),
home_alt=home.altitude,
speed=speed,
home=home,
),
MissionStep(
short_name="search",
description="Fly north to search area",
ned=NED(north=500, east=0, down=-50),
home_alt=home.altitude,
speed=speed,
home=home,
),
]
# Request zone access if needed
lease = request_sade_zone_entry(drone)
if lease:
print(f"Zone access granted until {lease.expiration_time}")
# Execute mission
await drone.execute_mission(mission)
# Land
await drone.action_land()
except Exception as e:
print(f"Mission failed: {e}")
if __name__ == "__main__":
asyncio.run(main())- Configuration - How to configure pilots
- Contributing - How to contribute missions
- Examples - Example missions
- Main README