def update_vehicle_db_entry(cur, ulog, log_id, vehicle_name): """ Update the Vehicle DB entry :param cur: DB cursor :param ulog: ULog object :param vehicle_name: new vehicle name or '' if not updated """ vehicle_data = DBVehicleData() if 'sys_uuid' in ulog.msg_info_dict: vehicle_data.uuid = cgi.escape(ulog.msg_info_dict['sys_uuid']) if vehicle_name == '': cur.execute('select Name ' 'from Vehicle where UUID = ?', [vehicle_data.uuid]) db_tuple = cur.fetchone() if db_tuple is not None: vehicle_data.name = db_tuple[0] print('reading vehicle name from db:' + vehicle_data.name) else: vehicle_data.name = vehicle_name print('vehicle name from uploader:' + vehicle_data.name) vehicle_data.log_id = log_id flight_time = get_total_flight_time(ulog) if flight_time is not None: vehicle_data.flight_time = flight_time # update or insert the DB entry cur.execute( 'insert or replace into Vehicle (UUID, LatestLogId, Name, FlightTime)' 'values (?, ?, ?, ?)', [ vehicle_data.uuid, vehicle_data.log_id, vehicle_data.name, vehicle_data.flight_time ])
def get_info_table_html(ulog, px4_ulog, db_data, vehicle_data, vtol_states): """ Get the html (as string) for a table with additional text info, such as logging duration, max speed etc. """ ### Setup the text for the left table with various information ### table_text_left = [] # airframe airframe_name_tuple = get_airframe_name(ulog, True) if airframe_name_tuple is not None: airframe_name, airframe_id = airframe_name_tuple if len(airframe_name) == 0: table_text_left.append(('Airframe', airframe_id)) else: table_text_left.append( ('Airframe', airframe_name + ' <small>(' + airframe_id + ')</small>')) # HW & SW sys_hardware = '' if 'ver_hw' in ulog.msg_info_dict: sys_hardware = escape(ulog.msg_info_dict['ver_hw']) if 'ver_hw_subtype' in ulog.msg_info_dict: sys_hardware += ' (' + escape( ulog.msg_info_dict['ver_hw_subtype']) + ')' table_text_left.append(('Hardware', sys_hardware)) release_str = ulog.get_version_info_str() if release_str is None: release_str = '' release_str_suffix = '' else: release_str += ' <small>(' release_str_suffix = ')</small>' branch_info = '' if 'ver_sw_branch' in ulog.msg_info_dict: branch_info = '<br> branch: ' + ulog.msg_info_dict['ver_sw_branch'] if 'ver_sw' in ulog.msg_info_dict: ver_sw = escape(ulog.msg_info_dict['ver_sw']) ver_sw_link = 'https://github.com/PX4/Firmware/commit/' + ver_sw table_text_left.append( ('Software Version', release_str + '<a href="' + ver_sw_link + '" target="_blank">' + ver_sw[:8] + '</a>' + release_str_suffix + branch_info)) if 'sys_os_name' in ulog.msg_info_dict and 'sys_os_ver_release' in ulog.msg_info_dict: os_name = escape(ulog.msg_info_dict['sys_os_name']) os_ver = ulog.get_version_info_str('sys_os_ver_release') if os_ver is not None: table_text_left.append(('OS Version', os_name + ', ' + os_ver)) table_text_left.append(('Estimator', px4_ulog.get_estimator())) table_text_left.append(('', '')) # spacing # logging start time & date try: # get the first non-zero timestamp gps_data = ulog.get_dataset('vehicle_gps_position') indices = np.nonzero(gps_data.data['time_utc_usec']) if len(indices[0]) > 0: # we use the timestamp from the log and then convert it with JS to # display with local timezone. # In addition we add a tooltip to show the timezone from the log logging_start_time = int( gps_data.data['time_utc_usec'][indices[0][0]] / 1000000) utc_offset_min = ulog.initial_parameters.get('SDLOG_UTC_OFFSET', 0) utctimestamp = datetime.datetime.utcfromtimestamp( logging_start_time + utc_offset_min * 60).replace(tzinfo=datetime.timezone.utc) tooltip = '''This is your local timezone. <br /> Log timezone: {} <br /> SDLOG_UTC_OFFSET: {}'''.format(utctimestamp.strftime('%d-%m-%Y %H:%M'), utc_offset_min) tooltip = 'data-toggle="tooltip" data-delay=\'{"show":0, "hide":100}\' '+ \ 'title="'+tooltip+'" ' table_text_left.append( ('Logging Start ' + '<i ' + tooltip + ' class="fa fa-question" aria-hidden="true" ' + 'style="font-size: larger; color:#666"></i>', '<span style="display:none" id="logging-start-element">' + str(logging_start_time) + '</span>')) except: # Ignore. Eg. if topic not found pass # logging duration m, s = divmod(int((ulog.last_timestamp - ulog.start_timestamp) / 1e6), 60) h, m = divmod(m, 60) table_text_left.append( ('Logging Duration', '{:d}:{:02d}:{:02d}'.format(h, m, s))) # dropouts dropout_durations = [dropout.duration for dropout in ulog.dropouts] if len(dropout_durations) > 0: total_duration = sum(dropout_durations) / 1000 if total_duration > 5: total_duration_str = '{:.0f}'.format(total_duration) else: total_duration_str = '{:.2f}'.format(total_duration) table_text_left.append( ('Dropouts', '{:} ({:} s)'.format(len(dropout_durations), total_duration_str))) # total vehicle flight time flight_time_s = get_total_flight_time(ulog) if flight_time_s is not None: m, s = divmod(int(flight_time_s), 60) h, m = divmod(m, 60) days, h = divmod(h, 24) flight_time_str = '' if days > 0: flight_time_str += '{:d} days '.format(days) if h > 0: flight_time_str += '{:d} hours '.format(h) if m > 0: flight_time_str += '{:d} minutes '.format(m) flight_time_str += '{:d} seconds '.format(s) table_text_left.append( ('Vehicle Life<br/>Flight Time', flight_time_str)) table_text_left.append(('', '')) # spacing # vehicle UUID (and name if provided). SITL does not have a (valid) UUID if 'sys_uuid' in ulog.msg_info_dict and sys_hardware != 'SITL' and \ sys_hardware != 'PX4_SITL': sys_uuid = escape(ulog.msg_info_dict['sys_uuid']) if vehicle_data is not None and vehicle_data.name != '': sys_uuid = sys_uuid + ' (' + vehicle_data.name + ')' if len(sys_uuid) > 0: table_text_left.append(('Vehicle UUID', sys_uuid)) table_text_left.append(('', '')) # spacing # Wind speed, rating, feedback if db_data.wind_speed >= 0: table_text_left.append(('Wind Speed', db_data.wind_speed_str())) if len(db_data.rating) > 0: table_text_left.append(('Flight Rating', db_data.rating_str())) if len(db_data.feedback) > 0: table_text_left.append( ('Feedback', db_data.feedback.replace('\n', '<br/>'))) if len(db_data.video_url) > 0: table_text_left.append( ('Video', '<a href="' + db_data.video_url + '" target="_blank">' + db_data.video_url + '</a>')) ### Setup the text for the right table: estimated numbers (e.g. max speed) ### table_text_right = [] try: local_pos = ulog.get_dataset('vehicle_local_position') pos_x = local_pos.data['x'] pos_y = local_pos.data['y'] pos_z = local_pos.data['z'] pos_xyz_valid = np.multiply(local_pos.data['xy_valid'], local_pos.data['z_valid']) local_vel_valid_indices = np.argwhere( np.multiply(local_pos.data['v_xy_valid'], local_pos.data['v_z_valid']) > 0) vel_x = local_pos.data['vx'][local_vel_valid_indices] vel_y = local_pos.data['vy'][local_vel_valid_indices] vel_z = local_pos.data['vz'][local_vel_valid_indices] # total distance (take only valid indexes) total_dist_m = 0 last_index = -2 for valid_index in np.argwhere(pos_xyz_valid > 0): index = valid_index[0] if index == last_index + 1: dx = pos_x[index] - pos_x[last_index] dy = pos_y[index] - pos_y[last_index] dz = pos_z[index] - pos_z[last_index] total_dist_m += sqrt(dx * dx + dy * dy + dz * dz) last_index = index if total_dist_m < 1: pass # ignore elif total_dist_m > 1000: table_text_right.append( ('Distance', "{:.2f} km".format(total_dist_m / 1000))) else: table_text_right.append( ('Distance', "{:.1f} m".format(total_dist_m))) if len(pos_z) > 0: max_alt_diff = np.amax(pos_z) - np.amin(pos_z) table_text_right.append( ('Max Altitude Difference', "{:.0f} m".format(max_alt_diff))) table_text_right.append(('', '')) # spacing # Speed if len(vel_x) > 0: max_h_speed = np.amax(np.sqrt(np.square(vel_x) + np.square(vel_y))) speed_vector = np.sqrt( np.square(vel_x) + np.square(vel_y) + np.square(vel_z)) max_speed = np.amax(speed_vector) if vtol_states is None: mean_speed = np.mean(speed_vector) table_text_right.append( ('Average Speed', "{:.1f} km/h".format(mean_speed * 3.6))) else: local_pos_timestamp = local_pos.data['timestamp'][ local_vel_valid_indices] speed_vector = speed_vector.reshape((len(speed_vector), )) mean_speed_mc, mean_speed_fw = _get_vtol_means_per_mode( vtol_states, local_pos_timestamp, speed_vector) if mean_speed_mc is not None: table_text_right.append( ('Average Speed MC', "{:.1f} km/h".format(mean_speed_mc * 3.6))) if mean_speed_fw is not None: table_text_right.append( ('Average Speed FW', "{:.1f} km/h".format(mean_speed_fw * 3.6))) table_text_right.append( ('Max Speed', "{:.1f} km/h".format(max_speed * 3.6))) table_text_right.append(('Max Speed Horizontal', "{:.1f} km/h".format(max_h_speed * 3.6))) table_text_right.append( ('Max Speed Up', "{:.1f} km/h".format(np.amax(-vel_z) * 3.6))) table_text_right.append( ('Max Speed Down', "{:.1f} km/h".format(-np.amin(-vel_z) * 3.6))) table_text_right.append(('', '')) # spacing vehicle_attitude = ulog.get_dataset('vehicle_attitude') roll = vehicle_attitude.data['roll'] pitch = vehicle_attitude.data['pitch'] if len(roll) > 0: # tilt = angle between [0,0,1] and [0,0,1] rotated by roll and pitch tilt_angle = np.arccos(np.multiply(np.cos(pitch), np.cos(roll))) * 180 / np.pi table_text_right.append( ('Max Tilt Angle', "{:.1f} deg".format(np.amax(tilt_angle)))) rollspeed = vehicle_attitude.data['rollspeed'] pitchspeed = vehicle_attitude.data['pitchspeed'] yawspeed = vehicle_attitude.data['yawspeed'] if len(rollspeed) > 0: max_rot_speed = np.amax( np.sqrt( np.square(rollspeed) + np.square(pitchspeed) + np.square(yawspeed))) table_text_right.append( ('Max Rotation Speed', "{:.1f} deg/s".format(max_rot_speed * 180 / np.pi))) table_text_right.append(('', '')) # spacing battery_status = ulog.get_dataset('battery_status') battery_current = battery_status.data['current_a'] if len(battery_current) > 0: max_current = np.amax(battery_current) if max_current > 0.1: if vtol_states is None: mean_current = np.mean(battery_current) table_text_right.append( ('Average Current', "{:.1f} A".format(mean_current))) else: mean_current_mc, mean_current_fw = _get_vtol_means_per_mode( vtol_states, battery_status.data['timestamp'], battery_current) if mean_current_mc is not None: table_text_right.append( ('Average Current MC', "{:.1f} A".format(mean_current_mc))) if mean_current_fw is not None: table_text_right.append( ('Average Current FW', "{:.1f} A".format(mean_current_fw))) table_text_right.append( ('Max Current', "{:.1f} A".format(max_current))) except: pass # ignore (e.g. if topic not found) # generate the tables def generate_html_table(rows_list, tooltip=None, max_width=None): """ return the html table (str) from a row list of tuples """ if tooltip is None: tooltip = '' else: tooltip = 'data-toggle="tooltip" data-placement="left" '+ \ 'data-delay=\'{"show": 1000, "hide": 100}\' title="'+tooltip+'" ' table = '<table ' + tooltip if max_width is not None: table += ' style="max-width: ' + max_width + ';"' table += '>' padding_text = '' for label, value in rows_list: if label == '': # empty label means: add some row spacing padding_text = ' style="padding-top: 0.5em;" ' else: table += ('<tr><td ' + padding_text + 'class="left">' + label + ':</td><td' + padding_text + '>' + value + '</td></tr>') padding_text = '' return table + '</table>' left_table = generate_html_table(table_text_left, max_width='65%') right_table = generate_html_table( table_text_right, 'Note: most of these values are based on estimations from the vehicle,' ' and thus require an accurate estimator') html_tables = ( '<p><div style="display: flex; justify-content: space-between;">' + left_table + right_table + '</div></p>') return html_tables
def get_heading_and_info(ulog, px4_ulog, plot_width, db_data, vehicle_data, vtol_states): """ get a bokeh widgetbox object with the html heading text and some tables with additional text info, such as logging duration, max speed etc. """ # Heading sys_name = '' if 'sys_name' in ulog.msg_info_dict: sys_name = cgi.escape(ulog.msg_info_dict['sys_name']) + ' ' div = Div(text="<h1>" + sys_name + px4_ulog.get_mav_type() + "</h1>", width=int(plot_width * 0.9)) header_divs = [div] if db_data.description != '': div_descr = Div(text="<h4>" + db_data.description + "</h4>", width=int(plot_width * 0.9)) header_divs.append(div_descr) ### Setup the text for the left table with various information ### table_text_left = [] # airframe airframe_name_tuple = get_airframe_name(ulog, True) if airframe_name_tuple is not None: airframe_name, airframe_id = airframe_name_tuple if len(airframe_name) == 0: table_text_left.append(('Airframe', airframe_id)) else: table_text_left.append( ('Airframe', airframe_name + ' <small>(' + airframe_id + ')</small>')) # HW & SW sys_hardware = '' if 'ver_hw' in ulog.msg_info_dict: sys_hardware = cgi.escape(ulog.msg_info_dict['ver_hw']) table_text_left.append(('Hardware', sys_hardware)) release_str = ulog.get_version_info_str() if release_str is None: release_str = '' release_str_suffix = '' else: release_str += ' <small>(' release_str_suffix = ')</small>' branch_info = '' if 'ver_sw_branch' in ulog.msg_info_dict: branch_info = '<br> branch: ' + ulog.msg_info_dict['ver_sw_branch'] if 'ver_sw' in ulog.msg_info_dict: ver_sw = cgi.escape(ulog.msg_info_dict['ver_sw']) ver_sw_link = 'https://github.com/PX4/Firmware/commit/' + ver_sw table_text_left.append( ('Software Version', release_str + '<a href="' + ver_sw_link + '" target="_blank">' + ver_sw[:8] + '</a>' + release_str_suffix + branch_info)) if 'sys_os_name' in ulog.msg_info_dict and 'sys_os_ver_release' in ulog.msg_info_dict: os_name = cgi.escape(ulog.msg_info_dict['sys_os_name']) os_ver = ulog.get_version_info_str('sys_os_ver_release') if os_ver is not None: table_text_left.append(('OS Version', os_name + ', ' + os_ver)) table_text_left.append(('Estimator', px4_ulog.get_estimator())) table_text_left.append(('', '')) # spacing # logging start time & date try: # get the first non-zero timestamp gps_data = ulog.get_dataset('vehicle_gps_position') indices = np.nonzero(gps_data.data['time_utc_usec']) if len(indices[0]) > 0: # we use the timestamp from the log and then convert it with JS to # display with local timezone logging_start_time = int( gps_data.data['time_utc_usec'][indices[0][0]] / 1000000) js_code = """ <script type="text/javascript"> var logging_span = $('#logging-start-element'); var d = new Date(0); d.setUTCSeconds(logging_span.text()); var date_str = ("0" + d.getDate()).slice(-2) + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + d.getFullYear() + " " + ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2); logging_span.text(date_str); logging_span.show(); </script> """ table_text_left.append( ('Logging Start', '<span style="display:none" id="logging-start-element">' + str(logging_start_time) + '</span>' + js_code)) except: # Ignore. Eg. if topic not found pass # logging duration m, s = divmod(int((ulog.last_timestamp - ulog.start_timestamp) / 1e6), 60) h, m = divmod(m, 60) table_text_left.append( ('Logging Duration', '{:d}:{:02d}:{:02d}'.format(h, m, s))) # dropouts dropout_durations = [dropout.duration for dropout in ulog.dropouts] if len(dropout_durations) > 0: total_duration = sum(dropout_durations) / 1000 if total_duration > 5: total_duration_str = '{:.0f}'.format(total_duration) else: total_duration_str = '{:.2f}'.format(total_duration) table_text_left.append( ('Dropouts', '{:} ({:} s)'.format(len(dropout_durations), total_duration_str))) # total vehicle flight time flight_time_s = get_total_flight_time(ulog) if flight_time_s is not None: m, s = divmod(int(flight_time_s), 60) h, m = divmod(m, 60) days, h = divmod(h, 24) flight_time_str = '' if days > 0: flight_time_str += '{:d} days '.format(days) if h > 0: flight_time_str += '{:d} hours '.format(h) if m > 0: flight_time_str += '{:d} minutes '.format(m) flight_time_str += '{:d} seconds '.format(s) table_text_left.append(('Vehicle Flight Time', flight_time_str)) table_text_left.append(('', '')) # spacing # vehicle UUID (and name if provided). SITL does not have a UUID if 'sys_uuid' in ulog.msg_info_dict and sys_hardware != 'SITL': sys_uuid = cgi.escape(ulog.msg_info_dict['sys_uuid']) if vehicle_data is not None and vehicle_data.name != '': sys_uuid = sys_uuid + ' (' + vehicle_data.name + ')' if len(sys_uuid) > 0: table_text_left.append(('Vehicle UUID', sys_uuid)) table_text_left.append(('', '')) # spacing # Wind speed, rating, feedback if db_data.wind_speed >= 0: table_text_left.append(('Wind Speed', db_data.wind_speed_str())) if len(db_data.rating) > 0: table_text_left.append(('Flight Rating', db_data.rating_str())) if len(db_data.feedback) > 0: table_text_left.append( ('Feedback', db_data.feedback.replace('\n', '<br/>'))) if len(db_data.video_url) > 0: table_text_left.append( ('Video', '<a href="' + db_data.video_url + '" target="_blank">' + db_data.video_url + '</a>')) ### Setup the text for the right table: estimated numbers (e.g. max speed) ### table_text_right = [] try: local_pos = ulog.get_dataset('vehicle_local_position') pos_x = local_pos.data['x'] pos_y = local_pos.data['y'] pos_z = local_pos.data['z'] pos_xyz_valid = np.multiply(local_pos.data['xy_valid'], local_pos.data['z_valid']) local_vel_valid_indices = np.argwhere( np.multiply(local_pos.data['v_xy_valid'], local_pos.data['v_z_valid']) > 0) vel_x = local_pos.data['vx'][local_vel_valid_indices] vel_y = local_pos.data['vy'][local_vel_valid_indices] vel_z = local_pos.data['vz'][local_vel_valid_indices] # total distance (take only valid indexes) total_dist_m = 0 last_index = -2 for valid_index in np.argwhere(pos_xyz_valid > 0): index = valid_index[0] if index == last_index + 1: dx = pos_x[index] - pos_x[last_index] dy = pos_y[index] - pos_y[last_index] dz = pos_z[index] - pos_z[last_index] total_dist_m += sqrt(dx * dx + dy * dy + dz * dz) last_index = index if total_dist_m < 1: pass # ignore elif total_dist_m > 1000: table_text_right.append( ('Distance', "{:.2f} km".format(total_dist_m / 1000))) else: table_text_right.append( ('Distance', "{:.1f} m".format(total_dist_m))) if len(pos_z) > 0: max_alt_diff = np.amax(pos_z) - np.amin(pos_z) table_text_right.append( ('Max Altitude Difference', "{:.0f} m".format(max_alt_diff))) table_text_right.append(('', '')) # spacing # Speed if len(vel_x) > 0: max_h_speed = np.amax(np.sqrt(np.square(vel_x) + np.square(vel_y))) speed_vector = np.sqrt( np.square(vel_x) + np.square(vel_y) + np.square(vel_z)) max_speed = np.amax(speed_vector) if vtol_states is None: mean_speed = np.mean(speed_vector) table_text_right.append( ('Average Speed', "{:.1f} km/h".format(mean_speed * 3.6))) else: local_pos_timestamp = local_pos.data['timestamp'][ local_vel_valid_indices] speed_vector = speed_vector.reshape((len(speed_vector), )) mean_speed_mc, mean_speed_fw = _get_vtol_means_per_mode( vtol_states, local_pos_timestamp, speed_vector) if mean_speed_mc is not None: table_text_right.append( ('Average Speed MC', "{:.1f} km/h".format(mean_speed_mc * 3.6))) if mean_speed_fw is not None: table_text_right.append( ('Average Speed FW', "{:.1f} km/h".format(mean_speed_fw * 3.6))) table_text_right.append( ('Max Speed', "{:.1f} km/h".format(max_speed * 3.6))) table_text_right.append(('Max Speed Horizontal', "{:.1f} km/h".format(max_h_speed * 3.6))) table_text_right.append( ('Max Speed Up', "{:.1f} km/h".format(np.amax(-vel_z) * 3.6))) table_text_right.append( ('Max Speed Down', "{:.1f} km/h".format(-np.amin(-vel_z) * 3.6))) table_text_right.append(('', '')) # spacing vehicle_attitude = ulog.get_dataset('vehicle_attitude') roll = vehicle_attitude.data['roll'] pitch = vehicle_attitude.data['pitch'] if len(roll) > 0: # tilt = angle between [0,0,1] and [0,0,1] rotated by roll and pitch tilt_angle = np.arccos(np.multiply(np.cos(pitch), np.cos(roll))) * 180 / np.pi table_text_right.append( ('Max Tilt Angle', "{:.1f} deg".format(np.amax(tilt_angle)))) rollspeed = vehicle_attitude.data['rollspeed'] pitchspeed = vehicle_attitude.data['pitchspeed'] yawspeed = vehicle_attitude.data['yawspeed'] if len(rollspeed) > 0: max_rot_speed = np.amax( np.sqrt( np.square(rollspeed) + np.square(pitchspeed) + np.square(yawspeed))) table_text_right.append( ('Max Rotation Speed', "{:.1f} deg/s".format(max_rot_speed * 180 / np.pi))) table_text_right.append(('', '')) # spacing battery_status = ulog.get_dataset('battery_status') battery_current = battery_status.data['current_a'] if len(battery_current) > 0: max_current = np.amax(battery_current) if max_current > 0.1: if vtol_states is None: mean_current = np.mean(battery_current) table_text_right.append( ('Average Current', "{:.1f} A".format(mean_current))) else: mean_current_mc, mean_current_fw = _get_vtol_means_per_mode( vtol_states, battery_status.data['timestamp'], battery_current) if mean_current_mc is not None: table_text_right.append( ('Average Current MC', "{:.1f} A".format(mean_current_mc))) if mean_current_fw is not None: table_text_right.append( ('Average Current FW', "{:.1f} A".format(mean_current_fw))) table_text_right.append( ('Max Current', "{:.1f} A".format(max_current))) except: pass # ignore (e.g. if topic not found) # generate the tables def generate_html_table(rows_list, tooltip=None, max_width=None): """ return the html table (str) from a row list of tuples """ if tooltip is None: tooltip = '' else: tooltip = 'data-toggle="tooltip" delay="{show: 500, hide: 100}" title="' + tooltip + '" ' table = '<table ' + tooltip if max_width is not None: table += ' style="max-width: ' + max_width + ';"' table += '>' padding_text = '' for label, value in rows_list: if label == '': # empty label means: add some row spacing padding_text = ' style="padding-top: 0.5em;" ' else: table += ('<tr><td ' + padding_text + 'class="left">' + label + ':</td><td' + padding_text + '>' + value + '</td></tr>') padding_text = '' return table + '</table>' left_table = generate_html_table(table_text_left, max_width='65%') right_table = generate_html_table( table_text_right, 'Note: most of these values are based on estimations from the vehicle,' ' and thus requiring an accurate estimator') html_tables = ( '<div style="display: flex; justify-content: space-between;">' + left_table + right_table + '</div>') header_divs.append(Div(text=html_tables)) return widgetbox(header_divs, width=int(plot_width * 0.99))