Skip to content

ROS 2 Integration

Full ROS 2 Humble package. The lyrebird_controller node publishes all telemetry fields as individual topics and subscribes to command topics. It checks for new telemetry at 20 Hz but publishes only when the drone has actually sent a new snapshot, so the topic rate follows the aircraft’s telemetry interval (default ~2 Hz) rather than repeating each sample.

GroundStation/ROS/
├── lyrebird_controller/ # Main control + telemetry node
│ ├── controller.py # DjiNode: 45+ topics, publishes on new telemetry
│ └── dji_interface.py
├── lyrebird_videofeed/ # RTSP video feed -> sensor_msgs/Image
└── lyrebird_bringup/
├── auto_discovery_native.launch.py # one namespaced lyrebird_controller per discovered drone, re-scans periodically
├── swarm_connection.launch.py
└── config/parameters.yaml

Published topics (per drone namespace, e.g. /mini1/)

Section titled “Published topics (per drone namespace, e.g. /mini1/)”
TopicTypeDescription
speedFloat64Velocity magnitude (m/s)
speed_vectorVector3Velocity vector x, y, z (m/s)
headingFloat64Compass heading (degrees)
attitudeString{pitch, roll, yaw} JSON
locationNavSatFixGPS latitude, longitude, altitude
gimbal_attitudeString{pitch, roll, yaw} JSON
gimbal_joint_attitudeStringJoint angles JSON
gimbal_yaw / gimbal_pitchFloat64Individual gimbal angles
zoom_fl / hybrid_fl / optical_flFloat64Focal lengths (-1 if N/A)
zoom_ratioFloat64Camera zoom ratio
battery_levelFloat64Battery %
satellite_countInt32GPS satellite count
waypoint_reachedBoolFinal WP flag
intermediary_waypoint_reachedBoolIntermediate WP flag
altitude_reached / yaw_reachedBoolReach flags
home_locationNavSatFixHome GPS coordinates
home_setBoolHome point set flag
distance_to_homeFloat64Distance to home (m)
remaining_flight_timeFloat64Remaining flight time (s)
time_needed_to_go_homeFloat64Time to RTH (s)
time_needed_to_landFloat64Time to land (s)
time_to_landing_spotFloat64Go-home + land (s)
max_radius_can_fly_and_go_homeFloat64Max safe radius (m)
battery_needed_to_go_homeFloat64Battery % for RTH
battery_needed_to_landFloat64Battery % to land
camera/is_recordingBoolRecording status
flight_modeStringCurrent DJI flight mode string
manual_override_activeBoolPilot override active
waypoint_seq / yaw_seq / altitude_seqInt32Sequence id of the command currently executing
command_ack/waypoint_seq / yaw_seq / altitude_seqInt32Sequence id assigned when the command was accepted
ready_to_takeoffBoolPreflight conditions satisfied
takeoff_block_reasonStringWhy takeoff is blocked
camera/capture_resultStringCapture descriptor JSON (async)
camera/media_listStringSD card listing JSON (async)
camera/download_resultStringDownload result JSON (async)
camera/thermal_max_tempFloat64Thermal max temperature (°C)
lrf/measurementStringLaser rangefinder reading JSON
lrf/targetNavSatFixGeo-referenced laser target

Camera, media, and LRF commands block for as long as the aircraft takes to answer (up to 120 s for a download), so they run on a single-worker thread pool and answer asynchronously on their own camera/* and lrf/* result topics rather than stalling the telemetry loop.

TopicTypeBody
command/takeoffEmpty
command/landEmpty
command/rthEmpty
command/abort_missionEmpty
command/abort_allEmpty
command/enable_virtual_stickEmpty
command/abort_dji_native_missionEmpty
command/deactivate_manual_overrideEmpty
command/camera/start_recordingEmpty
command/camera/stop_recordingEmpty
command/goto_waypoint_nose_forwardFloat64MultiArray[lat, lon, alt, yaw, speed?]yaw = final arrival heading
command/goto_waypoint_hold_headingFloat64MultiArray[lat, lon, alt, yaw, speed?]yaw held for the whole flight
command/goto_trajectory_dji_nativeString"(speed, [(lat,lon,alt),...])"
command/goto_yawFloat64Yaw angle (degrees)
command/goto_altitudeFloat64Altitude (m)
command/gimbal_pitchFloat64Pitch (degrees)
command/gimbal_yawFloat64Yaw joint angle
command/zoom_ratioFloat64Zoom ratio
command/set_rth_altitudeFloat64RTH altitude (m)
command/stickFloat64MultiArray[leftX, leftY, rightX, rightY] ∈ [-1,1]
command/gimbal_rel_pitchFloat64Pitch relative to current angle (degrees)
command/gimbal_rel_yawFloat64Yaw relative to current angle (degrees)
command/camera/captureEmptyCapture on the thermal lens
command/camera/capture_temperatureEmptyRead thermal max temperature
command/camera/list_mediaEmptyList SD card contents
command/camera/download_mediaStringDownload one media file by name
command/lrf/measureEmptyFire the laser rangefinder
command/dropEmptyRelease the payload

Docker (single-drone, auto-discovery):

Terminal window
cd GroundStation
docker build -t lyrebird-ros .
docker run --rm --network=host lyrebird-ros

The image is based on ros:humble with CycloneDDS, cv-bridge, vision-opencv, image-transport, plus all Python dependencies.

Manual multi-drone launch:

Terminal window
cd GroundStation/ROS
colcon build --symlink-install && source install/setup.bash
ros2 launch lyrebird_bringup auto_discovery_native.launch.py
# Example commands (namespace is the drone's own name, e.g. "mini1")
ros2 topic pub /mini1/command/takeoff std_msgs/msg/Empty "{}"
ros2 topic pub /mini1/command/goto_waypoint_nose_forward std_msgs/msg/Float64MultiArray \
"data: [49.306254, 4.593728, 20.0, 90.0, 5.0]"