This example creates and runs a waypoint mission using position information from a TLOG file.
The log used in this example contains around 2700 points. This is too many points to upload to the autopilot (and to usefully display). Instead we only add points that are more than 3 metres away from the previously kept point, and only store 99 points in total. After 60 seconds the mission is ended by setting the mode to RTL (return to launch).
Note
The method used to reduce the number of points is fairly effective, but we could do better by grouping some of the waypoints, and mapping others using spline waypoints. This might be a fun research project!
The example can be run as described in Running the Examples (which in turn assumes that the vehicle and DroneKit have been set up as described in Installing DroneKit).
In summary, after cloning the repository:
Navigate to the example folder as shown:
cd dronekit-python/examples/flight_replay/
You can run the example against a simulator (DroneKit-SITL) by specifying the Python script without any arguments. The example will download SITL binaries if needed, start the simulator, and then connect to it:
python flight_replay.py
On the command prompt you should see (something like):
Generating waypoints from tlog...
Generated 100 waypoints from tlog
Starting copter simulator (SITL)
SITL already Downloaded.
Connecting to vehicle on: tcp:127.0.0.1:5760
>>> APM:Copter V3.3 (d6053245)
>>> Frame: QUAD
>>> Calibrating barometer
>>> Initialising APM...
>>> barometer calibration complete
>>> GROUND START
Uploading 100 waypoints to vehicle...
Arm and Takeoff
Waiting for vehicle to initialise...
>>> flight plan received
Waiting for arming...
Waiting for arming...
Waiting for arming...
Waiting for arming...
>>> ARMING MOTORS
>>> GROUND START
Waiting for arming...
>>> Initialising APM...
Waiting for arming...
>>> ARMING MOTORS
Taking off!
Altitude: 0.000000 < 28.500000
Altitude: 0.010000 < 28.500000
...
Altitude: 26.350000 < 28.500000
Altitude: 28.320000 < 28.500000
Reached target altitude of ~30.000000
Starting mission
Distance to waypoint (1): 3.02389745499
>>> Reached Command #1
Distance to waypoint (2): 5.57718471895
Distance to waypoint (2): 4.1504263025
>>> Reached Command #2
Distance to waypoint (3): 0.872847106279
Distance to waypoint (3): 1.88967925144
Distance to waypoint (3): 2.16157704522
>>> Reached Command #3
Distance to waypoint (4): 4.91867197924
...
...
Distance to waypoint (35): 4.37309981133
>>> Reached Command #35
Distance to waypoint (36): 5.61829417257
>>> Reached Command #36
Return to launch
Close vehicle object
Completed...
Tip
It is more interesting to watch the example run on a map than the console. The topic Connecting an additional Ground Station explains how to set up Mission Planner to view a vehicle running on the simulator (SITL).
You can run the example against a specific connection (simulated or otherwise) by passing the connection string for your vehicle in the --connect
parameter.
For example, to connect to SITL running on UDP port 14550 on your local computer:
python simple_goto.py --connect 127.0.0.1:14550
The example parses the flight.tlog file for position information. First we read all the points. We then keep the first 99 points that are at least 3 metres separated from the preceding kept point.
For safety reasons, the altitude for the waypoints is set to 30 meters (irrespective of the recorded height).
def position_messages_from_tlog(filename):
"""
Given telemetry log, get a series of wpts approximating the previous flight
"""
# Pull out just the global position msgs
messages = []
mlog = mavutil.mavlink_connection(filename)
while True:
try:
m = mlog.recv_match(type=['GLOBAL_POSITION_INT'])
if m is None:
break
except Exception:
break
# ignore we get where there is no fix:
if m.lat == 0:
continue
messages.append(m)
# Shrink the number of points for readability and to stay within autopilot memory limits.
# For coding simplicity we:
# - only keep points that are with 3 metres of the previous kept point.
# - only keep the first 100 points that meet the above criteria.
num_points = len(messages)
keep_point_distance=3 #metres
kept_messages = []
kept_messages.append(messages[0]) #Keep the first message
pt1num=0
pt2num=1
while True:
#Keep the last point. Only record 99 points.
if pt2num==num_points-1 or len(kept_messages)==99:
kept_messages.append(messages[pt2num])
break
pt1 = LocationGlobalRelative(messages[pt1num].lat/1.0e7,messages[pt1num].lon/1.0e7,0)
pt2 = LocationGlobalRelative(messages[pt2num].lat/1.0e7,messages[pt2num].lon/1.0e7,0)
distance_between_points = get_distance_metres(pt1,pt2)
if distance_between_points > keep_point_distance:
kept_messages.append(messages[pt2num])
pt1num=pt2num
pt2num=pt2num+1
return kept_messages
The following code shows how the vehicle writes the received messages as commands (this part of the code is very similar to that shown in Example: Basic Mission):
print "Generating %s waypoints from replay..." % len(messages)
cmds = vehicle.commands
cmds.clear()
for i in xrange(0, len(messages)):
pt = messages[i]
lat = pt['lat']
lon = pt['lon']
# To prevent accidents we don't trust the altitude in the original flight, instead
# we just put in a conservative cruising altitude.
altitude = 30.0 # pt['alt']
cmd = Command( 0,
0,
0,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
0, 0, 0, 0, 0, 0,
lat, lon, altitude)
cmds.add(cmd)
#Upload clear message and command messages to vehicle.
cmds.upload()
There are no known issues with this example.
The full source code at documentation build-time is listed below (current version on github):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
© Copyright 2015-2016, 3D Robotics.
flight_replay.py:
This example requests a past flight from Droneshare, and then 'replays'
the flight by sending waypoints to a vehicle.
Full documentation is provided at http://python.dronekit.io/examples/flight_replay.html
"""
from __future__ import print_function
from dronekit import connect, Command, VehicleMode, LocationGlobalRelative
from pymavlink import mavutil
import json, urllib, math
import time
#Set up option parsing to get connection string
import argparse
parser = argparse.ArgumentParser(description='Load a telemetry log and use position data to create mission waypoints for a vehicle. Connects to SITL on local PC by default.')
parser.add_argument('--connect', help="vehicle connection target.")
parser.add_argument('--tlog', default='flight.tlog',
help="Telemetry log containing path to replay")
args = parser.parse_args()
def get_distance_metres(aLocation1, aLocation2):
"""
Returns the ground distance in metres between two LocationGlobal objects.
This method is an approximation, and will not be accurate over large distances and close to the
earth's poles. It comes from the ArduPilot test code:
https://github.com/diydrones/ardupilot/blob/master/Tools/autotest/common.py
"""
dlat = aLocation2.lat - aLocation1.lat
dlong = aLocation2.lon - aLocation1.lon
return math.sqrt((dlat*dlat) + (dlong*dlong)) * 1.113195e5
def distance_to_current_waypoint():
"""
Gets distance in metres to the current waypoint.
It returns None for the first waypoint (Home location).
"""
nextwaypoint = vehicle.commands.next
if nextwaypoint==0:
return None
missionitem=vehicle.commands[nextwaypoint-1] #commands are zero indexed
lat = missionitem.x
lon = missionitem.y
alt = missionitem.z
targetWaypointLocation = LocationGlobalRelative(lat,lon,alt)
distancetopoint = get_distance_metres(vehicle.location.global_frame, targetWaypointLocation)
return distancetopoint
def position_messages_from_tlog(filename):
"""
Given telemetry log, get a series of wpts approximating the previous flight
"""
# Pull out just the global position msgs
messages = []
mlog = mavutil.mavlink_connection(filename)
while True:
try:
m = mlog.recv_match(type=['GLOBAL_POSITION_INT'])
if m is None:
break
except Exception:
break
# ignore we get where there is no fix:
if m.lat == 0:
continue
messages.append(m)
# Shrink the number of points for readability and to stay within autopilot memory limits.
# For coding simplicity we:
# - only keep points that are with 3 metres of the previous kept point.
# - only keep the first 100 points that meet the above criteria.
num_points = len(messages)
keep_point_distance=3 #metres
kept_messages = []
kept_messages.append(messages[0]) #Keep the first message
pt1num=0
pt2num=1
while True:
#Keep the last point. Only record 99 points.
if pt2num==num_points-1 or len(kept_messages)==99:
kept_messages.append(messages[pt2num])
break
pt1 = LocationGlobalRelative(messages[pt1num].lat/1.0e7,messages[pt1num].lon/1.0e7,0)
pt2 = LocationGlobalRelative(messages[pt2num].lat/1.0e7,messages[pt2num].lon/1.0e7,0)
distance_between_points = get_distance_metres(pt1,pt2)
if distance_between_points > keep_point_distance:
kept_messages.append(messages[pt2num])
pt1num=pt2num
pt2num=pt2num+1
return kept_messages
def arm_and_takeoff(aTargetAltitude):
"""
Arms vehicle and fly to aTargetAltitude.
"""
# Don't try to arm until autopilot is ready
while not vehicle.is_armable:
print(" Waiting for vehicle to initialise...")
time.sleep(1)
# Set mode to GUIDED for arming and takeoff:
while (vehicle.mode.name != "GUIDED"):
vehicle.mode = VehicleMode("GUIDED")
time.sleep(0.1)
# Confirm vehicle armed before attempting to take off
while not vehicle.armed:
vehicle.armed = True
print(" Waiting for arming...")
time.sleep(1)
print(" Taking off!")
vehicle.simple_takeoff(aTargetAltitude) # Take off to target altitude
# Wait until the vehicle reaches a safe height
# before allowing next command to process.
while True:
requiredAlt = aTargetAltitude*0.95
#Break and return from function just below target altitude.
if vehicle.location.global_relative_frame.alt>=requiredAlt:
print(" Reached target altitude of ~%f" % (aTargetAltitude))
break
print(" Altitude: %f < %f" % (vehicle.location.global_relative_frame.alt,
requiredAlt))
time.sleep(1)
print("Generating waypoints from tlog...")
messages = position_messages_from_tlog(args.tlog)
print(" Generated %d waypoints from tlog" % len(messages))
if len(messages) == 0:
print("No position messages found in log")
exit(0)
#Start SITL if no connection string specified
if args.connect:
connection_string = args.connect
sitl = None
else:
start_lat = messages[0].lat/1.0e7
start_lon = messages[0].lon/1.0e7
import dronekit_sitl
sitl = dronekit_sitl.start_default(lat=start_lat,lon=start_lon)
connection_string = sitl.connection_string()
# Connect to the Vehicle
print('Connecting to vehicle on: %s' % connection_string)
vehicle = connect(connection_string, wait_ready=True)
# Now download the vehicle waypoints
cmds = vehicle.commands
cmds.wait_ready()
cmds = vehicle.commands
cmds.clear()
for pt in messages:
#print "Point: %d %d" % (pt.lat, pt.lon,)
lat = pt.lat
lon = pt.lon
# To prevent accidents we don't trust the altitude in the original flight, instead
# we just put in a conservative cruising altitude.
altitude = 30.0
cmd = Command( 0,
0,
0,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
0, 0, 0, 0, 0, 0,
lat/1.0e7, lon/1.0e7, altitude)
cmds.add(cmd)
#Upload clear message and command messages to vehicle.
print("Uploading %d waypoints to vehicle..." % len(messages))
cmds.upload()
print("Arm and Takeoff")
arm_and_takeoff(30)
print("Starting mission")
# Reset mission set to first (0) waypoint
vehicle.commands.next=0
# Set mode to AUTO to start mission:
while (vehicle.mode.name != "AUTO"):
vehicle.mode = VehicleMode("AUTO")
time.sleep(0.1)
# Monitor mission for 60 seconds then RTL and quit:
time_start = time.time()
while time.time() - time_start < 60:
nextwaypoint=vehicle.commands.next
print('Distance to waypoint (%s): %s' % (nextwaypoint, distance_to_current_waypoint()))
if nextwaypoint==len(messages):
print("Exit 'standard' mission when start heading to final waypoint")
break;
time.sleep(1)
print('Return to launch')
while (vehicle.mode.name != "RTL"):
vehicle.mode = VehicleMode("RTL")
time.sleep(0.1)
#Close vehicle object before exiting script
print("Close vehicle object")
vehicle.close()
# Shut down simulator if it was started.
if sitl is not None:
sitl.stop()
print("Completed...")