import plotly.express as px
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
Flight Dynamics¶
For this part we will look at the flight dynamics of the rocket. We will use the accelerometer data to estimate the flight path of the rocket.
We already know that during the ascent, the fins stabilised the rocket and we had no tumbling. This means we can just use the magnitue of the acceleration to estimate the flight path.
LAUNCH_TIME = 442461
df = pd.read_csv("mission-timeline.csv")
imu = pd.read_csv('data/AccelData-20250405-162645.csv')
imu = imu.iloc[1000:]
imu = imu[imu['timestamp'] > 440000]
imu['mission_time'] = (imu.timestamp - LAUNCH_TIME) / 1000
imu = imu.set_index('mission_time')
imu['total_accel'] = np.sqrt(imu['accelX']**2 + imu['accelY']**2 + imu['accelZ']**2)
imu.head()
time | timestamp | accelX | accelY | accelZ | gyroX | gyroY | gyroZ | temperature | total_accel | |
---|---|---|---|---|---|---|---|---|---|---|
mission_time | ||||||||||
-2.459 | 16:20:54.799000 | 440002 | -0.081984 | -0.155672 | -1.013576 | -1.40 | -2.24 | 2.24 | 42.7500 | 1.028733 |
-2.451 | 16:20:54.807000 | 440010 | -0.083448 | -0.093208 | -0.195688 | -1.19 | -2.59 | 1.82 | 42.7500 | 0.232261 |
-2.443 | 16:20:54.815000 | 440018 | -0.122976 | -0.087352 | -1.002840 | -1.82 | -2.73 | -8.12 | 42.6875 | 1.014121 |
-2.435 | 16:20:54.823000 | 440026 | -0.131272 | -0.179584 | -0.770064 | -2.80 | -0.91 | -4.20 | 42.6875 | 0.801549 |
-2.420 | 16:20:54.838000 | 440041 | 0.058072 | -0.064904 | -1.726544 | 0.49 | -1.96 | 12.88 | 42.6875 | 1.728739 |
imu[(imu.index < 0) & (imu.index > -0.6)]['total_accel'].plot()
<Axes: xlabel='mission_time'>
resting_accel = imu[(imu.index < 0) & (imu.index > -0.3)]['total_accel'].mean()
print("Resting", resting_accel)
print("Max\n", imu[['accelX', 'accelY', 'accelZ', 'total_accel']].max())
print("Min\n", imu[['accelX', 'accelY', 'accelZ', 'total_accel']].min())
Resting 1.0329131144010213 Max accelX 9.213928 accelY 13.235048 accelZ 13.162824 total_accel 17.335822 dtype: float64 Min accelX -5.569056 accelY -6.452336 accelZ -15.981024 total_accel 0.005478 dtype: float64
flight = imu[imu.index > 0].copy()
flight['accel_m_s2'] = (flight['total_accel'] - resting_accel) * 9.8
flight['dt'] = flight.index.diff().fillna(0)
flight['velocity'] = (flight['accel_m_s2'] * flight['dt']).cumsum()
flight['height'] = (flight['velocity'] * flight['dt']).cumsum()
flight
time | timestamp | accelX | accelY | accelZ | gyroX | gyroY | gyroZ | temperature | total_accel | accel_m_s2 | dt | velocity | height | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mission_time | ||||||||||||||
0.004 | 16:20:57.262000 | 442465 | -0.543144 | -1.331264 | -15.981024 | -15.890000 | -19.389999 | 11.900000 | 42.750 | 16.045572 | 147.124060 | 0.000 | 0.000000 | 0.000000 |
0.012 | 16:20:57.270000 | 442473 | -0.746640 | -0.967704 | -15.022592 | -16.730000 | 13.230000 | 36.049999 | 42.750 | 15.072232 | 137.585326 | 0.008 | 1.100683 | 0.008805 |
0.020 | 16:20:57.278000 | 442481 | -1.037488 | -0.344040 | -12.989096 | 1.330000 | 17.219999 | 12.880000 | 42.750 | 13.035005 | 117.620499 | 0.008 | 2.041647 | 0.025139 |
0.028 | 16:20:57.286000 | 442489 | -0.580720 | -2.834792 | -15.981024 | 6.160000 | -56.000000 | 65.660004 | 42.625 | 16.240887 | 149.038142 | 0.008 | 3.233952 | 0.051010 |
0.036 | 16:20:57.294000 | 442497 | -5.569056 | -3.758088 | -15.981024 | 79.519997 | -19.040001 | 15.470000 | 42.625 | 17.335822 | 159.768509 | 0.008 | 4.512100 | 0.087107 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
6.880 | 16:21:04.138000 | 449341 | 0.167384 | -2.013000 | 0.749568 | -399.559998 | -164.919998 | -610.049988 | 43.000 | 2.154539 | 10.991934 | 0.008 | -21.857350 | -59.348033 |
6.903 | 16:21:04.161000 | 449364 | 0.546072 | -2.473184 | 0.694424 | -533.539978 | -261.380005 | -548.729980 | 43.000 | 2.626225 | 15.614459 | 0.023 | -21.498217 | -59.842492 |
6.925 | 16:21:04.183000 | 449386 | 1.321016 | -2.814784 | 0.880352 | -593.460022 | -316.399994 | -486.709991 | 43.000 | 3.231580 | 21.546940 | 0.022 | -21.024185 | -60.305024 |
6.941 | 16:21:04.199000 | 449402 | 1.448384 | -3.236904 | 0.466528 | -562.659973 | -357.630005 | -429.589996 | 43.000 | 3.576732 | 24.929425 | 0.016 | -20.625314 | -60.635029 |
6.949 | 16:21:04.207000 | 449410 | 1.177544 | -3.361344 | 0.577792 | -509.390015 | -369.040009 | -408.660004 | 43.000 | 3.608197 | 25.237785 | 0.008 | -20.423412 | -60.798416 |
753 rows × 14 columns
# Plotting
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(flight.index, flight['velocity'], label='Velocity (m/s)')
plt.ylabel("Velocity (m/s)")
plt.grid(True)
plt.legend()
plt.subplot(2, 1, 2)
plt.plot(flight.index, flight['height'], label='Height (m)', color='orange')
plt.ylabel("Height (m)")
plt.xlabel("Mission Time (s)")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
imu[(imu.index > -0.05) & (imu.index < 0.15)].reset_index()
mission_time | time | timestamp | accelX | accelY | accelZ | gyroX | gyroY | gyroZ | temperature | total_accel | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | -0.044 | 16:20:57.214000 | 442417 | -0.048800 | -0.173240 | -1.009672 | 1.050000 | -0.910000 | 0.560000 | 42.6875 | 1.025588 |
1 | -0.036 | 16:20:57.222000 | 442425 | -0.041480 | -0.169824 | -1.012600 | 1.190000 | -1.260000 | 3.500000 | 42.7500 | 1.027579 |
2 | -0.028 | 16:20:57.230000 | 442433 | -0.033672 | -0.167872 | -1.015528 | 1.050000 | -1.750000 | 4.200000 | 42.7500 | 1.029860 |
3 | -0.020 | 16:20:57.238000 | 442441 | -0.028792 | -0.149816 | -1.013576 | 0.840000 | -2.310000 | 3.290000 | 42.7500 | 1.024993 |
4 | -0.012 | 16:20:57.246000 | 442449 | -0.035624 | -0.133224 | -1.017968 | -0.070000 | -3.430000 | 0.420000 | 42.8125 | 1.027267 |
5 | -0.004 | 16:20:57.254000 | 442457 | -0.082960 | -0.112728 | -1.014064 | 0.000000 | -3.780000 | -3.220000 | 42.8125 | 1.023678 |
6 | 0.004 | 16:20:57.262000 | 442465 | -0.543144 | -1.331264 | -15.981024 | -15.890000 | -19.389999 | 11.900000 | 42.7500 | 16.045572 |
7 | 0.012 | 16:20:57.270000 | 442473 | -0.746640 | -0.967704 | -15.022592 | -16.730000 | 13.230000 | 36.049999 | 42.7500 | 15.072232 |
8 | 0.020 | 16:20:57.278000 | 442481 | -1.037488 | -0.344040 | -12.989096 | 1.330000 | 17.219999 | 12.880000 | 42.7500 | 13.035005 |
9 | 0.028 | 16:20:57.286000 | 442489 | -0.580720 | -2.834792 | -15.981024 | 6.160000 | -56.000000 | 65.660004 | 42.6250 | 16.240887 |
10 | 0.036 | 16:20:57.294000 | 442497 | -5.569056 | -3.758088 | -15.981024 | 79.519997 | -19.040001 | 15.470000 | 42.6250 | 17.335822 |
11 | 0.044 | 16:20:57.302000 | 442505 | -3.574600 | -3.686352 | -15.981024 | 57.680000 | -8.050000 | 39.060001 | 42.4375 | 16.785711 |
12 | 0.052 | 16:20:57.310000 | 442513 | -4.168008 | -3.229584 | -15.981024 | 16.799999 | -36.330002 | 14.140000 | 42.4375 | 16.828417 |
13 | 0.059 | 16:20:57.317000 | 442520 | -4.291960 | -2.993392 | -15.981024 | 59.430000 | -54.459999 | 1.400000 | 41.9375 | 16.815898 |
14 | 0.067 | 16:20:57.325000 | 442528 | -3.885944 | -3.452112 | -15.981024 | 42.910000 | -6.580000 | -18.969999 | 41.9375 | 16.805081 |
15 | 0.075 | 16:20:57.333000 | 442536 | -3.794688 | -2.925072 | -15.981024 | 9.940000 | 16.799999 | -42.279999 | 41.9375 | 16.683789 |
16 | 0.083 | 16:20:57.341000 | 442544 | -2.411208 | -2.056432 | -15.981024 | -22.260000 | 42.980000 | -75.599998 | 41.2500 | 16.292205 |
17 | 0.091 | 16:20:57.349000 | 442552 | -1.585024 | -1.223416 | -15.981024 | -49.279999 | 73.779999 | -100.589996 | 41.2500 | 16.105967 |
18 | 0.106 | 16:20:57.364000 | 442567 | -0.865224 | -0.149816 | -3.614616 | -92.050003 | 74.199997 | -154.419998 | 41.0625 | 3.719745 |
19 | 0.114 | 16:20:57.372000 | 442575 | -0.428464 | 0.502152 | 1.268312 | -70.419998 | 51.590000 | -196.490005 | 41.0625 | 1.429809 |
20 | 0.122 | 16:20:57.380000 | 442583 | -0.166896 | 0.188856 | 1.723616 | -106.260002 | 62.369999 | -212.940002 | 40.1875 | 1.741945 |
21 | 0.130 | 16:20:57.388000 | 442591 | -0.652456 | 0.385520 | 1.062864 | -98.349998 | 52.709999 | -235.619995 | 40.1875 | 1.305375 |
22 | 0.138 | 16:20:57.396000 | 442599 | -0.334280 | 0.146400 | 0.671488 | -99.120003 | 50.259998 | -250.669998 | 40.3750 | 0.764246 |
23 | 0.146 | 16:20:57.404000 | 442607 | -0.377224 | -0.116632 | 1.174128 | -100.800003 | 43.959999 | -262.920013 | 40.3750 | 1.238740 |
The integration of acceleration data from the GY-LSM6DS3 IMU during the bottle rocket launch significantly underestimated the rocket’s actual flight profile. IMU-derived data suggested a maximum altitude of just over 20 meters, while barometric measurements indicated apogee occurred near 50 meters. The calculated landing point was also off by roughly 50 meters, showing a final altitude of –50 meters instead of ground level — confirmed by signal loss coinciding with impact. While initial analysis suggested the IMU data was within range, a closer inspection revealed clear evidence of sensor saturation: the accelZ readings were clipped at –15.981 g for multiple consecutive samples during the 0.1-second thrust phase. This confirms that the LSM6DS3, configured with a ±16g range, was unable to capture the true peak accelerations during launch. As a result, the integrated acceleration underestimated the initial velocity and, by extension, both apogee and descent time. Although the IMU performed accurately during lower-G phases of flight, this clipping event during the most critical moment of thrust directly accounts for the trajectory error.