Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
687 changes: 687 additions & 0 deletions docs/notebooks/controller_usage.ipynb

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions rocketpy/Controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
__author__ = ""
__copyright__ = "Copyright 20XX, RocketPy Team"
__license__ = "MIT"

import numpy as np


class Controller:
"""Keeps controller information.

Attributes
----------
Controller attributes:
name : string
Parachute name, such as drogue and main. Has no impact in
simulation, as it is only used to display data in a more
organized matter.
trigger : function
Function which defines if the parachute ejection system is
to be triggered. It must take as input the freestream
pressure in pascal and the state vector of the simulation,
which is defined by [x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz].
It will be called according to the sampling rate given next.
It should return True if the parachute ejection system is
to be triggered and False otherwise.
samplingRate : float
Sampling rate in which the trigger function works. It is used to
simulate the refresh rate of onboard sensors such as barometers.
Default value is 100. Value must be given in hertz.
lag : float
Time between the parachute ejection system is triggered and the
parachute is fully opened. During this time, the simulation will
consider the rocket as flying without a parachute. Default value
is 0. Must be given in seconds.
noiseBias : float
Mean value of the noise added to the pressure signal, which is
passed to the trigger function. Unit is in pascal.
noiseDeviation : float
Standard deviation of the noise added to the pressure signal,
which is passed to the trigger function. Unit is in pascal.
noiseCorr : tuple, list
Tuple with the correlation between noise and time.
noiseSignal : list
List of (t, noise signal) corresponding to signal passed to
trigger function. Completed after running a simulation.
noisyPressureSignal : list
List of (t, noisy pressure signal) that is passed to the
trigger function. Completed after running a simulation.
cleanPressureSignal : list
List of (t, clean pressure signal) corresponding to signal passed to
trigger function. Completed after running a simulation.
noiseSignalFunction : Function
Function of noiseSignal.
noisyPressureSignalFunction : Function
Function of noisyPressureSignal.
cleanPressureSignalFunction : Function
Function of cleanPressureSignal.
"""

def __init__(
self,
name,
controllerFunction,
samplingRate,
lag,
):
"""Initializes Parachute class.
Parameters
----------
name : string
Name of the parachute.
CdS : float
CdS of the parachute.
Trigger : function
Trigger function.
samplingRate : float
Sampling rate, in hertz, for the Trigger function.
lag : float
Time, in seconds, between the parachute ejection system is triggered and the
parachute is fully opened.
noise : tuple, list, optional
List in the format (mean, standard deviation, time-correlation).
The values are used to add noise to the pressure signal which is
passed to the trigger function. Default value is (0, 0, 0). Units
are in pascal.
Returns
-------
None
"""
self.name = name
self.controllerFunction = controllerFunction
self.samplingRate = samplingRate
self.lag = lag

return None
24 changes: 22 additions & 2 deletions rocketpy/Flight.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ def __init__(
self.env = environment
self.rocket = rocket
self.parachutes = self.rocket.parachutes[:]
self.controllers = self.rocket.controllers[:]
self.inclination = inclination
self.heading = heading
self.maxTime = maxTime
Expand Down Expand Up @@ -736,6 +737,9 @@ def __init__(
# Add non-overshootable parachute time nodes
if self.timeOvershoot is False:
phase.timeNodes.addParachutes(self.parachutes, phase.t, phase.timeBound)
phase.timeNodes.addControllers(
self.controllers, phase.t, phase.timeBound
)
# Add lst time node to permanent list
phase.timeNodes.addNode(phase.timeBound, [], [])
# Sort time nodes
Expand Down Expand Up @@ -3927,6 +3931,20 @@ def addParachutes(self, parachutes, t_init, t_end):
]
self.list += parachute_node_list

def addControllers(self, controllers, t_init, t_end):
# Iterate over parachutes
for controller in controllers:
# Calculate start of sampling time nodes
controllerTimeStep = 1 / controller.samplingRate
controller_node_list = [
self.TimeNode(i * controllerTimeStep, [], [controller])
for i in range(
math.ceil(t_init / controllerTimeStep),
math.floor(t_end / controllerTimeStep) + 1,
)
]
self.list += controller_node_list

def sort(self):
self.list.sort(key=(lambda node: node.t))

Expand All @@ -3950,10 +3968,12 @@ def flushAfter(self, index):
del self.list[index + 1 :]

class TimeNode:
def __init__(self, t, parachutes, callbacks):
def __init__(self, t, parachutes, controllers):
self.t = t
self.parachutes = parachutes
self.callbacks = callbacks
self.callbacks = [
controller.controllerFunction for controller in controllers
]

def __repr__(self):
return (
Expand Down
105 changes: 105 additions & 0 deletions rocketpy/Rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .Function import Function
from .Parachute import Parachute
from .Controller import Controller


class Rocket:
Expand Down Expand Up @@ -176,6 +177,9 @@ def __init__(
# Parachute data initialization
self.parachutes = []

# Controller data initialization
self.controllers = []

# Rail button data initialization
self.railButtons = None

Expand Down Expand Up @@ -689,6 +693,16 @@ def finNumCorrection(n):
"cp": (0, 0, cpz),
"cl": cl,
"roll parameters": rollParameters,
"metadata": {
"n": n,
"span": span,
"rootChord": rootChord,
"tipChord": tipChord,
"distanceToCM": distanceToCM,
"radius": radius,
"cantAngle": cantAngle,
"airfoil": airfoil,
},
"name": "Fins",
}
self.aerodynamicSurfaces.append(fin)
Expand All @@ -699,6 +713,34 @@ def finNumCorrection(n):
# Return self
return self.aerodynamicSurfaces[-1]

def changeCantAngle(self, newCantAngle, finGroup, maximumCantAngleChange=None):
"""Changes the cant angle of a fin group. Useful for canard control

Parameters
----------
newCantAngle : float
New cant angle in radians.
finGroup : dict
Dictionary with the fin group meta data."""

if maximumCantAngleChange:
if newCantAngle > finGroup["metadata"]["cantAngle"]:
newCantAngle = (
finGroup["metadata"]["cantAngle"] + maximumCantAngleChange
)
elif newCantAngle < finGroup["metadata"]["cantAngle"]:
newCantAngle = (
finGroup["metadata"]["cantAngle"] - maximumCantAngleChange
)

finGroup["metadata"]["cantAngle"] = newCantAngle # in radians

newFinGroup = self.addFins(**finGroup["metadata"])

self.aerodynamicSurfaces.remove(finGroup)

return newFinGroup

def addParachute(
self, name, CdS, trigger, samplingRate=100, lag=0, noise=(0, 0, 0)
):
Expand Down Expand Up @@ -758,6 +800,57 @@ def addParachute(
# Return self
return self.parachutes[-1]

def addController(
self,
name,
controllerFunction,
samplingRate=100,
lag=0,
):
"""Creates a new parachute, storing its parameters such as
opening delay, drag coefficients and trigger function.

Parameters
----------
name : string
Parachute name, such as drogue and main. Has no impact in
simulation, as it is only used to display data in a more
organized matter.
trigger : function
Function which defines if the parachute ejection system is
to be triggered. It must take as input the freestream
pressure in pascal and the state vector of the simulation,
which is defined by [x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz].
It will be called according to the sampling rate given next.
It should return True if the parachute ejection system is
to be triggered and False otherwise.
samplingRate : float, optional
Sampling rate in which the trigger function works. It is used to
simulate the refresh rate of onboard sensors such as barometers.
Default value is 100. Value must be given in hertz.
lag : float, optional
Time between the parachute ejection system is triggered and the
parachute is fully opened. During this time, the simulation will
consider the rocket as flying without a parachute. Default value
is 0. Must be given in seconds.

Returns
-------
parachute : Parachute
Parachute containing trigger, samplingRate, lag, CdS, noise
and name. Furthermore, it stores cleanPressureSignal,
noiseSignal and noisyPressureSignal which are filled in during
Flight simulation.
"""
# Create a controller
controller = Controller(name, controllerFunction, samplingRate, lag)

# Add parachute to list of parachutes
self.controllers.append(controller)

# Return self
return self.controllers[-1]

def setRailButtons(self, distanceToCM, angularPosition=45):
"""Adds rail buttons to the rocket, allowing for the
calculation of forces exerted by them when the rocket is
Expand Down Expand Up @@ -915,6 +1008,12 @@ def info(self):
print("\n" + chute.name.title() + " Parachute")
print("CdS Coefficient: " + str(chute.CdS) + " m2")

# Print controller data
for controller in self.controllers:
print("\n" + controller.name.title() + " Controller")
print("Sampling Rate: " + str(controller.samplingRate) + " Hz")
print("Lag: " + str(controller.lag) + " s")

# Show plots
print("\nAerodynamics Plots")
self.powerOnDrag()
Expand Down Expand Up @@ -1009,6 +1108,12 @@ def allInfo(self):
"parachute is fully opened: " + str(chute.lag) + " s"
)

# Print controller data
for controller in self.controllers:
print("\n" + controller.name.title() + " Controller")
print("Sampling Rate: " + str(controller.samplingRate) + " Hz")
print("Lag: " + str(controller.lag) + " s")

# Show plots
print("\nMass Plots")
self.totalMass()
Expand Down