In [1]:
from pyulog import ULog
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
plt.style.use('dark_background')
LOG_PATH = '../data/logs/log_0_2026-6-6-22-21-20.ulg'
ulog = ULog(LOG_PATH)
pos = ulog.get_dataset('vehicle_local_position')
t = pos.data['timestamp']
duration = (t[-1] - t[0]) / 1e6
print(f"Log loaded ✅")
print(f"Duration: {duration:.1f}s ({duration/60:.1f} min)")
print(f"Topics available: {[d.name for d in ulog.data_list]}")
Log loaded ✅ Duration: 334.2s (5.6 min) Topics available: ['actuator_armed', 'actuator_motors', 'actuator_outputs', 'battery_status', 'config_overrides', 'control_allocator_status', 'cpuload', 'distance_sensor_mode_change_request', 'ekf2_timestamps', 'esc_status', 'estimator_aid_src_baro_hgt', 'estimator_aid_src_gnss_hgt', 'estimator_aid_src_gnss_pos', 'estimator_aid_src_gnss_vel', 'estimator_aid_src_gravity', 'estimator_aid_src_mag', 'estimator_baro_bias', 'estimator_event_flags', 'estimator_fusion_control', 'estimator_gps_status', 'estimator_innovation_test_ratios', 'estimator_innovation_variances', 'estimator_innovations', 'estimator_sensor_bias', 'estimator_states', 'estimator_status', 'estimator_status_flags', 'event', 'failsafe_flags', 'failure_detector_status', 'hover_thrust_estimate', 'landing_gear', 'logger_status', 'mission_result', 'navigator_mission_item', 'navigator_status', 'position_setpoint_triplet', 'rate_ctrl_status', 'rtl_status', 'rtl_time_estimate', 'sensor_accel', 'sensor_baro', 'sensor_baro', 'sensor_combined', 'sensor_gps', 'sensor_gyro', 'sensor_mag', 'sensors_status_imu', 'system_power', 'takeoff_status', 'telemetry_status', 'telemetry_status', 'telemetry_status', 'telemetry_status', 'trajectory_setpoint', 'vehicle_acceleration', 'vehicle_air_data', 'vehicle_angular_velocity', 'vehicle_angular_velocity_groundtruth', 'vehicle_attitude', 'vehicle_attitude_groundtruth', 'vehicle_attitude_setpoint', 'vehicle_command', 'vehicle_command_ack', 'vehicle_constraints', 'vehicle_control_mode', 'vehicle_global_position', 'vehicle_global_position_groundtruth', 'vehicle_gps_position', 'vehicle_imu', 'vehicle_imu_status', 'vehicle_land_detected', 'vehicle_local_position', 'vehicle_local_position_groundtruth', 'vehicle_local_position_setpoint', 'vehicle_magnetometer', 'vehicle_rates_setpoint', 'vehicle_status', 'vehicle_thrust_setpoint', 'vehicle_torque_setpoint', 'yaw_estimator_status']
In [2]:
gps = ulog.get_dataset('vehicle_global_position')
t_gps = (gps.data['timestamp'] - gps.data['timestamp'][0]) / 1e6
lat = gps.data['lat']
lon = gps.data['lon']
alt = gps.data['alt']
# Failsafe trigger time (relative)
FAILSAFE_T = 268.5
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle('Sprint 02 · Signal Loss Failsafe · Flight Path',
color='white', fontsize=14, fontweight='bold')
# Top-down path
ax1 = axes[0]
ax1.plot(lon, lat, color='#00FF88', linewidth=1.2, label='Flight path')
# Mark failsafe trigger point
idx_fs = np.argmin(np.abs(t_gps - FAILSAFE_T))
ax1.scatter(lon[idx_fs], lat[idx_fs], color='#FF6B6B', s=120, zorder=5, label=f'RTL triggered (t={FAILSAFE_T}s)')
ax1.scatter(lon[0], lat[0], color='#FFD700', s=80, zorder=5, label='Start')
ax1.scatter(lon[-1], lat[-1], color='#4ECDC4', s=80, zorder=5, label='End')
ax1.set_xlabel('Longitude', color='#888888')
ax1.set_ylabel('Latitude', color='#888888')
ax1.set_title('Top-Down Path', color='white')
ax1.legend(fontsize=9)
ax1.tick_params(colors='#888888')
# Altitude profile
ax2 = axes[1]
ax2.plot(t_gps, alt, color='#00BFFF', linewidth=1.2)
ax2.axvline(x=FAILSAFE_T, color='#FF6B6B', linestyle='--', linewidth=1.5, label=f'RTL triggered (t={FAILSAFE_T}s)')
ax2.set_xlabel('Time (s)', color='#888888')
ax2.set_ylabel('Altitude (m)', color='#888888')
ax2.set_title('Altitude Profile', color='white')
ax2.legend(fontsize=9)
ax2.tick_params(colors='#888888')
plt.tight_layout()
plt.savefig('../data/flight_path_failsafe_signal_loss.png', dpi=150, bbox_inches='tight')
plt.show()
print("Flight path saved ✅")
Flight path saved ✅
In [3]:
att = ulog.get_dataset('vehicle_attitude')
t_att = (att.data['timestamp'] - att.data['timestamp'][0]) / 1e6
q0 = att.data['q[0]']
q1 = att.data['q[1]']
q2 = att.data['q[2]']
q3 = att.data['q[3]']
roll = np.degrees(np.arctan2(2*(q0*q1+q2*q3), 1-2*(q1**2+q2**2)))
pitch = np.degrees(np.arcsin(np.clip(2*(q0*q2-q3*q1), -1, 1)))
yaw = np.degrees(np.arctan2(2*(q0*q3+q1*q2), 1-2*(q2**2+q3**2)))
fig, axes = plt.subplots(3, 1, figsize=(14, 8), sharex=True)
fig.suptitle('Sprint 02 · Signal Loss Failsafe · Attitude',
color='white', fontsize=14, fontweight='bold')
for ax, data, label, color in zip(
axes,
[roll, pitch, yaw],
['Roll (°)', 'Pitch (°)', 'Yaw (°)'],
['#FF6B6B', '#FFD700', '#4ECDC4']
):
ax.plot(t_att, data, color=color, linewidth=0.8)
ax.axvline(x=FAILSAFE_T, color='#FF6B6B', linestyle='--', linewidth=1.2, alpha=0.7)
ax.set_ylabel(label, color='#888888')
ax.tick_params(colors='#888888')
axes[-1].set_xlabel('Time (s)', color='#888888')
axes[1].annotate('RTL triggered', xy=(FAILSAFE_T, 0), xytext=(FAILSAFE_T+10, 10),
color='#FF6B6B', fontsize=9, arrowprops=dict(arrowstyle='->', color='#FF6B6B'))
plt.tight_layout()
plt.savefig('../data/attitude_failsafe_signal_loss.png', dpi=150, bbox_inches='tight')
plt.show()
print("Attitude saved ✅")
Attitude saved ✅
In [4]:
vs = ulog.get_dataset('vehicle_status')
t_vs = (vs.data['timestamp'] - vs.data['timestamp'][0]) / 1e6
nav = vs.data['nav_state']
NAV_LABELS = {3: 'Auto Mission', 5: 'RTL'}
COLORS = {3: '#00FF88', 5: '#FF6B6B'}
fig, ax = plt.subplots(figsize=(14, 4))
fig.suptitle('Sprint 02 · Signal Loss Failsafe · Nav State (Failsafe Event)',
color='white', fontsize=14, fontweight='bold')
ax.step(t_vs, nav, color='#00BFFF', linewidth=1.5, where='post')
ax.axvline(x=268.5, color='#FF6B6B', linestyle='--', linewidth=2, label='Signal loss → RTL (t=268.5s)')
ax.axvline(x=332.9, color='#FFD700', linestyle='--', linewidth=1.5, label='nav_state=3 resumed (t=332.9s)')
ax.set_yticks([3, 5])
ax.set_yticklabels(['3 — Auto Mission', '5 — RTL'], color='white')
ax.set_xlabel('Time (s)', color='#888888')
ax.set_ylabel('nav_state', color='#888888')
ax.tick_params(colors='#888888')
ax.legend(fontsize=9)
# Shade RTL window
ax.axvspan(268.5, 332.9, alpha=0.15, color='#FF6B6B', label='RTL active')
plt.tight_layout()
plt.show()
print("Nav state plot complete ✅")
Nav state plot complete ✅
In [5]:
print("=" * 55)
print(" SPRINT 02 — SIGNAL LOSS FAILSAFE TEST")
print("=" * 55)
print(f" Log file : log_0_2026-6-6-22-21-20.ulg")
print(f" Duration : {duration:.1f}s ({duration/60:.1f} min)")
print(f" Mission items: 42")
print()
print(" FAILSAFE TIMELINE")
print(f" t=0s : nav_state=3 — Auto Mission begins")
print(f" t=268.5s : nav_state=5 — Signal loss detected → RTL triggered")
print(f" t=332.9s : nav_state=3 — Connection restored / mission state")
print()
print(" RESULT: Failsafe triggered correctly ✅")
print(" Vehicle executed RTL autonomously")
print("=" * 55)
=======================================================
SPRINT 02 — SIGNAL LOSS FAILSAFE TEST
=======================================================
Log file : log_0_2026-6-6-22-21-20.ulg
Duration : 334.2s (5.6 min)
Mission items: 42
FAILSAFE TIMELINE
t=0s : nav_state=3 — Auto Mission begins
t=268.5s : nav_state=5 — Signal loss detected → RTL triggered
t=332.9s : nav_state=3 — Connection restored / mission state
RESULT: Failsafe triggered correctly ✅
Vehicle executed RTL autonomously
=======================================================
In [ ]: