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_pid_analysis_plots(ulog, px4_ulog, db_data, link_to_main_plots): """ get all bokeh plots shown on the PID analysis page :return: list of bokeh plots """ def _resample(time_array, data, desired_time): """ resample data at a given time to a vector of desired_time """ data_f = interp1d(time_array, data, fill_value='extrapolate') return data_f(desired_time) page_intro = """ <p> This page shows step response plots for the PID controller. The step response is an objective measure to evaluate the performance of a PID controller, i.e. if the tuning gains are appropriate. In particular, the following metrics can be read from the plots: response time, overshoot and settling time. </p> <p> The step response plots are based on <a href="https://github.com/Plasmatree/PID-Analyzer"> PID-Analyzer</a>, originally written for Betaflight by Florian Melsheimer. Documentation with some examples can be found <a href="https://github.com/Plasmatree/PID-Analyzer/wiki/Influence-of-parameters">here</a>. </p> <p> The analysis may take a while... </p> """ curdoc().template_variables['title_html'] = get_heading_html( ulog, px4_ulog, db_data, None, [('Open Main Plots', link_to_main_plots)], 'PID Analysis') + page_intro plots = [] data = ulog.data_list flight_mode_changes = get_flight_mode_changes(ulog) x_range_offset = (ulog.last_timestamp - ulog.start_timestamp) * 0.05 x_range = Range1d(ulog.start_timestamp - x_range_offset, ulog.last_timestamp + x_range_offset) # COMPATIBILITY support for old logs if any(elem.name == 'vehicle_angular_velocity' for elem in data): rate_topic_name = 'vehicle_angular_velocity' rate_field_names = ['xyz[0]', 'xyz[1]', 'xyz[2]'] else: # old rate_topic_name = 'rate_ctrl_status' rate_field_names = ['rollspeed', 'pitchspeed', 'yawspeed'] # required PID response data pid_analysis_error = False try: # Rate rate_data = ulog.get_dataset(rate_topic_name) gyro_time = rate_data.data['timestamp'] vehicle_rates_setpoint = ulog.get_dataset('vehicle_rates_setpoint') actuator_controls_0 = ulog.get_dataset('actuator_controls_0') throttle = _resample(actuator_controls_0.data['timestamp'], actuator_controls_0.data['control[3]'] * 100, gyro_time) time_seconds = gyro_time / 1e6 except (KeyError, IndexError, ValueError) as error: print(type(error), ":", error) pid_analysis_error = True div = Div(text="<p><b>Error</b>: missing topics or data for PID analysis " "(required topics: vehicle_angular_velocity, vehicle_rates_setpoint, " "vehicle_attitude, vehicle_attitude_setpoint and " "actuator_controls_0).</p>", width=int(plot_width*0.9)) plots.append(column(div, width=int(plot_width*0.9))) has_attitude = True try: # Attitude (optional) vehicle_attitude = ulog.get_dataset('vehicle_attitude') attitude_time = vehicle_attitude.data['timestamp'] vehicle_attitude_setpoint = ulog.get_dataset('vehicle_attitude_setpoint') except (KeyError, IndexError, ValueError) as error: print(type(error), ":", error) has_attitude = False for index, axis in enumerate(['roll', 'pitch', 'yaw']): axis_name = axis.capitalize() # rate data_plot = DataPlot(data, plot_config, 'actuator_controls_0', y_axis_label='[deg/s]', title=axis_name+' Angular Rate', plot_height='small', x_range=x_range) thrust_max = 200 actuator_controls = data_plot.dataset if actuator_controls is None: # do not show the rate plot if actuator_controls is missing continue time_controls = actuator_controls.data['timestamp'] thrust = actuator_controls.data['control[3]'] * thrust_max # downsample if necessary max_num_data_points = 4.0*plot_config['plot_width'] if len(time_controls) > max_num_data_points: step_size = int(len(time_controls) / max_num_data_points) time_controls = time_controls[::step_size] thrust = thrust[::step_size] if len(time_controls) > 0: # make sure the polygon reaches down to 0 thrust = np.insert(thrust, [0, len(thrust)], [0, 0]) time_controls = np.insert(time_controls, [0, len(time_controls)], [time_controls[0], time_controls[-1]]) p = data_plot.bokeh_plot p.patch(time_controls, thrust, line_width=0, fill_color='#555555', fill_alpha=0.4, alpha=0, legend_label='Thrust [0, {:}]'.format(thrust_max)) data_plot.change_dataset(rate_topic_name) data_plot.add_graph([lambda data: ("rate"+str(index), np.rad2deg(data[rate_field_names[index]]))], colors3[0:1], [axis_name+' Rate Estimated'], mark_nan=True) data_plot.change_dataset('vehicle_rates_setpoint') data_plot.add_graph([lambda data: (axis, np.rad2deg(data[axis]))], colors3[1:2], [axis_name+' Rate Setpoint'], mark_nan=True, use_step_lines=True) axis_letter = axis[0].upper() rate_int_limit = '(*100)' # this param is MC/VTOL only (it will not exist on FW) rate_int_limit_param = 'MC_' + axis_letter + 'R_INT_LIM' if rate_int_limit_param in ulog.initial_parameters: rate_int_limit = '[-{0:.0f}, {0:.0f}]'.format( ulog.initial_parameters[rate_int_limit_param]*100) data_plot.change_dataset('rate_ctrl_status') data_plot.add_graph([lambda data: (axis, data[axis+'speed_integ']*100)], colors3[2:3], [axis_name+' Rate Integral '+rate_int_limit]) plot_flight_modes_background(data_plot, flight_mode_changes) if data_plot.finalize() is not None: plots.append(data_plot.bokeh_plot) # PID response if not pid_analysis_error: try: gyro_rate = np.rad2deg(rate_data.data[rate_field_names[index]]) setpoint = _resample(vehicle_rates_setpoint.data['timestamp'], np.rad2deg(vehicle_rates_setpoint.data[axis]), gyro_time) trace = Trace(axis, time_seconds, gyro_rate, setpoint, throttle) plots.append(plot_pid_response(trace, ulog.data_list, plot_config).bokeh_plot) except Exception as e: print(type(e), axis, ":", e) div = Div(text="<p><b>Error</b>: PID analysis failed. Possible " "error causes are: logged data rate is too low, or there " "is not enough motion for the analysis.</p>", width=int(plot_width*0.9)) plots.insert(0, column(div, width=int(plot_width*0.9))) pid_analysis_error = True # attitude if not pid_analysis_error and has_attitude: throttle = _resample(actuator_controls_0.data['timestamp'], actuator_controls_0.data['control[3]'] * 100, attitude_time) time_seconds = attitude_time / 1e6 # don't plot yaw, as yaw is mostly controlled directly by rate for index, axis in enumerate(['roll', 'pitch']): axis_name = axis.capitalize() # PID response if not pid_analysis_error and has_attitude: try: attitude_estimated = np.rad2deg(vehicle_attitude.data[axis]) setpoint = _resample(vehicle_attitude_setpoint.data['timestamp'], np.rad2deg(vehicle_attitude_setpoint.data[axis+'_d']), attitude_time) trace = Trace(axis, time_seconds, attitude_estimated, setpoint, throttle) plots.append(plot_pid_response(trace, ulog.data_list, plot_config, 'Angle').bokeh_plot) except Exception as e: print(type(e), axis, ":", e) div = Div(text="<p><b>Error</b>: Attitude PID analysis failed. Possible " "error causes are: logged data rate is too low/data missing, " "or there is not enough motion for the analysis.</p>", width=int(plot_width*0.9)) plots.insert(0, column(div, width=int(plot_width*0.9))) pid_analysis_error = True return plots
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()))