Sensor Class usage#

This code aims to briefly exemplify the usage of the Sensor classes.

[6]:
import matplotlib.pyplot as plt
import numpy as np

from rocketpy import Environment, SolidMotor, Rocket, Flight
[7]:
%matplotlib inline

Create an Environment#

[8]:
env = Environment(latitude=32.990254, longitude=-106.974998, elevation=1400)
env.set_atmospheric_model(
    type="custom_atmosphere", wind_u=[(0, 3), (10000, 3)], wind_v=[(0, 5), (10000, -5)]
)
env.info()

Gravity Details

Acceleration of gravity at surface level:    9.7913 m/s²
Acceleration of gravity at  80.000 km (ASL): 9.5534 m/s²


Launch Site Details

Launch Site Latitude: 32.99025°
Launch Site Longitude: -106.97500°
Reference Datum: SIRGAS2000
Launch Site UTM coordinates: 315468.64 W    3651938.65 N
Launch Site UTM zone: 13S
Launch Site Surface Elevation: 1400.0 m


Atmospheric Model Details

Atmospheric Model Type: custom_atmosphere
custom_atmosphere Maximum Height: 80.000 km

Surface Atmospheric Conditions

Surface Wind Speed: 5.83 m/s
Surface Wind Direction: 219.81°
Surface Wind Heading: 39.81°
Surface Pressure: 855.97 hPa
Surface Temperature: 279.07 K
Surface Air Density: 1.069 kg/m³
Surface Speed of Sound: 334.55 m/s


Earth Model Details

Earth Radius at Launch site: 6371.83 km
Semi-major Axis: 6378.14 km
Semi-minor Axis: 6356.75 km
Flattening: 0.0034


Atmospheric Model Plots

../_images/notebooks_sensors_4_1.png

Create a Motor#

[9]:
Pro75M1670 = SolidMotor(
    thrust_source="../../data/motors/cesaroni/Cesaroni_M1670.eng",
    dry_mass=1.815,
    dry_inertia=(0.125, 0.125, 0.002),
    nozzle_radius=33 / 1000,
    grain_number=5,
    grain_density=1815,
    grain_outer_radius=33 / 1000,
    grain_initial_inner_radius=15 / 1000,
    grain_initial_height=120 / 1000,
    grain_separation=5 / 1000,
    grains_center_of_mass_position=0.397,
    center_of_dry_mass_position=0.317,
    nozzle_position=0,
    burn_time=3.9,
    throat_radius=11 / 1000,
    coordinate_system_orientation="nozzle_to_combustion_chamber",
)

Create a Rocket#

[11]:
calisto = Rocket(
    radius=127 / 2000,
    mass=14.426,
    inertia=(6.321, 6.321, 0.034),
    power_off_drag="../../data/rockets/calisto/powerOffDragCurve.csv",
    power_on_drag="../../data/rockets/calisto/powerOnDragCurve.csv",
    center_of_mass_without_motor=0,
    coordinate_system_orientation="tail_to_nose",
)

rail_buttons = calisto.set_rail_buttons(
    upper_button_position=0.0818,
    lower_button_position=-0.618,
    angular_position=45,
)

calisto.add_motor(Pro75M1670, position=-1.255)

nose_cone = calisto.add_nose(length=0.55829, kind="vonKarman", position=1.278)

fin_set = calisto.add_trapezoidal_fins(
    n=4,
    root_chord=0.120,
    tip_chord=0.060,
    span=0.110,
    position=-1.04956,
    cant_angle=0.5,
    airfoil=("../../data/airfoils/NACA0012-radians.txt", "radians"),
)

tail = calisto.add_tail(
    top_radius=0.0635, bottom_radius=0.0435, length=0.060, position=-1.194656
)

Adds Sensors to the Rocket#

[12]:
from rocketpy import Accelerometer, Gyroscope, Barometer, GnssReceiver

accel_noisy_nosecone = Accelerometer(
    sampling_rate=100,
    consider_gravity=False,
    orientation=(60, 60, 60),
    measurement_range=70,
    resolution=0.4882,
    noise_density=0.05,
    random_walk_density=0.02,
    constant_bias=1,
    operating_temperature=25,
    temperature_bias=0.02,
    temperature_scale_factor=0.02,
    cross_axis_sensitivity=0.02,
    name='Accelerometer in Nosecone',
)
accel_clean_cdm = Accelerometer(
    sampling_rate=100,
    consider_gravity=False,
    orientation=[
        [0.25, -0.0581, 0.9665],
        [0.433, 0.8995, -0.0581],
        [-0.8661, 0.433, 0.25],
    ],
    name='Accelerometer in CDM',
)
calisto.add_sensor(accel_noisy_nosecone, 1.278)
calisto.add_sensor(accel_clean_cdm, -0.10482544178314143)  # , 127/2000)
C:\Users\guiga\Documents\github\RocketPy\rocketpy\sensors\sensor.py:120: UserWarning: The Sensor class (and all its subclasses) is still under experimental development. Some features may be changed in future versions, although we will try to keep the changes to a minimum.
  warnings.warn(
[13]:
accel_noisy_nosecone.prints.all()
accel_clean_cdm.prints.all()  # should have the same rotation matrix
Identification:

Name:                      Accelerometer in Nosecone
Type:                      Accelerometer

Orientation of the Sensor:

Orientation:               (60, 60, 60)
Normal Vector:             (0.7500000000000712, -0.43301270189204105, 0.5000000000000475)
Rotation Matrix:
                           [-0.12, -0.65, 0.75]
                           [0.65, -0.62, -0.43]
                           [0.75, 0.43, 0.5]

Quantization:

Measurement Range:         -70 to 70 (m/s^2)
Resolution:                0.4882 m/s^2/LSB

Noise:

Noise Density:             (0.05, 0.05, 0.05) m/s^2/√Hz
Noise Variance:            (1, 1, 1) (m/s^2)^2
Random Walk Density:       (0.02, 0.02, 0.02) m/s^2/√Hz
Random Walk Variance:      (1, 1, 1) (m/s^2)^2
Constant Bias:             (1, 1, 1) m/s^2
Operating Temperature:     25 K
Temperature Bias:          (0.02, 0.02, 0.02) m/s^2/K
Temperature Scale Factor:  (0.02, 0.02, 0.02) %/K
Cross Axis Sensitivity:    0.02 %
Identification:

Name:                      Accelerometer in CDM
Type:                      Accelerometer

Orientation of the Sensor:

Orientation:               [[0.25, -0.0581, 0.9665], [0.433, 0.8995, -0.0581], [-0.8661, 0.433, 0.25]]
Normal Vector:             (0.9665010341566599, -0.05810006216709978, 0.25000026750042936)
Rotation Matrix:
                           [0.25, -0.06, 0.97]
                           [0.43, 0.9, -0.06]
                           [-0.87, 0.43, 0.25]

Quantization:

Measurement Range:         -inf to inf (m/s^2)
Resolution:                0 m/s^2/LSB

Noise:

Noise Density:             (0, 0, 0) m/s^2/√Hz
Noise Variance:            (1, 1, 1) (m/s^2)^2
Random Walk Density:       (0, 0, 0) m/s^2/√Hz
Random Walk Variance:      (1, 1, 1) (m/s^2)^2
Constant Bias:             (0, 0, 0) m/s^2
Operating Temperature:     25 K
Temperature Bias:          (0, 0, 0) m/s^2/K
Temperature Scale Factor:  (0, 0, 0) %/K
Cross Axis Sensitivity:    0 %
[14]:
gyro_clean = Gyroscope(sampling_rate=100)
gyro_noisy = Gyroscope(
    sampling_rate=100,
    resolution=0.001064225153655079,
    orientation=(-60, -60, -60),
    noise_density=[0, 0.03, 0.05],
    noise_variance=1.01,
    random_walk_density=[0, 0.01, 0.02],
    random_walk_variance=[1, 1, 1.05],
    constant_bias=[0, 0.3, 0.5],
    operating_temperature=25,
    temperature_bias=[0, 0.01, 0.02],
    temperature_scale_factor=[0, 0.01, 0.02],
    cross_axis_sensitivity=0.5,
    acceleration_sensitivity=[0, 0.0008, 0.0017],
    name="Gyroscope",
)
calisto.add_sensor(gyro_clean, -0.10482544178314143)  # +0.5, 127/2000)
calisto.add_sensor(gyro_noisy, (1.278 - 0.4, 127 / 2000 - 127 / 4000, 0))
[15]:
barometer_clean = Barometer(
    sampling_rate=50,
    measurement_range=100000,
    resolution=0.16,
    noise_density=19,
    noise_variance=19,
    random_walk_density=0.01,
    constant_bias=1,
    operating_temperature=25,
    temperature_bias=0.02,
    temperature_scale_factor=0.02,
)
calisto.add_sensor(barometer_clean, (-0.10482544178314143 + 0.5, -127 / 2000, 0))
[16]:
gnss_clean = GnssReceiver(
    sampling_rate=1,
    position_accuracy=1,
    altitude_accuracy=1,
)
calisto.add_sensor(gnss_clean, (-0.10482544178314143 + 0.5, +127 / 2000, 0))
[17]:
calisto.draw(plane="xz")
../_images/notebooks_sensors_15_0.png
[18]:
calisto.draw(plane="yz")
../_images/notebooks_sensors_16_0.png

Add Air Brakes to the Rocket#

[19]:
def controller_function(
    time, sampling_rate, state, state_history, observed_variables, air_brakes, sensors
):
    # state = [x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz]
    altitude_ASL = state[2]
    altitude_AGL = altitude_ASL - env.elevation
    vx, vy, vz = state[3], state[4], state[5]

    # Get winds in x and y directions
    wind_x, wind_y = env.wind_velocity_x(altitude_ASL), env.wind_velocity_y(
        altitude_ASL
    )

    # Calculate Mach number
    free_stream_speed = ((wind_x - vx) ** 2 + (wind_y - vy) ** 2 + (vz) ** 2) ** 0.5
    mach_number = free_stream_speed / env.speed_of_sound(altitude_ASL)

    # Get previous state from state_history
    previous_state = state_history[-1]
    previous_vz = previous_state[5]

    # If we wanted to we could get the returned values from observed_variables:
    # returned_time, deployment_level, drag_coefficient = observed_variables[-1]

    # Check if the rocket has reached burnout
    accelerometer = sensors[0]
    if accelerometer.measurement[2] > 0:
        return None

    # If below 1500 meters above ground level, air_brakes are not deployed
    if altitude_AGL < 1500:
        air_brakes.deployment_level = 0

    # Else calculate the deployment level
    else:
        # Controller logic
        new_deployment_level = (
            air_brakes.deployment_level + 0.1 * vz + 0.01 * previous_vz**2
        )

        # Limiting the speed of the air_brakes to 0.2 per second
        # Since this function is called every 1/sampling_rate seconds
        # the max change in deployment level per call is 0.2/sampling_rate
        max_change = 0.2 / sampling_rate
        lower_bound = air_brakes.deployment_level - max_change
        upper_bound = air_brakes.deployment_level + max_change
        new_deployment_level = min(max(new_deployment_level, lower_bound), upper_bound)

        air_brakes.deployment_level = new_deployment_level

    # Return variables of interest to be saved in the observed_variables list
    return (
        time,
        air_brakes.deployment_level,
        air_brakes.drag_coefficient(air_brakes.deployment_level, mach_number),
    )
[20]:
air_brakes = calisto.add_air_brakes(
    drag_coefficient_curve="../../data/rockets/calisto/air_brakes_cd.csv",
    controller_function=controller_function,
    sampling_rate=10,
    reference_area=None,
    clamp=True,
    initial_observed_variables=[0, 0, 0],
    override_rocket_drag=False,
    name="AirBrakes",
    controller_name="AirBrakes Controller",
)
[21]:
# air_brakes.all_info()

Simulate Flight#

[22]:
test_flight = Flight(
    rocket=calisto,
    environment=env,
    rail_length=5.2,
    inclination=85,
    heading=0,
    time_overshoot=False,
    terminate_on_apogee=True,
)

Visualize the results#

[23]:
# To export sensor data to a csv file:
barometer_clean.export_measured_data("exported_barometer_data.csv")
Data saved to exported_barometer_data.csv
[24]:
# To visualize the altitude.
# Notice that the air brakes makes the rocket achieve a perfect apogee
test_flight.altitude()
test_flight.pressure()
../_images/notebooks_sensors_25_0.png
../_images/notebooks_sensors_25_1.png

Accelerometer#

[25]:
# get first column of every row as time from [(time,(ax,ay,az)),...] = a.measured_data
time1, ax, ay, az = zip(*accel_noisy_nosecone.measured_data)
time2, bx, by, bz = zip(*accel_clean_cdm.measured_data)


plt.plot(time1, ax, label="Noisy Accelerometer")
plt.plot(time2, bx, label="Clean Accelerometer")
plt.xlabel("Time (s)")
plt.ylabel("Acceleration ax (m/s^2)")
plt.legend()
plt.grid()
plt.title("Acceleration comparison - ax")
plt.show()

plt.plot(time1, ay, label="Noisy Accelerometer")
plt.plot(time2, by, label="Clean Accelerometer")
plt.xlabel("Time (s)")
plt.ylabel("Acceleration ay (m/s^2)")
plt.legend()
plt.grid()
plt.title("Acceleration comparison - ay")
plt.show()

plt.plot(time1, az, label="Noisy Accelerometer")
plt.plot(time2, bz, label="Clean Accelerometer")
plt.xlabel("Time (s)")
plt.ylabel("Acceleration az (m/s^2)")
plt.legend()
plt.grid()
plt.title("Acceleration comparison - az")
plt.show()
../_images/notebooks_sensors_27_0.png
../_images/notebooks_sensors_27_1.png
../_images/notebooks_sensors_27_2.png

Total Acceleration#

[26]:
abs_a = (np.array(ax) ** 2 + np.array(ay) ** 2 + np.array(az) ** 2) ** 0.5
abs_b = (np.array(bx) ** 2 + np.array(by) ** 2 + np.array(bz) ** 2) ** 0.5

plt.plot(time2, abs_b, label="clean")
plt.plot(time1, abs_a, label="noisy")
plt.xlabel("Time (s)")
plt.ylabel("Acceleration (m/s^2)")
plt.legend()
plt.xlim(0, 20)
plt.grid()
plt.title("Acceleration")
plt.show()
../_images/notebooks_sensors_29_0.png

Gyroscope#

[27]:
time1, wx, wy, wz = zip(*gyro_noisy.measured_data)
time2, zx, zy, zz = zip(*gyro_clean.measured_data)

plt.plot(time1, wx, label='Noisy Gyroscope')
# plt.plot(time2, zx, label='Clean Gyroscope')
plt.legend()
plt.show()

plt.plot(time1, wy, label='Noisy Gyroscope')
# plt.plot(time2, zy, label='Clean Gyroscope')
plt.legend()
plt.show()

plt.plot(time1, wz, label='Noisy Gyroscope')
plt.xlim(0, 4)
# plt.plot(time2, zz, label='Clean Gyroscope')
plt.legend()
plt.show()

# now plot the total angular velocity

abs_w = (np.array(wx) ** 2 + np.array(wy) ** 2 + np.array(wz) ** 2) ** 0.5
abs_z = (np.array(zx) ** 2 + np.array(zy) ** 2 + np.array(zz) ** 2) ** 0.5
plt.plot(time1, abs_w, label="noisy")
plt.plot(time2, abs_z, label="clean")
plt.xlabel("Time (s)")
plt.ylabel("Angular Velocity (rad/s)")
plt.legend()
plt.xlim(0, 10)
plt.show()
../_images/notebooks_sensors_31_0.png
../_images/notebooks_sensors_31_1.png
../_images/notebooks_sensors_31_2.png
../_images/notebooks_sensors_31_3.png

Barometer#

[28]:
time_barometer, pressure_barometer = zip(*barometer_clean.measured_data)

rocket_pressure = test_flight.pressure.y_array
rocket_time = test_flight.pressure.x_array

plt.plot(rocket_time, rocket_pressure, label="Rocket")
plt.plot(time_barometer, pressure_barometer, label="Barometer")
plt.xlabel("Time (s)")
plt.ylabel("Pressure (Pa)")
plt.title("Pressure comparison")
plt.grid()
plt.legend()
plt.show()
../_images/notebooks_sensors_33_0.png