Skip to content

Commit 17bb5a4

Browse files
committed
Rejig flight replay example to use a telemetry log
1 parent 4b9dfa8 commit 17bb5a4

File tree

2 files changed

+169
-91
lines changed

2 files changed

+169
-91
lines changed

examples/flight_replay/flight.tlog

2.6 MB
Binary file not shown.

examples/flight_replay/flight_replay.py

Lines changed: 169 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -7,122 +7,200 @@
77
Full documentation is provided at http://python.dronekit.io/examples/flight_replay.html
88
"""
99

10-
from dronekit import connect, Command
10+
from dronekit import connect, Command, VehicleMode, LocationGlobalRelative
1111
from pymavlink import mavutil
1212
import json, urllib, math
13+
import time
1314

1415
#Set up option parsing to get connection string
1516
import argparse
16-
parser = argparse.ArgumentParser(description='Get a flight from droneshare and send as waypoints to a vehicle. Connects to SITL on local PC by default.')
17-
parser.add_argument('--connect', default='127.0.0.1:14550',
18-
help="vehicle connection target. Default '127.0.0.1:14550'")
19-
parser.add_argument('--mission_id', default='101',
20-
help="DroneShare Mission ID to replay. Default is '101'")
21-
parser.add_argument('--api_server', default='https://api.3drobotics.com',
22-
help="API Server. Default is Droneshare API (https://api.3drobotics.com)")
23-
parser.add_argument('--api_key', default='89b511b1.d884d1cb57306e63925fcc07d032f2af',
24-
help="API Server Key (default should be fine!)")
17+
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.')
18+
parser.add_argument('--connect', help="vehicle connection target.")
19+
parser.add_argument('--tlog', default='flight.tlog',
20+
help="Telemetry log containing path to replay")
2521
args = parser.parse_args()
2622

2723

28-
# Connect to the Vehicle
29-
print 'Connecting to vehicle on: %s' % args.connect
30-
vehicle = connect(args.connect, wait_ready=True)
31-
32-
33-
34-
def _decode_list(data):
35-
"""A utility function for decoding JSON strings from unicode"""
36-
rv = []
37-
for item in data:
38-
if isinstance(item, unicode):
39-
item = item.encode('utf-8')
40-
elif isinstance(item, list):
41-
item = _decode_list(item)
42-
elif isinstance(item, dict):
43-
item = _decode_dict(item)
44-
rv.append(item)
45-
return rv
46-
47-
def _decode_dict(data):
48-
"""A utility function for decoding JSON strings from Unicode"""
49-
rv = {}
50-
for key, value in data.iteritems():
51-
if isinstance(key, unicode):
52-
key = key.encode('utf-8')
53-
if isinstance(value, unicode):
54-
value = value.encode('utf-8')
55-
elif isinstance(value, list):
56-
value = _decode_list(value)
57-
elif isinstance(value, dict):
58-
value = _decode_dict(value)
59-
rv[key] = value
60-
return rv
61-
62-
def download_messages(mission_id, max_freq = 1.0):
63-
"""Download a public mission from droneshare (as JSON)"""
64-
message_url="%s/api/v1/mission/%s/messages.json?max_freq=%s&api_key=%s" % (args.api_server, mission_id, max_freq, args.api_key)
65-
f = urllib.urlopen(message_url)
66-
j = json.load(f, object_hook=_decode_dict)
67-
f.close()
68-
return j
69-
70-
def replay_mission(payload):
71-
"""Given mission JSON, set a series of wpts approximating the previous flight"""
24+
def start_default_sitl(lat=None, lon=None):
25+
print "Starting copter simulator (SITL)"
26+
from dronekit_sitl import SITL
27+
sitl = SITL()
28+
sitl.download('copter', '3.3', verbose=True)
29+
if ((lat is not None and lon is None) or
30+
(lat is None and lon is not None)):
31+
print("Supply both lat and lon, or neither")
32+
exit(1)
33+
sitl_args = ['-I0', '--model', 'quad', ]
34+
if lat is not None:
35+
sitl_args.append('--home=%f,%f,584,353' % (lat,lon,))
36+
sitl.launch(sitl_args, await_ready=True, restart=True)
37+
connection_string='tcp:127.0.0.1:5760'
38+
return (sitl, connection_string)
39+
40+
def get_distance_metres(aLocation1, aLocation2):
41+
"""
42+
Returns the ground distance in metres between two LocationGlobal objects.
43+
44+
This method is an approximation, and will not be accurate over large distances and close to the
45+
earth's poles. It comes from the ArduPilot test code:
46+
https://github.com/diydrones/ardupilot/blob/master/Tools/autotest/common.py
47+
"""
48+
dlat = aLocation2.lat - aLocation1.lat
49+
dlong = aLocation2.lon - aLocation1.lon
50+
return math.sqrt((dlat*dlat) + (dlong*dlong)) * 1.113195e5
51+
52+
53+
54+
def distance_to_current_waypoint():
55+
"""
56+
Gets distance in metres to the current waypoint.
57+
It returns None for the first waypoint (Home location).
58+
"""
59+
nextwaypoint = vehicle.commands.next
60+
if nextwaypoint==0:
61+
return None
62+
missionitem=vehicle.commands[nextwaypoint-1] #commands are zero indexed
63+
lat = missionitem.x
64+
lon = missionitem.y
65+
alt = missionitem.z
66+
targetWaypointLocation = LocationGlobalRelative(lat,lon,alt)
67+
distancetopoint = get_distance_metres(vehicle.location.global_frame, targetWaypointLocation)
68+
return distancetopoint
69+
70+
def position_messages_from_tlog(filename):
71+
"""Given telemetry log, get a series of wpts approximating the previous flight"""
7272
# Pull out just the global position msgs
73-
try:
74-
messages = payload['messages']
75-
except:
76-
print "Exception: payload from site is: %s" % payload
77-
sys.exit()
78-
messages = filter(lambda obj: obj['typ'] == 'MAVLINK_MSG_ID_GLOBAL_POSITION_INT', messages)
79-
messages = map(lambda obj: obj['fld'], messages)
80-
81-
# Shrink the # of pts to be lower than the max # of wpts allowed by vehicle
73+
messages = []
74+
mlog = mavutil.mavlink_connection(filename)
75+
while True:
76+
try:
77+
m = mlog.recv_match(type=['GLOBAL_POSITION_INT'])
78+
if m is None:
79+
break
80+
except Exception:
81+
break
82+
# ignore we get where there is no fix:
83+
if m.lat == 0:
84+
continue
85+
messages.append(m)
86+
87+
# Shrink the # of pts to be lower than the max # of wpts allowed by vehicle
8288
num_points = len(messages)
8389
max_points = 99
8490
if num_points > max_points:
8591
step = int(math.ceil((float(num_points) / max_points)))
8692
shorter = [messages[i] for i in xrange(0, num_points, step)]
8793
messages = shorter
94+
return messages
95+
96+
print("Generating waypoints from tlog...")
97+
messages = position_messages_from_tlog(args.tlog)
98+
print "Generated %d waypoints from tlog" % len(messages)
99+
if len(messages) == 0:
100+
print("No position messages found in log")
101+
exit(0)
102+
103+
#Start SITL if no connection string specified
104+
if args.connect:
105+
connection_string = args.connect
106+
sitl = None
107+
else:
108+
start_lat = messages[0].lat/1.0e7
109+
start_lon = messages[0].lon/1.0e7
88110

89-
print "Generating %s waypoints from replay..." % len(messages)
90-
cmds = vehicle.commands
91-
cmds.clear()
92-
for i in xrange(0, len(messages)):
93-
pt = messages[i]
94-
lat = pt['lat']
95-
lon = pt['lon']
96-
# To prevent accidents we don't trust the altitude in the original flight, instead
97-
# we just put in a conservative cruising altitude.
98-
altitude = 30.0 # pt['alt']
99-
cmd = Command( 0,
100-
0,
101-
0,
102-
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
103-
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
104-
0, 0, 0, 0, 0, 0,
105-
lat, lon, altitude)
106-
cmds.add(cmd)
107-
#Upload clear message and command messages to vehicle.
108-
cmds.upload()
111+
(sitl, connection_string) = start_default_sitl(lat=start_lat,lon=start_lon)
112+
113+
# Connect to the Vehicle
114+
print 'Connecting to vehicle on: %s' % connection_string
115+
vehicle = connect(connection_string, wait_ready=True)
109116

110117

111118
# Now download the vehicle waypoints
112119
cmds = vehicle.commands
113120
cmds.wait_ready()
114-
mission_id = int(args.mission_id)
115-
max_freq = 0.1
116-
json = download_messages(mission_id, max_freq)
117-
print "JSON downloaded..."
118-
replay_mission(json)
119121

120122

121-
#Here you could actually replay the mission by
122-
#taking off and changing to AUTO mode.
123-
123+
cmds = vehicle.commands
124+
cmds.clear()
125+
for i in xrange(0, len(messages)):
126+
pt = messages[i]
127+
print "Point: %d %d" % (pt.lat, pt.lon,)
128+
lat = pt.lat
129+
lon = pt.lon
130+
# To prevent accidents we don't trust the altitude in the original flight, instead
131+
# we just put in a conservative cruising altitude.
132+
altitude = 30.0
133+
cmd = Command( 0,
134+
0,
135+
0,
136+
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
137+
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
138+
0, 0, 0, 0, 0, 0,
139+
lat/1.0e7, lon/1.0e7, altitude)
140+
cmds.add(cmd)
141+
142+
#Upload clear message and command messages to vehicle.
143+
print("Uploading %d waypoints to vehicle..." % len(messages))
144+
cmds.upload()
145+
146+
# Set mode to STABILISE for arming and takeoff:
147+
while (vehicle.mode.name != "GUIDED"):
148+
vehicle.mode = VehicleMode("GUIDED")
149+
time.sleep(0.1)
150+
151+
while not vehicle.armed:
152+
print("Arming vehicle")
153+
vehicle.armed = True
154+
print "Waiting for arming..."
155+
time.sleep(1)
156+
157+
print("Taking off")
158+
aTargetAltitude = 30.0
159+
vehicle.simple_takeoff(aTargetAltitude)
160+
161+
# Wait until the vehicle reaches a safe height
162+
while True:
163+
requiredAlt = aTargetAltitude*0.95
164+
#Break and return from function just below target altitude.
165+
if vehicle.location.global_relative_frame.alt>=requiredAlt:
166+
print "Reached target altitude of ~%f" % (aTargetAltitude)
167+
break
168+
print " Altitude: %f < %f" % (vehicle.location.global_relative_frame.alt,
169+
requiredAlt)
170+
time.sleep(1)
171+
172+
print "Starting mission"
173+
174+
# Reset mission set to first (0) waypoint
175+
vehicle.commands.next=0
176+
177+
# Set mode to AUTO to start mission:
178+
while (vehicle.mode.name != "AUTO"):
179+
vehicle.mode = VehicleMode("AUTO")
180+
time.sleep(0.1)
181+
182+
# Monitor mission for 60 seconds then RTL and quit:
183+
time_start = time.time()
184+
while time.time() - time_start < 60:
185+
nextwaypoint=vehicle.commands.next
186+
print 'Distance to waypoint (%s): %s' % (nextwaypoint, distance_to_current_waypoint())
187+
188+
if nextwaypoint==len(messages):
189+
print "Exit 'standard' mission when start heading to final waypoint"
190+
break;
191+
time.sleep(1)
192+
193+
print 'Return to launch'
194+
while (vehicle.mode.name != "RTL"):
195+
vehicle.mode = VehicleMode("RTL")
196+
time.sleep(0.1)
124197

125198
#Close vehicle object before exiting script
126199
print "Close vehicle object"
127200
vehicle.close()
201+
202+
# Shut down simulator if it was started.
203+
if sitl is not None:
204+
sitl.stop()
205+
128206
print("Completed...")

0 commit comments

Comments
 (0)