def post(self, *args, **kwargs): """ POST request """ data = tornado.escape.json_decode(self.request.body) log_id = data['log'] if not validate_log_id(log_id): raise tornado.web.HTTPError(400, 'Invalid Parameter') error_ids = data['labels'] if not validate_error_ids(error_ids): raise tornado.web.HTTPError(400, 'Invalid Parameter') error_id_str = "" for error_ix, error_id in enumerate(error_ids): error_id_str += str(error_id) if error_ix < len(error_ids) - 1: error_id_str += "," con = sqlite3.connect(get_db_filename(), detect_types=sqlite3.PARSE_DECLTYPES) cur = con.cursor() cur.execute('UPDATE Logs SET ErrorLabels = ? WHERE Id = ?', (error_id_str, log_id)) con.commit() cur.close() con.close() self.write('OK')
def get(self): log_id = self.get_argument('log') if not validate_log_id(log_id): raise tornado.web.HTTPError(400, 'Invalid Parameter') log_file_name = get_log_filename(log_id) download_type = self.get_argument('type', default='0') if not os.path.exists(log_file_name): raise tornado.web.HTTPError(404, 'Log not found') if download_type == '1': # download the parameters ulog = load_ulog_file(log_file_name) param_keys = sorted(ulog.initial_parameters.keys()) self.set_header("Content-Type", "text/plain") self.set_header('Content-Disposition', 'inline; filename=params.txt') delimiter = ', ' for param_key in param_keys: self.write(param_key) self.write(delimiter) self.write(str(ulog.initial_parameters[param_key])) self.write('\n') elif download_type == '2': # download the kml file kml_path = get_kml_filepath() kml_file_name = os.path.join(kml_path, log_id.replace('/', '.') + '.kml') # check if chached file exists if not os.path.exists(kml_file_name): print('need to create kml file', kml_file_name) def kml_colors(flight_mode): """ flight mode colors for KML file """ if not flight_mode in flight_modes_table: flight_mode = 0 color_str = flight_modes_table[flight_mode][1][ 1:] # color in form 'ff00aa' # increase brightness to match colors with template rgb = [ int(color_str[2 * x:2 * x + 2], 16) for x in range(3) ] for i in range(3): rgb[i] += 40 if rgb[i] > 255: rgb[i] = 255 color_str = "".join(map(lambda x: format(x, '02x'), rgb)) return 'ff' + color_str[4:6] + color_str[2:4] + color_str[ 0:2] # KML uses aabbggrr style = {'line_width': 2} # create in random temporary file, then move it (to avoid races) try: temp_file_name = kml_file_name + '.' + str(uuid.uuid4()) convert_ulog2kml(log_file_name, temp_file_name, 'vehicle_global_position', kml_colors, style=style) shutil.move(temp_file_name, kml_file_name) except: print('Error creating KML file', sys.exc_info()[0], sys.exc_info()[1]) raise CustomHTTPError(400, 'No Position Data in log') # send the whole KML file self.set_header("Content-Type", "application/vnd.google-earth.kml+xml") self.set_header('Content-Disposition', 'attachment; filename=track.kml') with open(kml_file_name, 'rb') as kml_file: while True: data = kml_file.read(4096) if not data: break self.write(data) self.finish() elif download_type == '3': # download the non-default parameters ulog = load_ulog_file(log_file_name) param_keys = sorted(ulog.initial_parameters.keys()) self.set_header("Content-Type", "text/plain") self.set_header('Content-Disposition', 'inline; filename=params.txt') default_params = get_default_parameters() delimiter = ', ' for param_key in param_keys: try: param_value = str(ulog.initial_parameters[param_key]) is_default = False if param_key in default_params: default_param = default_params[param_key] if default_param['type'] == 'FLOAT': is_default = abs( float(default_param['default']) - float(param_value)) < 0.00001 else: is_default = int( default_param['default']) == int(param_value) if not is_default: self.write(param_key) self.write(delimiter) self.write(param_value) self.write('\n') except: pass else: # download the log file self.set_header('Content-Type', 'application/octet-stream') self.set_header("Content-Description", "File Transfer") self.set_header( 'Content-Disposition', 'attachment; filename={}'.format( os.path.basename(log_file_name))) with open(log_file_name, 'rb') as log_file: while True: data = log_file.read(4096) if not data: break self.write(data) self.finish()
def get(self, *args, **kwargs): """ GET request callback """ # load the log file log_id = self.get_argument('log') if not validate_log_id(log_id): raise tornado.web.HTTPError(400, 'Invalid Parameter') log_file_name = get_log_filename(log_id) ulog = load_ulog_file(log_file_name) # extract the necessary information from the log try: # required topics: none of these are optional gps_pos = ulog.get_dataset('vehicle_gps_position').data vehicle_global_position = ulog.get_dataset( 'vehicle_global_position').data attitude = ulog.get_dataset('vehicle_attitude').data except (KeyError, IndexError, ValueError) as error: raise CustomHTTPError( 400, 'The log does not contain all required topics<br />' '(vehicle_gps_position, vehicle_global_position, ' 'vehicle_attitude)') from error # manual control setpoint is optional manual_control_setpoint = None try: manual_control_setpoint = ulog.get_dataset( 'manual_control_setpoint').data except (KeyError, IndexError, ValueError) as error: pass # Get the takeoff location. We use the first position with a valid fix, # and assume that the vehicle is not in the air already at that point takeoff_index = 0 gps_indices = np.nonzero(gps_pos['fix_type'] > 2) if len(gps_indices[0]) > 0: takeoff_index = gps_indices[0][0] takeoff_altitude = '{:.3f}' \ .format(gps_pos['alt'][takeoff_index] * 1.e-3) takeoff_latitude = '{:.10f}'.format(gps_pos['lat'][takeoff_index] * 1.e-7) takeoff_longitude = '{:.10f}'.format(gps_pos['lon'][takeoff_index] * 1.e-7) # calculate UTC time offset (assume there's no drift over the entire log) utc_offset = int(gps_pos['time_utc_usec'][takeoff_index]) - \ int(gps_pos['timestamp'][takeoff_index]) # flight modes flight_mode_changes = get_flight_mode_changes(ulog) flight_modes_str = '[ ' for t, mode in flight_mode_changes: t += utc_offset utctimestamp = datetime.datetime.utcfromtimestamp( t / 1.e6).replace(tzinfo=datetime.timezone.utc) if mode in flight_modes_table: mode_name, color = flight_modes_table[mode] else: mode_name = '' color = '#ffffff' flight_modes_str += '["{:}", "{:}"], ' \ .format(utctimestamp.isoformat(), mode_name) flight_modes_str += ' ]' # manual control setpoints (stick input) manual_control_setpoints_str = '[ ' if manual_control_setpoint: for i in range(len(manual_control_setpoint['timestamp'])): manual_x = manual_control_setpoint['x'][i] manual_y = manual_control_setpoint['y'][i] manual_z = manual_control_setpoint['z'][i] manual_r = manual_control_setpoint['r'][i] t = manual_control_setpoint['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp( t / 1.e6).replace(tzinfo=datetime.timezone.utc) manual_control_setpoints_str += '["{:}", {:.3f}, {:.3f}, {:.3f}, {:.3f}], ' \ .format(utctimestamp.isoformat(), manual_x, manual_y, manual_z, manual_r) manual_control_setpoints_str += ' ]' # position # Note: alt_ellipsoid from gps_pos would be the better match for # altitude, but it's not always available. And since we add an offset # (to match the takeoff location with the ground altitude) it does not # matter as much. position_data = '[ ' # TODO: use vehicle_global_position? If so, then: # - altitude requires an offset (to match the GPS data) # - it's worse for some logs where the estimation is bad -> acro flights # (-> add both: user-selectable between GPS & estimated trajectory?) for i in range(len(gps_pos['timestamp'])): lon = gps_pos['lon'][i] * 1.e-7 lat = gps_pos['lat'][i] * 1.e-7 alt = gps_pos['alt'][i] * 1.e-3 t = gps_pos['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp( t / 1.e6).replace(tzinfo=datetime.timezone.utc) if i == 0: start_timestamp = utctimestamp end_timestamp = utctimestamp position_data += '["{:}", {:.10f}, {:.10f}, {:.3f}], ' \ .format(utctimestamp.isoformat(), lon, lat, alt) position_data += ' ]' start_timestamp_str = '"{:}"'.format(start_timestamp.isoformat()) boot_timestamp = datetime.datetime.utcfromtimestamp( utc_offset / 1.e6).replace(tzinfo=datetime.timezone.utc) boot_timestamp_str = '"{:}"'.format(boot_timestamp.isoformat()) end_timestamp_str = '"{:}"'.format(end_timestamp.isoformat()) # orientation as quaternion attitude_data = '[ ' for i in range(len(attitude['timestamp'])): att_qw = attitude['q[0]'][i] att_qx = attitude['q[1]'][i] att_qy = attitude['q[2]'][i] att_qz = attitude['q[3]'][i] t = attitude['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp( t / 1.e6).replace(tzinfo=datetime.timezone.utc) # Cesium uses (x, y, z, w) attitude_data += '["{:}", {:.6f}, {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), att_qx, att_qy, att_qz, att_qw) attitude_data += ' ]' # handle different vehicle types # the model_scale_factor should scale the different models to make them # equal in size (in proportion) mav_type = ulog.initial_parameters.get('MAV_TYPE', None) if mav_type == 1: # fixed wing model_scale_factor = 0.06 model_uri = 'plot_app/static/cesium/SampleData/models/CesiumAir/Cesium_Air.glb' elif mav_type == 2: # quad model_scale_factor = 1 model_uri = 'plot_app/static/cesium/models/iris/iris.glb' elif mav_type == 22: # delta-quad # TODO: use the delta-quad model model_scale_factor = 0.06 model_uri = 'plot_app/static/cesium/SampleData/models/CesiumAir/Cesium_Air.glb' else: # TODO: handle more types model_scale_factor = 1 model_uri = 'plot_app/static/cesium/models/iris/iris.glb' template = get_jinja_env().get_template(THREED_TEMPLATE) self.write( template.render( flight_modes=flight_modes_str, manual_control_setpoints=manual_control_setpoints_str, takeoff_altitude=takeoff_altitude, takeoff_longitude=takeoff_longitude, takeoff_latitude=takeoff_latitude, position_data=position_data, start_timestamp=start_timestamp_str, boot_timestamp=boot_timestamp_str, end_timestamp=end_timestamp_str, attitude_data=attitude_data, model_scale_factor=model_scale_factor, model_uri=model_uri, log_id=log_id, bing_api_key=get_bing_maps_api_key(), cesium_api_key=get_cesium_api_key()))
def get(self, *args, **kwargs): """ GET request callback """ log_id = self.get_argument('log') if not validate_log_id(log_id): raise tornado.web.HTTPError(400, 'Invalid Parameter') log_file_name = get_log_filename(log_id) download_type = self.get_argument('type', default='0') if not os.path.exists(log_file_name): raise tornado.web.HTTPError(404, 'Log not found') def get_original_filename(default_value, new_file_suffix): """ get the uploaded file name & exchange the file extension """ try: con = sqlite3.connect(get_db_filename(), detect_types=sqlite3.PARSE_DECLTYPES) cur = con.cursor() cur.execute('select OriginalFilename ' 'from Logs where Id = ?', [log_id]) db_tuple = cur.fetchone() if db_tuple is not None: original_file_name = escape(db_tuple[0]) if original_file_name[-4:].lower() == '.ulg': original_file_name = original_file_name[:-4] return original_file_name + new_file_suffix cur.close() con.close() except: print("DB access failed:", sys.exc_info()[0], sys.exc_info()[1]) return default_value if download_type == '1': # download the parameters ulog = load_ulog_file(log_file_name) param_keys = sorted(ulog.initial_parameters.keys()) self.set_header("Content-Type", "text/plain") self.set_header('Content-Disposition', 'inline; filename=params.txt') delimiter = ', ' for param_key in param_keys: self.write(param_key) self.write(delimiter) self.write(str(ulog.initial_parameters[param_key])) self.write('\n') elif download_type == '2': # download the kml file kml_path = get_kml_filepath() kml_file_name = os.path.join(kml_path, log_id.replace('/', '.')+'.kml') # check if chached file exists if not os.path.exists(kml_file_name): print('need to create kml file', kml_file_name) def kml_colors(flight_mode): """ flight mode colors for KML file """ if not flight_mode in flight_modes_table: flight_mode = 0 color_str = flight_modes_table[flight_mode][1][1:] # color in form 'ff00aa' # increase brightness to match colors with template rgb = [int(color_str[2*x:2*x+2], 16) for x in range(3)] for i in range(3): rgb[i] += 40 if rgb[i] > 255: rgb[i] = 255 color_str = "".join(map(lambda x: format(x, '02x'), rgb)) return 'ff'+color_str[4:6]+color_str[2:4]+color_str[0:2] # KML uses aabbggrr style = {'line_width': 2} # create in random temporary file, then move it (to avoid races) try: temp_file_name = kml_file_name+'.'+str(uuid.uuid4()) convert_ulog2kml(log_file_name, temp_file_name, 'vehicle_global_position', kml_colors, style=style, camera_trigger_topic_name='camera_capture') shutil.move(temp_file_name, kml_file_name) except Exception as e: print('Error creating KML file', sys.exc_info()[0], sys.exc_info()[1]) raise CustomHTTPError(400, 'No Position Data in log') from e kml_dl_file_name = get_original_filename('track.kml', '.kml') # send the whole KML file self.set_header("Content-Type", "application/vnd.google-earth.kml+xml") self.set_header('Content-Disposition', 'attachment; filename='+kml_dl_file_name) with open(kml_file_name, 'rb') as kml_file: while True: data = kml_file.read(4096) if not data: break self.write(data) self.finish() elif download_type == '3': # download the non-default parameters ulog = load_ulog_file(log_file_name) param_keys = sorted(ulog.initial_parameters.keys()) self.set_header("Content-Type", "text/plain") self.set_header('Content-Disposition', 'inline; filename=params.txt') default_params = get_default_parameters() delimiter = ', ' for param_key in param_keys: try: param_value = str(ulog.initial_parameters[param_key]) is_default = False if param_key in default_params: default_param = default_params[param_key] if default_param['type'] == 'FLOAT': is_default = abs(float(default_param['default']) - float(param_value)) < 0.00001 else: is_default = int(default_param['default']) == int(param_value) if not is_default: self.write(param_key) self.write(delimiter) self.write(param_value) self.write('\n') except: pass else: # download the log file self.set_header('Content-Type', 'application/octet-stream') self.set_header("Content-Description", "File Transfer") self.set_header('Content-Disposition', 'attachment; filename={}'.format( os.path.basename(log_file_name))) with open(log_file_name, 'rb') as log_file: while True: data = log_file.read(4096) if not data: break self.write(data) self.finish()
def get(self, *args, **kwargs): """ GET request callback """ # load the log file log_id = self.get_argument('log') if not validate_log_id(log_id): raise tornado.web.HTTPError(400, 'Invalid Parameter') log_file_name = get_log_filename(log_id) ulog = load_ulog_file(log_file_name) # extract the necessary information from the log try: # required topics: none of these are optional gps_pos = ulog.get_dataset('vehicle_gps_position').data vehicle_global_position = ulog.get_dataset('vehicle_global_position').data attitude = ulog.get_dataset('vehicle_attitude').data except (KeyError, IndexError, ValueError) as error: raise CustomHTTPError( 400, 'The log does not contain all required topics<br />' '(vehicle_gps_position, vehicle_global_position, ' 'vehicle_attitude)') # These are optional data streams. Most of them are added # for charting/streaming 2D plots on the 3D viewer. manual_control_setpoint = None vehicle_local_position = None vehicle_local_position_setpoint = None vehicle_attitude_setpoint = None vehicle_rates_setpoint = None actuator_outputs = None sensor_combined = None actuator_controls_0 = None # Exception handling is done on each dataset separately to find # source of the missing stream. # Exception: manual_control_setpoint try: manual_control_setpoint = ulog.get_dataset('manual_control_setpoint').data except (KeyError, IndexError, ValueError) as error: print("Manual control setpoint not found") # Exception: vehicle_local_position try: vehicle_local_position = ulog.get_dataset('vehicle_local_position').data except (KeyError, IndexError, ValueError) as error: print("Vehicle local position not found") # Exception: vehicle_local_position_setpoint try: vehicle_local_position_setpoint = ulog.get_dataset('vehicle_local_position_setpoint').data except (KeyError, IndexError, ValueError) as error: print("Vehicle local position setpoint not found") # Exception: vehicle_attitude_setpoint try: vehicle_attitude_setpoint = ulog.get_dataset('vehicle_attitude_setpoint').data except (KeyError, IndexError, ValueError) as error: print("Vehicle attitude setpoint not found") # Exception: vehicle_rates_setpoint try: vehicle_rates_setpoint = ulog.get_dataset('vehicle_rates_setpoint').data except (KeyError, IndexError, ValueError) as error: print("Vehicle rates setpoint not found") # Exception: actuator_outputs try: actuator_outputs = ulog.get_dataset('actuator_outputs').data except (KeyError, IndexError, ValueError) as error: print("Actuator output not found") # Exception: sensor_combined try: sensor_combined = ulog.get_dataset('sensor_combined').data except (KeyError, IndexError, ValueError) as error: print("Sensor combined not found") # Exception: actuator_controls_0 try: actuator_controls_0 = ulog.get_dataset('actuator_controls_0').data except (KeyError, IndexError, ValueError) as error: print("Actuator Controls 0 not found") # Get the takeoff location. We use the first position with a valid fix, # and assume that the vehicle is not in the air already at that point takeoff_index = 0 gps_indices = np.nonzero(gps_pos['fix_type'] > 2) if len(gps_indices[0]) > 0: takeoff_index = gps_indices[0][0] takeoff_altitude = '{:.3f}' \ .format(gps_pos['alt'][takeoff_index] * 1.e-3) takeoff_latitude = '{:.10f}'.format(gps_pos['lat'][takeoff_index] * 1.e-7) takeoff_longitude = '{:.10f}'.format(gps_pos['lon'][takeoff_index] * 1.e-7) # calculate UTC time offset (assume there's no drift over the entire log) utc_offset = int(gps_pos['time_utc_usec'][takeoff_index]) - \ int(gps_pos['timestamp'][takeoff_index]) # flight modes flight_mode_changes = get_flight_mode_changes(ulog) flight_modes_str = '[ ' for t, mode in flight_mode_changes: t += utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) if mode in flight_modes_table: mode_name, color = flight_modes_table[mode] else: mode_name = '' color = '#ffffff' flight_modes_str += '["{:}", "{:}"], ' \ .format(utctimestamp.isoformat(), mode_name) flight_modes_str += ' ]' # manual control setpoints (stick input) manual_control_setpoints_str = '[ ' if manual_control_setpoint: for i in range(len(manual_control_setpoint['timestamp'])): manual_x = manual_control_setpoint['x'][i] manual_y = manual_control_setpoint['y'][i] manual_z = manual_control_setpoint['z'][i] manual_r = manual_control_setpoint['r'][i] t = manual_control_setpoint['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) manual_control_setpoints_str += '["{:}", {:.3f}, {:.3f}, {:.3f}, {:.3f}], ' \ .format(utctimestamp.isoformat(), manual_x, manual_y, manual_z, manual_r) manual_control_setpoints_str += ' ]' # position # Note: alt_ellipsoid from gps_pos would be the better match for # altitude, but it's not always available. And since we add an offset # (to match the takeoff location with the ground altitude) it does not # matter as much. position_data = '[ ' # TODO: use vehicle_global_position? If so, then: # - altitude requires an offset (to match the GPS data) # - it's worse for some logs where the estimation is bad -> acro flights # (-> add both: user-selectable between GPS & estimated trajectory?) for i in range(len(gps_pos['timestamp'])): lon = gps_pos['lon'][i] * 1.e-7 lat = gps_pos['lat'][i] * 1.e-7 alt = gps_pos['alt'][i] * 1.e-3 t = gps_pos['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) if i == 0: start_timestamp = utctimestamp end_timestamp = utctimestamp position_data += '["{:}", {:.10f}, {:.10f}, {:.3f}], ' \ .format(utctimestamp.isoformat(), lon, lat, alt) position_data += ' ]' start_timestamp_str = '"{:}"'.format(start_timestamp.isoformat()) boot_timestamp = datetime.datetime.utcfromtimestamp(utc_offset/1.e6).replace( tzinfo=datetime.timezone.utc) boot_timestamp_str = '"{:}"'.format(boot_timestamp.isoformat()) end_timestamp_str = '"{:}"'.format(end_timestamp.isoformat()) # orientation as quaternion attitude_data = '[ ' for i in range(len(attitude['timestamp'])): att_qw = attitude['q[0]'][i] att_qx = attitude['q[1]'][i] att_qy = attitude['q[2]'][i] att_qz = attitude['q[3]'][i] rollSpeed = attitude['rollspeed'][i] pitchSpeed = attitude['pitchspeed'][i] yawSpeed = attitude['yawspeed'][i] t = attitude['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) # Cesium uses (x, y, z, w) attitude_data += '["{:}", {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), att_qx, att_qy, att_qz, att_qw, rollSpeed, pitchSpeed, yawSpeed) attitude_data += ' ]' # Optional data stream serialization starts here. # The code checks None condition to decide whether # to serialize or not. # Attitude setpoint data vehicle_rates_setpoint_data = '[ ' if vehicle_rates_setpoint is not None: for i in range(len(vehicle_rates_setpoint['timestamp'])): rollRateSP = vehicle_rates_setpoint['roll'][i] pitchRateSP = vehicle_rates_setpoint['pitch'][i] yawRateSp = vehicle_rates_setpoint['yaw'][i] t = vehicle_rates_setpoint['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) vehicle_rates_setpoint_data += '["{:}", {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), rollRateSP, pitchRateSP, yawRateSp) vehicle_rates_setpoint_data += ' ]' # Sensor combined data. Includes things like raw gyro, raw accelleration. sensor_combined_data = '[ ' if sensor_combined is not None: for i in range(len(sensor_combined['timestamp'])): rawRoll = sensor_combined['gyro_rad[0]'][i] rawPitch = sensor_combined['gyro_rad[1]'][i] rawYaw = sensor_combined['gyro_rad[2]'][i] rawXAcc = sensor_combined['accelerometer_m_s2[0]'][i] rawYAcc = sensor_combined['accelerometer_m_s2[1]'][i] rawZAcc = sensor_combined['accelerometer_m_s2[2]'][i] t = sensor_combined['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) sensor_combined_data += '["{:}", {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), rawRoll, rawPitch, rawYaw, rawXAcc, rawYAcc, rawZAcc) sensor_combined_data += ' ]' # Attitude setpoint vehicle_attitude_setpoint_data = '[ ' if vehicle_attitude_setpoint is not None: for i in range(len(vehicle_attitude_setpoint['timestamp'])): rollSP = vehicle_attitude_setpoint['roll_body'][i] pitchSP = vehicle_attitude_setpoint['pitch_body'][i] yawSP = vehicle_attitude_setpoint['yaw_body'][i] t = vehicle_attitude_setpoint['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) vehicle_attitude_setpoint_data += '["{:}", {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), rollSP, pitchSP, yawSP) vehicle_attitude_setpoint_data += ' ]' # Local Position vehicle_local_position_data = '[ ' if vehicle_local_position is not None: for i in range(len(vehicle_local_position['timestamp'])): xPos = vehicle_local_position['x'][i] yPos = vehicle_local_position['y'][i] zPos = vehicle_local_position['z'][i] xVel = vehicle_local_position['vx'][i] yVel = vehicle_local_position['vy'][i] zVel = vehicle_local_position['vz'][i] t = vehicle_local_position['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) vehicle_local_position_data += '["{:}", {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), xPos, yPos, zPos, xVel, yVel, zVel) vehicle_local_position_data += ' ]' # Local Position Setpoint vehicle_local_position_setpoint_data = '[ ' if vehicle_local_position_setpoint is not None: for i in range(len(vehicle_local_position_setpoint['timestamp'])): xPosSP = vehicle_local_position_setpoint['x'][i] yPosSP = vehicle_local_position_setpoint['y'][i] zPosSP = vehicle_local_position_setpoint['z'][i] xVelSP = vehicle_local_position_setpoint['vx'][i] yVelSP = vehicle_local_position_setpoint['vy'][i] zVelSP = vehicle_local_position_setpoint['vz'][i] t = vehicle_local_position_setpoint['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) vehicle_local_position_setpoint_data += '["{:}", {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), xPosSP, yPosSP, zPosSP, xVelSP, yVelSP, zVelSP) vehicle_local_position_setpoint_data += ' ]' # Actuator Outputs. This can handle airframes up to 8 actuation outputs (i.e. motors). # Tons of formatting things ... actuator_outputs_data = '[ ' if actuator_outputs is not None: num_actuator_outputs = 8 max_outputs = np.amax(actuator_outputs['noutputs']) if max_outputs < num_actuator_outputs: num_actuator_outputs = max_outputs for i in range(len(actuator_outputs['timestamp'])): t = actuator_outputs['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) actuatorList = [] actuatorList.append(utctimestamp.isoformat()) actuatorDictionary={} formatStringLoop = '' for x in range(max_outputs): actuatorDictionary["actuator_outputs_{0}".format(x)]=actuator_outputs['output['+str(x)+']'][i] formatStringLoop += ', {:.6f}' actuatorList.append(actuatorDictionary["actuator_outputs_{0}".format(x)]) formatStringLoop += '], ' formatString = '["{:}"' + formatStringLoop actuator_outputs_data += formatString.format(*actuatorList) actuator_outputs_data += ' ]' # Actuator controls actuator_controls_0_data = '[ ' if actuator_controls_0 is not None: for i in range(len(actuator_controls_0['timestamp'])): cont0 = actuator_controls_0['control[0]'][i] cont1 = actuator_controls_0['control[1]'][i] cont2 = actuator_controls_0['control[2]'][i] cont3 = actuator_controls_0['control[3]'][i] t = actuator_controls_0['timestamp'][i] + utc_offset utctimestamp = datetime.datetime.utcfromtimestamp(t/1.e6).replace( tzinfo=datetime.timezone.utc) actuator_controls_0_data += '["{:}", {:.6f}, {:.6f}, {:.6f}, {:.6f}], ' \ .format(utctimestamp.isoformat(), cont0, cont1, cont2, cont3) actuator_controls_0_data += ' ]' # handle different vehicle types # the model_scale_factor should scale the different models to make them # equal in size (in proportion) mav_type = ulog.initial_parameters.get('MAV_TYPE', None) if mav_type == 1: # fixed wing model_scale_factor = 0.06 model_uri = 'plot_app/static/cesium/SampleData/models/CesiumAir/Cesium_Air.glb' elif mav_type == 2: # quad model_scale_factor = 1 model_uri = 'plot_app/static/cesium/models/iris/iris.glb' elif mav_type == 22: # delta-quad # TODO: use the delta-quad model model_scale_factor = 0.06 model_uri = 'plot_app/static/cesium/SampleData/models/CesiumAir/Cesium_Air.glb' else: # TODO: handle more types model_scale_factor = 1 model_uri = 'plot_app/static/cesium/models/iris/iris.glb' template = get_jinja_env().get_template(THREED_TEMPLATE) self.write(template.render( flight_modes=flight_modes_str, manual_control_setpoints=manual_control_setpoints_str, takeoff_altitude=takeoff_altitude, takeoff_longitude=takeoff_longitude, takeoff_latitude=takeoff_latitude, position_data=position_data, start_timestamp=start_timestamp_str, boot_timestamp=boot_timestamp_str, end_timestamp=end_timestamp_str, attitude_data=attitude_data, vehicle_attitude_setpoint_data = vehicle_attitude_setpoint_data, vehicle_local_position_data = vehicle_local_position_data, vehicle_local_position_setpoint_data = vehicle_local_position_setpoint_data, actuator_outputs_data = actuator_outputs_data, vehicle_rates_setpoint_data = vehicle_rates_setpoint_data, sensor_combined_data = sensor_combined_data, actuator_controls_0_data = actuator_controls_0_data, model_scale_factor=model_scale_factor, model_uri=model_uri, log_id=log_id, bing_api_key=get_bing_maps_api_key(), cesium_api_key=get_cesium_api_key()))