|
7 | 7 | Full documentation is provided at http://python.dronekit.io/examples/flight_replay.html |
8 | 8 | """ |
9 | 9 |
|
10 | | -from dronekit import connect, Command |
| 10 | +from dronekit import connect, Command, VehicleMode, LocationGlobalRelative |
11 | 11 | from pymavlink import mavutil |
12 | 12 | import json, urllib, math |
| 13 | +import time |
13 | 14 |
|
14 | 15 | #Set up option parsing to get connection string |
15 | 16 | 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") |
25 | 21 | args = parser.parse_args() |
26 | 22 |
|
27 | 23 |
|
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""" |
72 | 72 | # 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 |
82 | 88 | num_points = len(messages) |
83 | 89 | max_points = 99 |
84 | 90 | if num_points > max_points: |
85 | 91 | step = int(math.ceil((float(num_points) / max_points))) |
86 | 92 | shorter = [messages[i] for i in xrange(0, num_points, step)] |
87 | 93 | 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 |
88 | 110 |
|
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) |
109 | 116 |
|
110 | 117 |
|
111 | 118 | # Now download the vehicle waypoints |
112 | 119 | cmds = vehicle.commands |
113 | 120 | 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) |
119 | 121 |
|
120 | 122 |
|
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) |
124 | 197 |
|
125 | 198 | #Close vehicle object before exiting script |
126 | 199 | print "Close vehicle object" |
127 | 200 | vehicle.close() |
| 201 | + |
| 202 | +# Shut down simulator if it was started. |
| 203 | +if sitl is not None: |
| 204 | + sitl.stop() |
| 205 | + |
128 | 206 | print("Completed...") |
0 commit comments