In [1]:
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.

In [2]:
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()
Out[2]:
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
In [3]:
imu[(imu.index < 0) & (imu.index > -0.6)]['total_accel'].plot()
Out[3]:
<Axes: xlabel='mission_time'>
No description has been provided for this image
In [4]:
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
In [5]:
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
Out[5]:
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

In [6]:
# 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()
No description has been provided for this image
In [7]:
imu[(imu.index > -0.05) & (imu.index < 0.15)].reset_index()
Out[7]:
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.